@intentius/chant 0.22.0 → 0.24.0

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.
Files changed (65) hide show
  1. package/dist/cli/build-params-cli.d.ts +55 -0
  2. package/dist/cli/build-params-cli.d.ts.map +1 -0
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/commands/lint.d.ts.map +1 -1
  5. package/dist/cli/handlers/build.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts +22 -1
  7. package/dist/cli/handlers/run.d.ts.map +1 -1
  8. package/dist/cli/lsp/server.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/plugins.d.ts +8 -0
  11. package/dist/cli/plugins.d.ts.map +1 -1
  12. package/dist/components/cli-support.d.ts +33 -2
  13. package/dist/components/cli-support.d.ts.map +1 -1
  14. package/dist/components/discover.d.ts +28 -0
  15. package/dist/components/discover.d.ts.map +1 -1
  16. package/dist/config.d.ts +18 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/discovery/fold-import.d.ts +38 -8
  19. package/dist/discovery/fold-import.d.ts.map +1 -1
  20. package/dist/discovery/index.d.ts +15 -4
  21. package/dist/discovery/index.d.ts.map +1 -1
  22. package/dist/fold/subset.d.ts +16 -2
  23. package/dist/fold/subset.d.ts.map +1 -1
  24. package/dist/lint/config.d.ts +1 -12
  25. package/dist/lint/config.d.ts.map +1 -1
  26. package/dist/lint/engine.d.ts +11 -1
  27. package/dist/lint/engine.d.ts.map +1 -1
  28. package/dist/lint/rule.d.ts +14 -0
  29. package/dist/lint/rule.d.ts.map +1 -1
  30. package/dist/project-root.d.ts +51 -0
  31. package/dist/project-root.d.ts.map +1 -0
  32. package/package.json +1 -1
  33. package/src/cli/build-params-cli.test.ts +139 -0
  34. package/src/cli/build-params-cli.ts +107 -0
  35. package/src/cli/commands/build.ts +44 -41
  36. package/src/cli/commands/lint.test.ts +74 -0
  37. package/src/cli/commands/lint.ts +33 -9
  38. package/src/cli/handlers/build.test.ts +149 -0
  39. package/src/cli/handlers/build.ts +28 -8
  40. package/src/cli/handlers/run.test.ts +191 -5
  41. package/src/cli/handlers/run.ts +61 -8
  42. package/src/cli/lsp/server.ts +7 -2
  43. package/src/cli/main.test.ts +23 -0
  44. package/src/cli/main.ts +17 -3
  45. package/src/cli/plugins.ts +10 -2
  46. package/src/components/cli-support.test.ts +221 -3
  47. package/src/components/cli-support.ts +37 -6
  48. package/src/components/discover.test.ts +63 -1
  49. package/src/components/discover.ts +42 -0
  50. package/src/config.ts +23 -0
  51. package/src/discovery/fold-import.ts +202 -20
  52. package/src/discovery/index.test.ts +131 -0
  53. package/src/discovery/index.ts +38 -8
  54. package/src/discovery/sandbox/fold-boundary.test.ts +254 -0
  55. package/src/fold/subset.test.ts +28 -14
  56. package/src/fold/subset.ts +16 -2
  57. package/src/lint/config.test.ts +9 -4
  58. package/src/lint/config.ts +7 -23
  59. package/src/lint/engine.ts +12 -0
  60. package/src/lint/policy.ts +5 -5
  61. package/src/lint/rule.ts +14 -0
  62. package/src/lint/rules/evl001-non-literal-expression.test.ts +39 -0
  63. package/src/lint/rules/evl001-non-literal-expression.ts +1 -1
  64. package/src/project-root.test.ts +105 -0
  65. package/src/project-root.ts +78 -0
