Skip to content

Commit 789644a

Browse files
authored
Linter: Resolve custom rules from project root (marcoroth#945)
Resolves marcoroth#908
1 parent e332a42 commit 789644a

File tree

4 files changed

+238
-102
lines changed

4 files changed

+238
-102
lines changed

javascript/packages/config/src/config.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export class Config {
8585

8686
private static PROJECT_INDICATORS = [
8787
'.git',
88+
'.herb',
89+
'.herb.yml',
8890
'Gemfile',
8991
'package.json',
9092
'Rakefile',
@@ -424,6 +426,71 @@ export class Config {
424426
}
425427
}
426428

429+
/**
430+
* Find the project root by walking up from a given path.
431+
* Looks for .herb.yml first, then falls back to project indicators
432+
* (.git, Gemfile, package.json, etc.)
433+
*
434+
* @param startPath - File or directory path to start searching from
435+
* @returns The project root directory path
436+
*/
437+
static async findProjectRoot(startPath: string): Promise<string> {
438+
const { projectRoot } = await this.findConfigFile(startPath)
439+
440+
return projectRoot
441+
}
442+
443+
/**
444+
* Synchronous version of findProjectRoot for use in CLIs.
445+
*
446+
* @param startPath - File or directory path to start searching from
447+
* @returns The project root directory path
448+
*/
449+
static findProjectRootSync(startPath: string): string {
450+
const fsSync = require('fs')
451+
let currentPath = path.resolve(startPath)
452+
453+
try {
454+
const stats = fsSync.statSync(currentPath)
455+
456+
if (stats.isFile()) {
457+
currentPath = path.dirname(currentPath)
458+
}
459+
} catch {
460+
currentPath = path.resolve(process.cwd())
461+
}
462+
463+
while (true) {
464+
const configPath = path.join(currentPath, this.configPath)
465+
466+
try {
467+
fsSync.accessSync(configPath)
468+
469+
return currentPath
470+
} catch {
471+
// Config not in this directory, continue
472+
}
473+
474+
for (const indicator of this.PROJECT_INDICATORS) {
475+
try {
476+
fsSync.accessSync(path.join(currentPath, indicator))
477+
478+
return currentPath
479+
} catch {
480+
// Indicator not found, continue checking
481+
}
482+
}
483+
484+
const parentPath = path.dirname(currentPath)
485+
486+
if (parentPath === currentPath) {
487+
return process.cwd()
488+
}
489+
490+
currentPath = parentPath
491+
}
492+
}
493+
427494
/**
428495
* Read raw YAML content from a config file.
429496
* Handles both explicit .herb.yml paths and directory paths.

javascript/packages/linter/src/cli.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,7 @@ export class CLI {
6161
const resolvedPattern = resolve(pattern)
6262

6363
if (existsSync(resolvedPattern)) {
64-
const stats = statSync(resolvedPattern)
65-
66-
if (stats.isDirectory()) {
67-
this.projectPath = resolvedPattern
68-
} else {
69-
this.projectPath = dirname(resolvedPattern)
70-
}
64+
this.projectPath = Config.findProjectRootSync(resolvedPattern)
7165
}
7266
}
7367
}

0 commit comments

Comments
 (0)