@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.
- package/dist/cli/build-params-cli.d.ts +55 -0
- package/dist/cli/build-params-cli.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +22 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +8 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +33 -2
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts +28 -0
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +38 -8
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts +15 -4
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +16 -2
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lint/config.d.ts +1 -12
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/engine.d.ts +11 -1
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/rule.d.ts +14 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/project-root.d.ts +51 -0
- package/dist/project-root.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/cli/build-params-cli.test.ts +139 -0
- package/src/cli/build-params-cli.ts +107 -0
- package/src/cli/commands/build.ts +44 -41
- package/src/cli/commands/lint.test.ts +74 -0
- package/src/cli/commands/lint.ts +33 -9
- package/src/cli/handlers/build.test.ts +149 -0
- package/src/cli/handlers/build.ts +28 -8
- package/src/cli/handlers/run.test.ts +191 -5
- package/src/cli/handlers/run.ts +61 -8
- package/src/cli/lsp/server.ts +7 -2
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +17 -3
- package/src/cli/plugins.ts +10 -2
- package/src/components/cli-support.test.ts +221 -3
- package/src/components/cli-support.ts +37 -6
- package/src/components/discover.test.ts +63 -1
- package/src/components/discover.ts +42 -0
- package/src/config.ts +23 -0
- package/src/discovery/fold-import.ts +202 -20
- package/src/discovery/index.test.ts +131 -0
- package/src/discovery/index.ts +38 -8
- package/src/discovery/sandbox/fold-boundary.test.ts +254 -0
- package/src/fold/subset.test.ts +28 -14
- package/src/fold/subset.ts +16 -2
- package/src/lint/config.test.ts +9 -4
- package/src/lint/config.ts +7 -23
- package/src/lint/engine.ts +12 -0
- package/src/lint/policy.ts +5 -5
- package/src/lint/rule.ts +14 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +39 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +1 -1
- package/src/project-root.test.ts +105 -0
- 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
|
+
}
|