@@ -0,0 +1,105 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { findProjectConfig, findProjectRoot } from "./project-root";
6
+
7
+ // Every test builds its own isolated directory tree under a fresh tmpdir —
8
+ // never reused across tests, and never anywhere near the real repo's own
9
+ // .git/package.json — so the boundary-stop behavior is exercised
10
+ // deterministically instead of depending on where this file happens to sit
11
+ // in the real chant checkout.
12
+ let root: string;
13
+
14
+ beforeEach(() => {
15
+ root = mkdtempSync(join(tmpdir(), "chant-project-root-"));
16
+ });
17
+
18
+ afterEach(() => {
19
+ rmSync(root, { recursive: true, force: true });
20
+ });
21
+
22
+ describe("findProjectConfig / findProjectRoot (chant #1117)", () => {
23
+ test("found at root — chant.config.ts in the start dir itself", () => {
24
+ writeFileSync(join(root, "chant.config.ts"), "export default {};");
25
+
26
+ const result = findProjectConfig(root);
27
+
28
+ expect(result.dir).toBe(root);
29
+ expect(result.configPath).toBe(join(root, "chant.config.ts"));
30
+ expect(findProjectRoot(root)).toBe(root);
31
+ });
32
+
33
+ test("found at an intermediate ancestor — several levels below the config", () => {
34
+ writeFileSync(join(root, "chant.config.json"), "{}");
35
+ const stackDir = join(root, "src", "stacks", "shared-foundation");
36
+ mkdirSync(stackDir, { recursive: true });
37
+
38
+ const result = findProjectConfig(stackDir);
39
+
40
+ expect(result.dir).toBe(root);
41
+ expect(result.configPath).toBe(join(root, "chant.config.json"));
42
+ expect(findProjectRoot(stackDir)).toBe(root);
43
+ });
44
+
45
+ test("not found — no chant.config, no .git, no package.json anywhere above start dir: falls back to start dir, not the filesystem root", () => {
46
+ const deep = join(root, "a", "b", "c");
47
+ mkdirSync(deep, { recursive: true });
48
+
49
+ const result = findProjectConfig(deep);
50
+
51
+ // Nothing between `deep` and the real filesystem root declares a chant
52
+ // config or a boundary marker (this mkdtemp tree carries none). Rather
53
+ // than adopting "/" as the project root — which would make a downstream
54
+ // caller that scopes a directory walk off this result (e.g.
55
+ // `resolveProjectLexicons` -> `findInfraFiles`) scan the entire disk —
56
+ // the walk gives up and returns `deep` itself unchanged.
57
+ expect(result.configPath).toBeUndefined();
58
+ expect(result.dir).toBe(deep);
59
+ expect(findProjectRoot(deep)).toBe(deep);
60
+ });
61
+
62
+ test("boundary stop — a .git/package.json ancestor halts the walk before an outer chant.config.ts", () => {
63
+ // An unrelated chant.config.ts two levels above this project's own git
64
+ // root must never be picked up — the boundary marker (.git here) stops
65
+ // the walk at the project's own root first.
66
+ writeFileSync(join(root, "chant.config.ts"), "export default { unrelated: true };");
67
+ const projectRoot = join(root, "project");
68
+ mkdirSync(join(projectRoot, ".git"), { recursive: true });
69
+ const stackDir = join(projectRoot, "src", "stack");
70
+ mkdirSync(stackDir, { recursive: true });
71
+
72
+ const result = findProjectConfig(stackDir);
73
+
74
+ expect(result.dir).toBe(projectRoot);
75
+ expect(result.configPath).toBeUndefined();
76
+ expect(findProjectRoot(stackDir)).toBe(projectRoot);
77
+ });
78
+
79
+ test("boundary stop — package.json (no .git) halts the walk the same way", () => {
80
+ writeFileSync(join(root, "chant.config.json"), "{}");
81
+ const projectRoot = join(root, "project");
82
+ mkdirSync(projectRoot, { recursive: true });
83
+ writeFileSync(join(projectRoot, "package.json"), "{}");
84
+ const stackDir = join(projectRoot, "src", "stack");
85
+ mkdirSync(stackDir, { recursive: true });
86
+
87
+ const result = findProjectConfig(stackDir);
88
+
89
+ expect(result.dir).toBe(projectRoot);
90
+ expect(result.configPath).toBeUndefined();
91
+ });
92
+
93
+ test("a config living exactly at the boundary dir is still found — boundary check never wins over a real config", () => {
94
+ const projectRoot = join(root, "project");
95
+ mkdirSync(join(projectRoot, ".git"), { recursive: true });
96
+ writeFileSync(join(projectRoot, "chant.config.ts"), "export default {};");
97
+ const stackDir = join(projectRoot, "src", "stack");
98
+ mkdirSync(stackDir, { recursive: true });
99
+
100
+ const result = findProjectConfig(stackDir);
101
+
102
+ expect(result.dir).toBe(projectRoot);
103
+ expect(result.configPath).toBe(join(projectRoot, "chant.config.ts"));
104
+ });
105
+ });
@@ -0,0 +1,78 @@
1
+ import { existsSync } from "fs";
2
+ import { dirname, join, resolve } from "path";
3
+
4
+ /**
5
+ * Shared upward config-discovery walk (chant #1117).
6
+ *
7
+ * Before this, `chant build <subdir>` and `lint.policies`
8
+ * (`./lint/policy.ts`'s `evaluateProjectPolicies`) each searched only the
9
+ * build directory and its immediate parent for `chant.config.ts`/`.json`,
10
+ * while `chant lint`/`chant graph` (`./lint/config.ts`'s old, file-local
11
+ * `findProjectRoot`) already walked all the way up. A project with a deeper
12
+ * `src/<stack>` layout — `chant build src/<stack>` two or more levels below
13
+ * the project root — silently never found the root config: `buildParams`'
14
+ * declared `env:` mappings went inert, `ownership`/`lint.policies`/etc quietly
15
+ * fell back to defaults, and nothing warned (loomster#162: `LOOM_TIER`/
16
+ * `LOOM_ENV` inert under every `npm run synth:*` for two releases).
17
+ *
18
+ * `findProjectConfig` is the one walk every config-discovery call site now
19
+ * shares. It stops at the first of:
20
+ *
21
+ * 1. A directory holding `chant.config.ts` or `chant.config.json` — found.
22
+ * 2. A directory holding `.git` or `package.json` with no chant config of its
23
+ * own — the project boundary. Discovery must never wander past the actual
24
+ * project into an unrelated ancestor directory just because this project
25
+ * happens not to declare a config (a stray `chant.config.ts` two levels
26
+ * above an unrelated git repo must never be picked up).
27
+ * 3. `startDir` itself, unchanged, if the walk reaches the filesystem root
28
+ * without ever finding a config OR a boundary marker. This is not just a
29
+ * "give up gracefully" nicety — several callers (`resolveProjectLexicons`
30
+ * -> `findInfraFiles`) scope a real directory walk off this function's
31
+ * result; if a rootless/marker-less start dir (a bare tmpdir, as chant's
32
+ * own test suites use) resolved all the way to `/`, that downstream walk
33
+ * would scan the entire filesystem instead of failing fast. Falling back
34
+ * to `startDir` keeps every caller's blast radius local no matter how far
35
+ * up the walk had to look.
36
+ */
37
+ export interface ProjectConfigSearch {
38
+ /** The resolved project root: the config's directory, the boundary directory, or the (resolved) `startDir` when neither was found. */
39
+ dir: string;
40
+ /** Absolute path to the discovered `chant.config.ts`/`.json`, if any. */
41
+ configPath?: string;
42
+ }
43
+
44
+ /** Walk up from `startDir` (inclusive) to the nearest chant config or project boundary. See {@link ProjectConfigSearch}. */
45
+ export function findProjectConfig(startDir: string): ProjectConfigSearch {
46
+ const resolvedStart = resolve(startDir);
47
+ let dir = resolvedStart;
48
+ for (;;) {
49
+ const tsPath = join(dir, "chant.config.ts");
50
+ if (existsSync(tsPath)) return { dir, configPath: tsPath };
51
+ const jsonPath = join(dir, "chant.config.json");
52
+ if (existsSync(jsonPath)) return { dir, configPath: jsonPath };
53
+
54
+ if (existsSync(join(dir, ".git")) || existsSync(join(dir, "package.json"))) {
55
+ return { dir };
56
+ }
57
+
58
+ const parent = dirname(dir);
59
+ if (parent === dir) {
60
+ // Filesystem root, no config, no boundary ever seen — give up rather
61
+ // than adopting "/" as the project root (see the module doc above).
62
+ return { dir: resolvedStart };
63
+ }
64
+ dir = parent;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Walk up from `startDir` to the nearest ancestor holding a chant project
70
+ * config (`chant.config.ts` or `chant.config.json`), the `.git`/`package.json`
71
+ * project boundary, or `startDir` itself when neither is found. Thin wrapper
72
+ * over {@link findProjectConfig} for callers that only need the directory
73
+ * (e.g. `chant lint`'s rule/plugin resolution, which just needs *a* stable
74
+ * root to resolve relative paths against).
75
+ */
76
+ export function findProjectRoot(startDir: string): string {
77
+ return findProjectConfig(startDir).dir;
78
+ }