@intentius/chant 0.124.30 → 0.124.32
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/bin/chant +7 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +23 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +74 -12
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +65 -0
- package/dist/cli/registry.d.ts +29 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +2 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/local-executor.d.ts +12 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -3
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/redact.d.ts +34 -0
- package/dist/op/redact.d.ts.map +1 -0
- package/dist/op/redact.js +94 -0
- package/dist/op/step-console.d.ts +2 -0
- package/dist/op/step-console.d.ts.map +1 -1
- package/dist/op/step-console.js +5 -2
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +27 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +51 -6
- package/dist/pr-loop.d.ts +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +10 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +52 -6
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +208 -208
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +108 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -0
- package/dist/workspace/ci-run-index.js +164 -0
- package/dist/workspace/ci-run-index.schema.json +52 -0
- package/dist/workspace/ci-run.d.ts +159 -9
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +419 -50
- package/dist/workspace/ci-runner-key.d.ts +133 -0
- package/dist/workspace/ci-runner-key.d.ts.map +1 -0
- package/dist/workspace/ci-runner-key.js +237 -0
- package/dist/workspace/ci-secrets.d.ts +65 -0
- package/dist/workspace/ci-secrets.d.ts.map +1 -0
- package/dist/workspace/ci-secrets.js +211 -0
- package/dist/workspace/ci-serve.d.ts +67 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +240 -35
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +74 -7
- package/dist/workspace/reason-codes.d.ts +7 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +9 -4
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/trust/trust-write.d.ts +109 -0
- package/dist/workspace/trust/trust-write.d.ts.map +1 -0
- package/dist/workspace/trust/trust-write.js +320 -0
- package/dist/workspace/trust-write.schema.json +265 -0
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +69 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +19 -0
- package/src/cli/main.ts +73 -12
- package/src/cli/parse-args.ts +48 -0
- package/src/cli/registry.ts +35 -4
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/local-executor.ts +31 -3
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/redact.ts +93 -0
- package/src/op/step-console.ts +7 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +71 -0
- package/src/pr-forge.ts +70 -6
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +58 -6
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +52 -0
- package/src/workspace/ci-run-index.test.ts +392 -0
- package/src/workspace/ci-run-index.ts +206 -0
- package/src/workspace/ci-run.test.ts +520 -42
- package/src/workspace/ci-run.ts +483 -51
- package/src/workspace/ci-runner-key.test.ts +204 -0
- package/src/workspace/ci-runner-key.ts +275 -0
- package/src/workspace/ci-secrets.test.ts +106 -0
- package/src/workspace/ci-secrets.ts +213 -0
- package/src/workspace/ci-serve.test.ts +282 -39
- package/src/workspace/ci-serve.ts +231 -34
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +9 -4
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/trust/trust-write.test.ts +179 -0
- package/src/workspace/trust/trust-write.ts +377 -0
- package/src/workspace/trust-write.schema.json +265 -0
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
|
@@ -274,13 +274,16 @@ describe("scaffold content validation", () => {
|
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
// #2090, the scaffolded package.json failed tier 1 as generated: a bare
|
|
277
|
-
// string exports["."]
|
|
278
|
-
//
|
|
279
|
-
test("package.json exports[\".\"]
|
|
277
|
+
// string exports["."] and a prepack that never bundled, so
|
|
278
|
+
// dist/manifest.json never existed. #3981 — the package ships compiled JS.
|
|
279
|
+
test("package.json exports[\".\"] is development src, default dist JS, build keeps JS, prepack bundles (#2090, #3981)", () => {
|
|
280
280
|
const pkg = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
|
|
281
|
-
expect(pkg.exports["."].
|
|
282
|
-
expect(pkg.
|
|
281
|
+
expect(pkg.exports["."].development).toBe("./src/index.ts");
|
|
282
|
+
expect(pkg.exports["."].default).toBe("./dist/index.js");
|
|
283
|
+
expect(pkg.exports["./*"].default).toBe("./dist/*.js");
|
|
284
|
+
expect(pkg.scripts.build).not.toContain("-delete");
|
|
283
285
|
expect(pkg.scripts.prepack).toContain("bundle");
|
|
286
|
+
expect(pkg.scripts.prepack).toContain("npm run build");
|
|
284
287
|
});
|
|
285
288
|
|
|
286
289
|
test("tsconfig.build.json uses bundler resolution + the development condition (#749)", () => {
|
|
@@ -305,6 +305,51 @@ describe("onboardCommand", () => {
|
|
|
305
305
|
});
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
+
describe("chant.yml validate step for a lexicon it already has (#2877)", () => {
|
|
309
|
+
let root: string;
|
|
310
|
+
|
|
311
|
+
beforeEach(() => {
|
|
312
|
+
root = makeTempDir();
|
|
313
|
+
writeFixtureRepo(root);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
afterEach(() => {
|
|
317
|
+
rmSync(root, { recursive: true, force: true });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const withGithubStep = (run: string) =>
|
|
321
|
+
ciContent.replace(
|
|
322
|
+
" - name: Generate and validate K8s lexicon",
|
|
323
|
+
` - name: Generate and validate GitHub lexicon\n ${run}\n\n - name: Generate and validate K8s lexicon`,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const githubSteps = (ci: string) => ci.match(/Generate and validate github lexicon/gi)?.length;
|
|
327
|
+
|
|
328
|
+
test("a named step's bare run: line counts as the lexicon's prepack line", () => {
|
|
329
|
+
const before = withGithubStep("run: npm run --prefix lexicons/github prepack");
|
|
330
|
+
expect(before).not.toBe(ciContent);
|
|
331
|
+
writeFileSync(join(root, ".github/workflows/chant.yml"), before);
|
|
332
|
+
|
|
333
|
+
const result = onboardCommand({ name: "github", root });
|
|
334
|
+
expect(result.patched).not.toContain("chant.yml (prepack + validate)");
|
|
335
|
+
expect(result.skipped).toContain("chant.yml: github already in chant.yml");
|
|
336
|
+
expect(readFileSync(join(root, ".github/workflows/chant.yml"), "utf-8")).toBe(before);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("the step-name check ignores case (Github vs GitHub)", () => {
|
|
340
|
+
// A step whose run line is not a plain prepack call, so only the name can
|
|
341
|
+
// tell onboard the validate step already exists.
|
|
342
|
+
writeFileSync(
|
|
343
|
+
join(root, ".github/workflows/chant.yml"),
|
|
344
|
+
withGithubStep("run: npm run --prefix lexicons/github prepack -- --strict"),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
onboardCommand({ name: "github", root });
|
|
348
|
+
const ci = readFileSync(join(root, ".github/workflows/chant.yml"), "utf-8");
|
|
349
|
+
expect(githubSteps(ci)).toBe(1);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
308
353
|
describe("root tsconfig.json paths (#1614)", () => {
|
|
309
354
|
let root: string;
|
|
310
355
|
|
|
@@ -32,13 +32,14 @@ function findRepoRoot(): string {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* A real prepack invocation line: a Dockerfile `RUN`, a workflow `- run
|
|
36
|
-
*
|
|
35
|
+
* A real prepack invocation line: a Dockerfile `RUN`, a workflow `- run:` or
|
|
36
|
+
* `run:` step key (a named step puts `run:` on its own line, #2877), or a bare
|
|
37
|
+
* line inside a `run: |` block. Comments, `echo` messages and
|
|
37
38
|
* `$lex` / `<lex>` placeholders do not match. Matching those placeholder
|
|
38
39
|
* substrings was how onboard appended duplicates to Dockerfile.smoke on
|
|
39
40
|
* every run (#1678).
|
|
40
41
|
*/
|
|
41
|
-
const PREPACK_LINE = /^\s*(?:RUN\s
|
|
42
|
+
const PREPACK_LINE = /^\s*(?:RUN\s+|(?:-\s*)?run:\s*)?npm run --prefix lexicons\/[a-z0-9-]+ prepack\s*$/i;
|
|
42
43
|
|
|
43
44
|
function isPrepackLine(line: string): boolean {
|
|
44
45
|
return PREPACK_LINE.test(line);
|
|
@@ -256,7 +257,10 @@ function patchCiWorkflow(root: string, name: string): PatchResult {
|
|
|
256
257
|
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
257
258
|
const validateStepName = `Generate and validate ${displayName} lexicon`;
|
|
258
259
|
|
|
259
|
-
|
|
260
|
+
// Case-insensitive: the name is built from the directory ("Github"), and an
|
|
261
|
+
// existing step may spell the product its own way ("GitHub", #2877).
|
|
262
|
+
const stepNameLower = validateStepName.toLowerCase();
|
|
263
|
+
if (!lines.some((l) => l.toLowerCase().includes(stepNameLower))) {
|
|
260
264
|
let lastValidateRunIdx = -1;
|
|
261
265
|
for (let i = 0; i < lines.length; i++) {
|
|
262
266
|
if (lines[i].includes("Generate and validate")) {
|
|
@@ -25,6 +25,12 @@ describe("updateCommand", () => {
|
|
|
25
25
|
const result = await updateCommand({ path: testDir });
|
|
26
26
|
expect(result.success).toBe(true);
|
|
27
27
|
expect(result.warnings.some((w) => w.includes("not found"))).toBe(true);
|
|
28
|
+
// chant #3988 — the warning names the install command for the package.
|
|
29
|
+
expect(
|
|
30
|
+
result.warnings.some((w) =>
|
|
31
|
+
w.includes('npm install --save-dev @intentius/chant-lexicon-nonexistent'),
|
|
32
|
+
),
|
|
33
|
+
).toBe(true);
|
|
28
34
|
});
|
|
29
35
|
});
|
|
30
36
|
|
|
@@ -154,7 +154,10 @@ export async function updateCommand(options: UpdateOptions): Promise<UpdateResul
|
|
|
154
154
|
const pkgPath = resolvePackagePath(pkgName, projectDir);
|
|
155
155
|
|
|
156
156
|
if (!pkgPath) {
|
|
157
|
-
|
|
157
|
+
// chant #3988 — update copies from node_modules and never installs.
|
|
158
|
+
warnings.push(
|
|
159
|
+
`${pkgName} not found in node_modules. chant update does not install lexicons: run "npm install --save-dev ${pkgName}" first.`,
|
|
160
|
+
);
|
|
158
161
|
continue;
|
|
159
162
|
}
|
|
160
163
|
|
|
@@ -7,14 +7,19 @@
|
|
|
7
7
|
* Each run is traced through a load hook that NODE_OPTIONS hands to the CLI's
|
|
8
8
|
* node, so the test can say what loaded: none of chant's TypeScript source,
|
|
9
9
|
* ever, and tsx only for a command that imports TypeScript (here the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* project source). tsx's ESM hooks run on a thread of their own, about 200 MB
|
|
11
|
+
* of the process (#3276), so a workspace read must not register it either.
|
|
12
|
+
*
|
|
13
|
+
* The lexicon ships compiled JS (#3981): its exports' `default` is
|
|
14
|
+
* `dist/*.js`. No command loads a module of the lexicon's TypeScript, and
|
|
15
|
+
* `serve mcp` on a project whose config is JSON loads no tsx at all, with the
|
|
16
|
+
* same lint rules and post-synth checks as the lexicon loaded from source.
|
|
13
17
|
*
|
|
14
18
|
* End to end because it packs, installs from the registry and spawns the CLI.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
22
|
+
import { loadPlugin } from "./plugins";
|
|
18
23
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
19
24
|
import { tmpdir } from "node:os";
|
|
20
25
|
import { join, resolve } from "node:path";
|
|
@@ -33,14 +38,20 @@ let waves: string;
|
|
|
33
38
|
let workspace: string;
|
|
34
39
|
let chant: string;
|
|
35
40
|
let tracer: string;
|
|
41
|
+
let mcpProject: string;
|
|
36
42
|
|
|
37
43
|
const TRACER = `
|
|
38
44
|
import { registerHooks } from "node:module";
|
|
39
45
|
import { appendFileSync } from "node:fs";
|
|
40
46
|
const out = process.env.CHANT_TRACE_OUT;
|
|
41
|
-
const seen = { chantSrc: 0, chantDist: 0, tsx: 0, typescript: 0, dist: [] };
|
|
47
|
+
const seen = { chantSrc: 0, chantDist: 0, tsx: 0, typescript: 0, dist: [], lexiconTs: 0, lexiconJs: 0 };
|
|
42
48
|
registerHooks({
|
|
43
49
|
load(url, context, next) {
|
|
50
|
+
if (url.includes("/@intentius/chant-lexicon-")) {
|
|
51
|
+
const path = url.replace(/[?#].*$/, "");
|
|
52
|
+
if (/\\.[cm]?tsx?$/.test(path) && !/\\.d\\.[cm]?ts$/.test(path)) seen.lexiconTs++;
|
|
53
|
+
else if (path.includes("/dist/") && path.endsWith(".js")) seen.lexiconJs++;
|
|
54
|
+
}
|
|
44
55
|
if (url.includes("/@intentius/chant/src/")) seen.chantSrc++;
|
|
45
56
|
else if (url.includes("/@intentius/chant/dist/")) { seen.chantDist++; seen.dist.push(url.slice(url.indexOf("/@intentius/chant/dist/") + 23)); }
|
|
46
57
|
else if (url.includes("/node_modules/tsx/")) seen.tsx++;
|
|
@@ -61,15 +72,15 @@ interface Run {
|
|
|
61
72
|
status: number | null;
|
|
62
73
|
stdout: string;
|
|
63
74
|
stderr: string;
|
|
64
|
-
/** Counts of what loaded, plus each compiled module's path under `dist/`. */
|
|
65
|
-
loaded: { chantSrc: number; chantDist: number; tsx: number; typescript: number; dist: string[] };
|
|
75
|
+
/** Counts of what loaded, plus each compiled module's path under `dist/`. `lexiconTs` and `lexiconJs` count the lexicon's TypeScript and compiled modules. */
|
|
76
|
+
loaded: { chantSrc: number; chantDist: number; tsx: number; typescript: number; dist: string[]; lexiconTs: number; lexiconJs: number };
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
function run(cwd: string, args: string[], extraEnv: NodeJS.ProcessEnv = {}, command = chant): Run {
|
|
79
|
+
function run(cwd: string, args: string[], extraEnv: NodeJS.ProcessEnv = {}, command = chant, input?: string): Run {
|
|
69
80
|
const traceOut = join(scratch, `trace-${Math.random().toString(36).slice(2)}.jsonl`);
|
|
70
81
|
const env: NodeJS.ProcessEnv = { ...process.env, ...extraEnv, NODE_OPTIONS: `--import ${tracer}`, CHANT_TRACE_OUT: traceOut };
|
|
71
82
|
delete env.CHANT_ENV;
|
|
72
|
-
const result = spawnSync(command, args, { cwd, env, encoding: "utf-8" });
|
|
83
|
+
const result = spawnSync(command, args, { cwd, env, encoding: "utf-8", input, timeout: 120_000 });
|
|
73
84
|
const lines = readFileSync(traceOut, "utf-8").trim().split("\n");
|
|
74
85
|
return { status: result.status, stdout: result.stdout, stderr: result.stderr, loaded: JSON.parse(lines[0]!) as Run["loaded"] };
|
|
75
86
|
}
|
|
@@ -80,7 +91,9 @@ beforeAll(() => {
|
|
|
80
91
|
writeFileSync(tracer, TRACER);
|
|
81
92
|
const core = pack(coreRoot);
|
|
82
93
|
// The lexicon's prepack regenerates it from upstream specs; the checkout's
|
|
83
|
-
// generated source and
|
|
94
|
+
// generated source and bundle are what this test needs, so pack them as they
|
|
95
|
+
// are, after the prepack's last step, the build that writes dist/*.js.
|
|
96
|
+
execFileSync("npm", ["run", "build"], { cwd: renderRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
84
97
|
const render = pack(renderRoot, true);
|
|
85
98
|
|
|
86
99
|
project = join(scratch, "project");
|
|
@@ -95,6 +108,13 @@ beforeAll(() => {
|
|
|
95
108
|
);
|
|
96
109
|
chant = join(project, "node_modules", ".bin", "chant");
|
|
97
110
|
|
|
111
|
+
// The same lexicon, from a config that is JSON, with no source: nothing for tsx to load.
|
|
112
|
+
mcpProject = join(scratch, "mcp-project");
|
|
113
|
+
mkdirSync(mcpProject);
|
|
114
|
+
writeFileSync(join(mcpProject, "package.json"), JSON.stringify({ name: "compiled-cli-mcp-e2e", private: true, type: "module" }));
|
|
115
|
+
writeFileSync(join(mcpProject, "chant.config.json"), JSON.stringify({ lexicons: ["render"] }));
|
|
116
|
+
symlinkSync(join(project, "node_modules"), join(mcpProject, "node_modules"), "dir");
|
|
117
|
+
|
|
98
118
|
// The same project with no package.json at all, so nothing above the config
|
|
99
119
|
// says "type": "module" and tsx compiles chant.config.ts as CommonJS (#3897).
|
|
100
120
|
cjsProject = join(scratch, "cjs-project");
|
|
@@ -217,19 +237,64 @@ describe("the installed chant CLI (#3878)", () => {
|
|
|
217
237
|
expect(heavy).toEqual([]);
|
|
218
238
|
});
|
|
219
239
|
|
|
220
|
-
test("build loads the
|
|
240
|
+
test("build loads the project through tsx and the lexicon as compiled JS, against the one compiled core", () => {
|
|
221
241
|
const r = run(project, ["build", "src", "-o", "out/render.json"]);
|
|
222
242
|
expect(r.status, r.stderr).toBe(0);
|
|
223
243
|
const built = JSON.parse(readFileSync(join(project, "out", "render.json"), "utf-8")) as Record<string, { body?: { name?: string } }>;
|
|
224
244
|
expect(built.db?.body?.name).toBe("e2e-db");
|
|
225
245
|
expect(r.loaded.tsx).toBeGreaterThan(0);
|
|
226
246
|
expect(r.loaded.chantSrc).toBe(0);
|
|
247
|
+
expect(r.loaded.lexiconJs).toBeGreaterThan(0);
|
|
248
|
+
expect(r.loaded.lexiconTs).toBe(0);
|
|
227
249
|
});
|
|
228
250
|
|
|
229
|
-
test("lint passes on the project", () => {
|
|
251
|
+
test("lint passes on the project and loads none of the lexicon's TypeScript", () => {
|
|
230
252
|
const r = run(project, ["lint", "src"]);
|
|
231
253
|
expect(r.status, r.stdout + r.stderr).toBe(0);
|
|
232
254
|
expect(r.loaded.chantSrc).toBe(0);
|
|
255
|
+
expect(r.loaded.lexiconTs).toBe(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("serve mcp loads the packed lexicon's plugin with no tsx (#3981)", () => {
|
|
259
|
+
const input =
|
|
260
|
+
[
|
|
261
|
+
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", clientInfo: { name: "e2e" } } },
|
|
262
|
+
{ jsonrpc: "2.0", method: "notifications/initialized" },
|
|
263
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
|
|
264
|
+
{ jsonrpc: "2.0", id: 3, method: "resources/list", params: {} },
|
|
265
|
+
]
|
|
266
|
+
.map((m) => JSON.stringify(m))
|
|
267
|
+
.join("\n") + "\n";
|
|
268
|
+
const r = run(mcpProject, ["serve", "mcp"], {}, chant, input);
|
|
269
|
+
expect(r.status, r.stderr).toBe(0);
|
|
270
|
+
const responses = r.stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line) as { id?: number; result?: Record<string, unknown> });
|
|
271
|
+
const result = (id: number) => responses.find((m) => m.id === id)?.result;
|
|
272
|
+
expect((result(1)?.serverInfo as { name?: string } | undefined)?.name).toBe("chant");
|
|
273
|
+
const tools = ((result(2)?.tools ?? []) as Array<{ name: string }>).map((t) => t.name);
|
|
274
|
+
for (const name of ["build", "lint", "explain"]) expect(tools).toContain(name);
|
|
275
|
+
expect(r.loaded.lexiconJs).toBeGreaterThan(0);
|
|
276
|
+
expect(r.loaded).toMatchObject({ chantSrc: 0, tsx: 0, lexiconTs: 0 });
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("the packed lexicon's plugin has the lint rules and post-synth checks of its source (#3981)", async () => {
|
|
280
|
+
// The plugin as the installed CLI loads it: compiled core, plain node, the lexicon's dist/*.js.
|
|
281
|
+
const script =
|
|
282
|
+
`const core = ${JSON.stringify(pathToFileURL(join(project, "node_modules", "@intentius", "chant", "dist")).href)};\n` +
|
|
283
|
+
`const { installCompiledCli } = await import(core + "/ts-loader.js");\n` +
|
|
284
|
+
`installCompiledCli();\n` +
|
|
285
|
+
`const { loadPlugin } = await import(core + "/cli/plugins.js");\n` +
|
|
286
|
+
`const plugin = await loadPlugin("render");\n` +
|
|
287
|
+
`process.stdout.write(JSON.stringify({ lintRules: plugin.lintRules().map((r) => r.id).sort(), postSynthChecks: (plugin.postSynthChecks?.() ?? []).map((c) => c.id).sort() }));\n`;
|
|
288
|
+
const r = run(mcpProject, ["--input-type=module", "-e", script], {}, process.execPath);
|
|
289
|
+
expect(r.status, r.stderr).toBe(0);
|
|
290
|
+
expect(r.loaded).toMatchObject({ chantSrc: 0, tsx: 0, lexiconTs: 0 });
|
|
291
|
+
const packed = JSON.parse(r.stdout) as { lintRules: string[]; postSynthChecks: string[] };
|
|
292
|
+
|
|
293
|
+
// The same plugin from the repository's source, in this (vitest) process.
|
|
294
|
+
const source = await loadPlugin("render");
|
|
295
|
+
expect(packed.lintRules).toEqual((source.lintRules?.() ?? []).map((rule) => rule.id).sort());
|
|
296
|
+
expect(packed.postSynthChecks).toEqual((source.postSynthChecks?.() ?? []).map((check) => check.id).sort());
|
|
297
|
+
expect(packed.lintRules.length).toBeGreaterThan(0);
|
|
233
298
|
});
|
|
234
299
|
|
|
235
300
|
test("build --sandbox bundles chant's source for the sandboxed child", () => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #4116, end to end through the real CLI: a project whose dependencies
|
|
3
|
+
* are not installed is told so, and a project without node_modules that builds
|
|
4
|
+
* today still builds.
|
|
5
|
+
*
|
|
6
|
+
* Both fixtures are temporary git repositories with no package.json install,
|
|
7
|
+
* run with this checkout's chant from source the way
|
|
8
|
+
* ../workspace/member-commands.e2e.test.ts runs it:
|
|
9
|
+
*
|
|
10
|
+
* - `bare` is a clone of a project like examples/fan-out-estate: its source
|
|
11
|
+
* imports `@intentius/chant-lexicon-k8s` and nothing installs it;
|
|
12
|
+
* - `linked` is member-commands' setup: a node_modules holding only the
|
|
13
|
+
* lexicon, linked to this checkout, and no `@intentius/chant`. chant loads
|
|
14
|
+
* the lexicon, and the lexicon resolves `@intentius/chant` from its own
|
|
15
|
+
* location, so it builds. The first attempt at #4116 (#4118) refused it.
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
18
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
22
|
+
|
|
23
|
+
const REPO = realpathSync(join(import.meta.dirname, "..", "..", "..", ".."));
|
|
24
|
+
const LOADER = join(REPO, "node_modules", "tsx", "dist", "loader.mjs");
|
|
25
|
+
const MAIN = join(REPO, "packages", "core", "src", "cli", "main.ts");
|
|
26
|
+
const TIMEOUT = 240_000;
|
|
27
|
+
|
|
28
|
+
let root: string;
|
|
29
|
+
|
|
30
|
+
function chant(args: string[], cwd: string): { status: number | null; stdout: string; stderr: string } {
|
|
31
|
+
const r = spawnSync(process.execPath, ["--import", `file://${LOADER}`, MAIN, ...args], {
|
|
32
|
+
cwd,
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
timeout: TIMEOUT,
|
|
35
|
+
env: { ...process.env, TSX_DISABLE_CACHE: "1", NO_COLOR: "1" },
|
|
36
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
37
|
+
});
|
|
38
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function project(name: string): string {
|
|
42
|
+
const dir = join(root, name);
|
|
43
|
+
const write = (path: string, text: string) => {
|
|
44
|
+
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
45
|
+
writeFileSync(join(dir, path), text);
|
|
46
|
+
};
|
|
47
|
+
write("chant.config.ts", 'export default { lexicons: ["k8s"] };\n');
|
|
48
|
+
write(
|
|
49
|
+
"src/infra.ts",
|
|
50
|
+
'import { Namespace } from "@intentius/chant-lexicon-k8s";\nexport const appNs = new Namespace({ metadata: { name: "app" } });\n',
|
|
51
|
+
);
|
|
52
|
+
write(".gitignore", "node_modules\nout\n");
|
|
53
|
+
const git = (...args: string[]) =>
|
|
54
|
+
execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", ...args], { cwd: dir, stdio: "ignore" });
|
|
55
|
+
git("init", "-q");
|
|
56
|
+
git("add", "-A");
|
|
57
|
+
git("commit", "-qm", "fixture");
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let bare: string;
|
|
62
|
+
let linked: string;
|
|
63
|
+
|
|
64
|
+
beforeAll(() => {
|
|
65
|
+
root = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-e2e-")));
|
|
66
|
+
bare = project("bare");
|
|
67
|
+
linked = project("linked");
|
|
68
|
+
mkdirSync(join(linked, "node_modules", "@intentius"), { recursive: true });
|
|
69
|
+
symlinkSync(join(REPO, "lexicons", "k8s"), join(linked, "node_modules", "@intentius", "chant-lexicon-k8s"), "dir");
|
|
70
|
+
}, TIMEOUT);
|
|
71
|
+
|
|
72
|
+
afterAll(() => {
|
|
73
|
+
if (root) rmSync(root, { recursive: true, force: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("dependencies not installed (#4116)", () => {
|
|
77
|
+
test("graph says the dependencies are not installed, not that the source has lint errors", () => {
|
|
78
|
+
const r = chant(["graph", "--format", "ir"], bare);
|
|
79
|
+
expect(r.status, r.stderr).toBe(1);
|
|
80
|
+
expect(r.stderr).toContain(`dependencies not installed: @intentius/chant-lexicon-k8s does not resolve from ${bare}`);
|
|
81
|
+
expect(r.stderr).toContain("run npm install (or the project's package manager)");
|
|
82
|
+
expect(r.stderr).not.toMatch(/lint errors/i);
|
|
83
|
+
expect(r.stdout).toBe("");
|
|
84
|
+
}, TIMEOUT);
|
|
85
|
+
|
|
86
|
+
test("build and lint lead with the dependencies message and exit 1", () => {
|
|
87
|
+
for (const verb of ["build", "lint"]) {
|
|
88
|
+
const r = chant([verb], bare);
|
|
89
|
+
expect(r.status, `${verb}: ${r.stderr}`).toBe(1);
|
|
90
|
+
expect(r.stderr, verb).toContain("dependencies not installed: @intentius/chant-lexicon-k8s does not resolve from");
|
|
91
|
+
}
|
|
92
|
+
}, TIMEOUT);
|
|
93
|
+
|
|
94
|
+
test("a project without @intentius/chant in node_modules that builds today still builds", () => {
|
|
95
|
+
for (const args of [["build"], ["lint"], ["graph", "--format", "ir"]]) {
|
|
96
|
+
const r = chant(args, linked);
|
|
97
|
+
expect(r.status, `${args.join(" ")}: ${r.stderr}`).toBe(0);
|
|
98
|
+
expect(r.stderr, args.join(" ")).not.toContain("dependencies not installed");
|
|
99
|
+
}
|
|
100
|
+
}, TIMEOUT);
|
|
101
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #4116: which failure messages are imports of packages the project has
|
|
3
|
+
* not installed. The command paths that print the message have their own tests
|
|
4
|
+
* (handlers/graph.test.ts, handlers/lint.test.ts, dependencies.e2e.test.ts).
|
|
5
|
+
*/
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
7
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { reportUninstalledDependencies, uninstalledDependencyIn } from "./dependencies";
|
|
11
|
+
|
|
12
|
+
let root: string;
|
|
13
|
+
let project: string;
|
|
14
|
+
|
|
15
|
+
const notFound = (pkg: string, file = "src/a.ts") => `Cannot find package '${pkg}' imported from ${join(project, file)}`;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
root = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-")));
|
|
19
|
+
project = join(root, "proj");
|
|
20
|
+
mkdirSync(join(project, "src"), { recursive: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
rmSync(root, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("uninstalledDependencyIn", () => {
|
|
29
|
+
test("an @intentius package no node_modules holds, as lint and build report it", () => {
|
|
30
|
+
expect(uninstalledDependencyIn(notFound("@intentius/chant-lexicon-aws"), project)).toEqual({
|
|
31
|
+
package: "@intentius/chant-lexicon-aws",
|
|
32
|
+
dir: project,
|
|
33
|
+
});
|
|
34
|
+
expect(uninstalledDependencyIn(`Component discovery error: ${notFound("@intentius/chant", "a.component.ts")}`, project)).toEqual({
|
|
35
|
+
package: "@intentius/chant",
|
|
36
|
+
dir: project,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("a subpath import names its package", () => {
|
|
41
|
+
expect(uninstalledDependencyIn("Cannot find module '@intentius/chant/params' imported from /x/a.ts", project)?.package).toBe("@intentius/chant");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a package found in a parent node_modules is installed", () => {
|
|
45
|
+
mkdirSync(join(root, "node_modules", "@intentius", "chant-lexicon-aws"), { recursive: true });
|
|
46
|
+
expect(uninstalledDependencyIn(notFound("@intentius/chant-lexicon-aws"), project)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a package found from the importing file's directory is installed", () => {
|
|
50
|
+
const lexicon = join(root, "elsewhere", "lexicon");
|
|
51
|
+
mkdirSync(join(root, "elsewhere", "node_modules", "@intentius", "chant"), { recursive: true });
|
|
52
|
+
mkdirSync(lexicon, { recursive: true });
|
|
53
|
+
expect(uninstalledDependencyIn(`Cannot find package '@intentius/chant' imported from ${join(lexicon, "index.ts")}`, project)).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("another package counts only when the project's package.json declares it", () => {
|
|
57
|
+
expect(uninstalledDependencyIn(notFound("lodahs"), project)).toBeUndefined();
|
|
58
|
+
writeFileSync(join(project, "package.json"), JSON.stringify({ dependencies: { yaml: "*" } }));
|
|
59
|
+
expect(uninstalledDependencyIn(notFound("yaml"), project)).toEqual({ package: "yaml", dir: project });
|
|
60
|
+
expect(uninstalledDependencyIn(notFound("lodahs"), project)).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("relative imports and other messages are not dependencies", () => {
|
|
64
|
+
expect(uninstalledDependencyIn("Cannot find module './missing' imported from /x/a.ts", project)).toBeUndefined();
|
|
65
|
+
expect(uninstalledDependencyIn("Exported declarable 'x' is never referenced", project)).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("reportUninstalledDependencies", () => {
|
|
70
|
+
let stderr: string[];
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
stderr = [];
|
|
73
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void stderr.push(s));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("names the first uninstalled package with the install hint; all when every message is one", () => {
|
|
77
|
+
expect(reportUninstalledDependencies([notFound("@intentius/chant-lexicon-aws"), notFound("@intentius/chant")], project)).toBe("all");
|
|
78
|
+
expect(stderr).toHaveLength(1);
|
|
79
|
+
expect(stderr[0]).toContain(`dependencies not installed: @intentius/chant-lexicon-aws does not resolve from ${project}`);
|
|
80
|
+
expect(stderr[0]).toContain("run npm install (or the project's package manager)");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("some when other errors are beside them", () => {
|
|
84
|
+
expect(reportUninstalledDependencies([notFound("@intentius/chant"), "a real problem"], project)).toBe("some");
|
|
85
|
+
expect(stderr).toHaveLength(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("prints nothing when no message is one", () => {
|
|
89
|
+
expect(reportUninstalledDependencies(["a real problem"], project)).toBeUndefined();
|
|
90
|
+
expect(reportUninstalledDependencies([], project)).toBeUndefined();
|
|
91
|
+
expect(stderr).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #4116: a failure caused by a project whose dependencies are not installed.
|
|
3
|
+
*
|
|
4
|
+
* In a clone with no node_modules, every project file that imports
|
|
5
|
+
* `@intentius/chant` or a lexicon package fails to load. Lint reports each one
|
|
6
|
+
* (LEX002, COMP000), the graph's lint gate then says the source has lint
|
|
7
|
+
* errors, and build prints node's own "Cannot find package" per file. The
|
|
8
|
+
* source is fine; the install is missing.
|
|
9
|
+
*
|
|
10
|
+
* This runs only after something has already failed, on that failure's
|
|
11
|
+
* messages. A project chant builds today never reaches it, whatever its
|
|
12
|
+
* node_modules looks like: chant resolves a lexicon from its own install first,
|
|
13
|
+
* and a project that imports only what it has installed loads fine. That is why
|
|
14
|
+
* the check is not made up front (#4118, reverted in #4125).
|
|
15
|
+
*
|
|
16
|
+
* A message counts when it is node's "Cannot find package|module '<spec>'",
|
|
17
|
+
* `<spec>` names a package that is `@intentius/*` or declared in the project's
|
|
18
|
+
* package.json, and no node_modules from the project directory up holds it. A
|
|
19
|
+
* typo in an import of an undeclared package keeps its message as it is.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { packageNameOf } from "../lexicon-module";
|
|
25
|
+
import { formatError } from "./format";
|
|
26
|
+
|
|
27
|
+
/** A package that a failure could not import and that is not installed for the project. */
|
|
28
|
+
export interface UninstalledDependency {
|
|
29
|
+
package: string;
|
|
30
|
+
/** The project directory it does not resolve from. */
|
|
31
|
+
dir: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const NOT_FOUND = /Cannot find (?:package|module) '([^']+)'(?: imported from (\S+))?/g;
|
|
35
|
+
|
|
36
|
+
/** True when a node_modules at `dir` or above holds `pkg`. */
|
|
37
|
+
function installedFrom(pkg: string, dir: string): boolean {
|
|
38
|
+
for (let d = resolve(dir); ; d = dirname(d)) {
|
|
39
|
+
if (existsSync(join(d, "node_modules", ...pkg.split("/")))) return true;
|
|
40
|
+
if (dirname(d) === d) return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The packages the nearest package.json at `dir` or above declares, in any dependency field. */
|
|
45
|
+
function declaredPackages(dir: string): Set<string> {
|
|
46
|
+
for (let d = resolve(dir); ; d = dirname(d)) {
|
|
47
|
+
const file = join(d, "package.json");
|
|
48
|
+
if (existsSync(file)) {
|
|
49
|
+
try {
|
|
50
|
+
const json = JSON.parse(readFileSync(file, "utf-8")) as Record<string, unknown>;
|
|
51
|
+
const names = new Set<string>();
|
|
52
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
53
|
+
const deps = json[field];
|
|
54
|
+
if (deps && typeof deps === "object") for (const name of Object.keys(deps)) names.add(name);
|
|
55
|
+
}
|
|
56
|
+
return names;
|
|
57
|
+
} catch {
|
|
58
|
+
return new Set();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (dirname(d) === d) return new Set();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function importerDir(importer: string | undefined): string | undefined {
|
|
66
|
+
if (!importer) return undefined;
|
|
67
|
+
try {
|
|
68
|
+
const path = importer.startsWith("file:") ? fileURLToPath(importer) : importer;
|
|
69
|
+
return isAbsolute(path) ? dirname(path) : undefined;
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The uninstalled package `message` reports, or `undefined` when it reports none. */
|
|
76
|
+
export function uninstalledDependencyIn(message: string, projectDir: string): UninstalledDependency | undefined {
|
|
77
|
+
const dir = resolve(projectDir);
|
|
78
|
+
let declared: Set<string> | undefined;
|
|
79
|
+
for (const match of message.matchAll(NOT_FOUND)) {
|
|
80
|
+
const pkg = packageNameOf(match[1]);
|
|
81
|
+
if (pkg === undefined) continue;
|
|
82
|
+
if (!pkg.startsWith("@intentius/")) {
|
|
83
|
+
declared ??= declaredPackages(dir);
|
|
84
|
+
if (!declared.has(pkg)) continue;
|
|
85
|
+
}
|
|
86
|
+
if (installedFrom(pkg, dir)) continue;
|
|
87
|
+
const from = importerDir(match[2]);
|
|
88
|
+
if (from !== undefined && installedFrom(pkg, from)) continue;
|
|
89
|
+
return { package: pkg, dir };
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The dependencies message, formatted as an error with the install hint. */
|
|
95
|
+
export function formatDependenciesNotInstalled(dep: UninstalledDependency): string {
|
|
96
|
+
return formatError({
|
|
97
|
+
message: `dependencies not installed: ${dep.package} does not resolve from ${dep.dir}`,
|
|
98
|
+
hint: "run npm install (or the project's package manager)",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Print the dependencies message, naming the first uninstalled package, when
|
|
104
|
+
* any of a failure's `messages` reports one. Prints nothing and returns
|
|
105
|
+
* `undefined` when none does. Returns `"all"` when every message reports one,
|
|
106
|
+
* so a caller can drop its own wording when that wording blames the source.
|
|
107
|
+
*/
|
|
108
|
+
export function reportUninstalledDependencies(
|
|
109
|
+
messages: readonly string[],
|
|
110
|
+
projectDir: string,
|
|
111
|
+
): "all" | "some" | undefined {
|
|
112
|
+
let first: UninstalledDependency | undefined;
|
|
113
|
+
let all = messages.length > 0;
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
const dep = uninstalledDependencyIn(message, projectDir);
|
|
116
|
+
if (dep) first ??= dep;
|
|
117
|
+
else all = false;
|
|
118
|
+
}
|
|
119
|
+
if (!first) return undefined;
|
|
120
|
+
console.error(formatDependenciesNotInstalled(first));
|
|
121
|
+
return all ? "all" : "some";
|
|
122
|
+
}
|