@intentius/chant 0.18.26 → 0.18.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
- import { loadConfig, DEFAULT_CONFIG } from "./config";
2
+ import { loadConfig, DEFAULT_CONFIG, findProjectRoot } from "./config";
3
3
  import { writeFileSync, mkdirSync, rmSync } from "fs";
4
4
  import { join } from "path";
5
5
 
@@ -684,3 +684,31 @@ describe("loadConfig", () => {
684
684
  expect(config.rules?.["noisy-rule"]).toBe("off");
685
685
  });
686
686
  });
687
+
688
+ describe("findProjectRoot", () => {
689
+ test("returns the directory holding chant.config.json", () => {
690
+ writeFileSync(join(TEST_DIR, "chant.config.json"), "{}");
691
+ expect(findProjectRoot(TEST_DIR)).toBe(TEST_DIR);
692
+ });
693
+
694
+ test("walks up from a subpath to the config-bearing root", () => {
695
+ writeFileSync(join(TEST_DIR, "chant.config.json"), "{}");
696
+ const sub = join(TEST_DIR, "src", "lib");
697
+ mkdirSync(sub, { recursive: true });
698
+ expect(findProjectRoot(sub)).toBe(TEST_DIR);
699
+ });
700
+
701
+ test("also recognizes chant.config.ts as a project root", () => {
702
+ writeFileSync(join(TEST_DIR, "chant.config.ts"), "export default {};");
703
+ const sub = join(TEST_DIR, "src");
704
+ mkdirSync(sub, { recursive: true });
705
+ expect(findProjectRoot(sub)).toBe(TEST_DIR);
706
+ });
707
+
708
+ test("falls back to the start dir when no config is found", () => {
709
+ const sub = join(TEST_DIR, "nowhere");
710
+ mkdirSync(sub, { recursive: true });
711
+ // No chant.config anywhere under TEST_DIR — returns the (resolved) start dir.
712
+ expect(findProjectRoot(sub)).toBe(sub);
713
+ });
714
+ });
@@ -323,6 +323,29 @@ function loadConfigFile(configPath: string, visited: Set<string> = new Set()): L
323
323
  return mergedConfig;
324
324
  }
325
325
 
326
+ /**
327
+ * Walk up from `startDir` to the nearest ancestor holding a chant project
328
+ * config (`chant.config.ts` or `chant.config.json`). Returns that directory, or
329
+ * `startDir` unchanged when none is found before the filesystem root.
330
+ *
331
+ * Linting a subpath (`chant graph src --format ir`, `chant lint src/lib`) must
332
+ * still see the project-root config: its `lint.overrides` globs are written
333
+ * project-root-relative (`src/lib/**`), and a rule set scoped only to the lint
334
+ * arg would silently drop them. Config discovery therefore anchors on the
335
+ * project root, not the path being linted.
336
+ */
337
+ export function findProjectRoot(startDir: string): string {
338
+ let dir = resolve(startDir);
339
+ for (;;) {
340
+ if (existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json"))) {
341
+ return dir;
342
+ }
343
+ const parent = dirname(dir);
344
+ if (parent === dir) return resolve(startDir);
345
+ dir = parent;
346
+ }
347
+ }
348
+
326
349
  /**
327
350
  * Load lint configuration from a directory.
328
351
  *