@intentius/chant 0.124.31 → 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 +44 -16
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +15 -0
- package/dist/cli/registry.d.ts +13 -4
- 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/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/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 +10 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +39 -0
- 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 +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- 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 +12 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.js +6 -4
- package/dist/workspace/ci-run-index.schema.json +5 -3
- package/dist/workspace/ci-run.d.ts +75 -2
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +195 -53
- package/dist/workspace/ci-runner-key.d.ts +16 -3
- package/dist/workspace/ci-runner-key.d.ts.map +1 -1
- package/dist/workspace/ci-runner-key.js +30 -10
- package/dist/workspace/ci-serve.d.ts +54 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +175 -26
- 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 +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +2 -2
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- 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 +18 -0
- package/src/cli/main.ts +43 -16
- package/src/cli/parse-args.ts +12 -0
- package/src/cli/registry.ts +19 -6
- 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/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/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 +42 -0
- package/src/pr-forge.ts +40 -0
- 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 +5 -5
- 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 +5 -3
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +18 -4
- package/src/workspace/ci-run.test.ts +185 -7
- package/src/workspace/ci-run.ts +224 -54
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-serve.test.ts +150 -6
- package/src/workspace/ci-serve.ts +169 -25
- 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.ts +2 -2
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
|
@@ -148,6 +148,75 @@ describe("runBuild --components --generate (chant #1108 build-time parameters)",
|
|
|
148
148
|
vi.restoreAllMocks();
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
test("--pr-loop without --report-url bakes the workspace's ci.reportUrl into the jobs, and the flag wins (#4160)", async () => {
|
|
152
|
+
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
|
|
153
|
+
const { tmpdir } = await import("node:os");
|
|
154
|
+
const { join } = await import("node:path");
|
|
155
|
+
const { execFileSync } = await import("node:child_process");
|
|
156
|
+
const root = mkdtempSync(join(tmpdir(), "chant-build-report-url-"));
|
|
157
|
+
try {
|
|
158
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
159
|
+
mkdirSync(join(root, "app"));
|
|
160
|
+
writeFileSync(
|
|
161
|
+
join(root, "chant.workspace.json"),
|
|
162
|
+
JSON.stringify({ name: "acme", schema: 1, ci: { reportUrl: "s3://acme-reports/prod/" }, members: [{ name: "app", dir: "app", kind: "chant", because: "the app" }] }),
|
|
163
|
+
);
|
|
164
|
+
generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
|
|
165
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
166
|
+
const path = join(root, "app");
|
|
167
|
+
const output = join(root, "out.yml");
|
|
168
|
+
|
|
169
|
+
expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
|
|
170
|
+
expect(generateComponentsPipelineMock.mock.calls[0][2].prLoop).toEqual({ reportUrl: "s3://acme-reports/prod" });
|
|
171
|
+
|
|
172
|
+
expect(await runBuild({ args: makeArgs({ path, output, generate: "github", prLoop: true, reportUrl: "file:///srv/reports" }), plugins: [], serializers: [] })).toBe(0);
|
|
173
|
+
expect(generateComponentsPipelineMock.mock.calls[1][2].prLoop).toEqual({ reportUrl: "file:///srv/reports" });
|
|
174
|
+
|
|
175
|
+
// Without --pr-loop the declaration's URL is not read and nothing changes.
|
|
176
|
+
expect(await runBuild({ args: makeArgs({ path, output, generate: "github" }), plugins: [], serializers: [] })).toBe(0);
|
|
177
|
+
expect(generateComponentsPipelineMock.mock.calls[2][2]).not.toHaveProperty("prLoop");
|
|
178
|
+
} finally {
|
|
179
|
+
rmSync(root, { recursive: true, force: true });
|
|
180
|
+
vi.restoreAllMocks();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("--pr-loop outside any workspace has no report URL unless the flag gives one (#4160)", async () => {
|
|
185
|
+
const { mkdtempSync, rmSync } = await import("node:fs");
|
|
186
|
+
const { tmpdir } = await import("node:os");
|
|
187
|
+
const { join } = await import("node:path");
|
|
188
|
+
const plain = mkdtempSync(join(tmpdir(), "chant-build-plain-"));
|
|
189
|
+
try {
|
|
190
|
+
generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
|
|
191
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
192
|
+
expect(await runBuild({ args: makeArgs({ path: plain, generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(0);
|
|
193
|
+
expect(generateComponentsPipelineMock).toHaveBeenCalledWith(plain, "github", { env: undefined, prLoop: {} }, undefined, []);
|
|
194
|
+
} finally {
|
|
195
|
+
rmSync(plain, { recursive: true, force: true });
|
|
196
|
+
vi.restoreAllMocks();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("an invalid ci.reportUrl stops --pr-loop with its reason code, and generates nothing (#4160)", async () => {
|
|
201
|
+
const { mkdtempSync, mkdirSync, writeFileSync, rmSync } = await import("node:fs");
|
|
202
|
+
const { tmpdir } = await import("node:os");
|
|
203
|
+
const { join } = await import("node:path");
|
|
204
|
+
const { execFileSync } = await import("node:child_process");
|
|
205
|
+
const root = mkdtempSync(join(tmpdir(), "chant-build-report-url-bad-"));
|
|
206
|
+
try {
|
|
207
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
208
|
+
mkdirSync(join(root, "app"));
|
|
209
|
+
writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, ci: { reportUrl: "https://reports.example" }, members: [{ name: "app", dir: "app", kind: "chant", because: "the app" }] }));
|
|
210
|
+
const stderr = makeStderrSpy();
|
|
211
|
+
expect(await runBuild({ args: makeArgs({ path: join(root, "app"), generate: "github", prLoop: true }), plugins: [], serializers: [] })).toBe(1);
|
|
212
|
+
expect(generateComponentsPipelineMock).not.toHaveBeenCalled();
|
|
213
|
+
expect(stderr.join("\n")).toMatch(/ci\.reportUrl https:\/\/reports\.example: .*\[declaration-invalid\]/);
|
|
214
|
+
} finally {
|
|
215
|
+
rmSync(root, { recursive: true, force: true });
|
|
216
|
+
vi.restoreAllMocks();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
151
220
|
test("--pr-build and --pr-build-command become the prLoop build options (#4088)", async () => {
|
|
152
221
|
generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "jobs: {}", stages: [], jobs: [] });
|
|
153
222
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
@@ -2,6 +2,7 @@ import { writeFileSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { buildCommand, buildCommandWatch, printErrors, printWarnings, resolveBuildFormat } from "../commands/build";
|
|
4
4
|
import { formatError, formatInfo, formatSuccess, formatBold } from "../format";
|
|
5
|
+
import { reportUninstalledDependencies } from "../dependencies";
|
|
5
6
|
import type { CommandContext } from "../registry";
|
|
6
7
|
import { generateComponentsPipeline } from "../../components/cli-support";
|
|
7
8
|
import { loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
@@ -15,13 +16,14 @@ import type { PrLoopPipelineOptions } from "../../components/pr-pipeline";
|
|
|
15
16
|
* and the branch, and with `--events-json` and `--report-url` (#4007) the
|
|
16
17
|
* flags the generated jobs pass to pr-plan and pr-apply. `--pr-build` and
|
|
17
18
|
* `--pr-build-command` (#4088) add the build and lint steps before both.
|
|
19
|
+
* `reportUrl` is `--report-url`, else the workspace's `ci.reportUrl` (#4160).
|
|
18
20
|
*/
|
|
19
|
-
function prLoopOption(args: CommandContext["args"]): PrLoopPipelineOptions {
|
|
21
|
+
function prLoopOption(args: CommandContext["args"], reportUrl: string | undefined): PrLoopPipelineOptions {
|
|
20
22
|
return {
|
|
21
23
|
...(args.gate ? { gate: args.gate } : {}),
|
|
22
24
|
...(args.branch ? { branch: args.branch } : {}),
|
|
23
25
|
...(args.eventsJson ? { events: true } : {}),
|
|
24
|
-
...(
|
|
26
|
+
...(reportUrl ? { reportUrl } : {}),
|
|
25
27
|
...(args.prBuild ? { build: true } : {}),
|
|
26
28
|
...(args.prBuildCommand ? { buildCommand: args.prBuildCommand } : {}),
|
|
27
29
|
};
|
|
@@ -90,6 +92,16 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
90
92
|
const found = findWorkspaceRoot(resolve(args.path));
|
|
91
93
|
let plan: import("../../workspace/member-pipeline").MemberComponentPlan | undefined;
|
|
92
94
|
let memberPipeline: typeof import("../../workspace/member-pipeline") | undefined;
|
|
95
|
+
// #4160: without --report-url, the declaration's ci.reportUrl. Read only inside a workspace.
|
|
96
|
+
let reportUrl = args.reportUrl;
|
|
97
|
+
if (found && args.prLoop && !reportUrl) {
|
|
98
|
+
try {
|
|
99
|
+
reportUrl = await (await import("../../workspace/ci-report-url")).declaredReportUrl(resolve(args.path));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
93
105
|
if (found) {
|
|
94
106
|
memberPipeline = await import("../../workspace/member-pipeline");
|
|
95
107
|
try {
|
|
@@ -104,7 +116,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
104
116
|
params: args.param,
|
|
105
117
|
paramsFile: args.paramsFile,
|
|
106
118
|
...(args.waveGate ? { waveGate: { gate: args.waveGate, ...(args.canary?.length ? { canary: args.canary } : {}), ...(args.base ? { base: args.base } : {}) } } : {}),
|
|
107
|
-
...(args.prLoop ? { prLoop: prLoopOption(args) } : {}),
|
|
119
|
+
...(args.prLoop ? { prLoop: prLoopOption(args, reportUrl) } : {}),
|
|
108
120
|
...(args.image ? { image: args.image } : {}),
|
|
109
121
|
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
110
122
|
});
|
|
@@ -134,7 +146,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
134
146
|
? { gatedWaves: { gate: args.waveGate, ...(args.canary?.length ? { canary: args.canary } : {}), ...(args.base ? { base: args.base } : {}) } }
|
|
135
147
|
: {}),
|
|
136
148
|
// #3183: a plan job per pull request and an apply job per merge.
|
|
137
|
-
...(args.prLoop ? { prLoop: prLoopOption(args) } : {}),
|
|
149
|
+
...(args.prLoop ? { prLoop: prLoopOption(args, reportUrl) } : {}),
|
|
138
150
|
// #4002: the job image and the lines that install what it lacks, chant itself included.
|
|
139
151
|
...(args.image ? { image: args.image } : {}),
|
|
140
152
|
...(args.beforeScript?.length ? { beforeScript: args.beforeScript } : {}),
|
|
@@ -284,6 +296,8 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
284
296
|
const own = result.warnings.filter((w) => !w.includes("No serializer found for lexicon") && !seenWarnings.has(w));
|
|
285
297
|
for (const w of own) seenWarnings.add(w);
|
|
286
298
|
printWarnings(own);
|
|
299
|
+
// #4116: once, before the first failing group's errors.
|
|
300
|
+
if (!result.success && !failed) reportUninstalledDependencies(result.errors, resolve(args.path));
|
|
287
301
|
printErrors(result.errors);
|
|
288
302
|
if (!result.success) failed = true;
|
|
289
303
|
}
|
|
@@ -333,6 +347,8 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
333
347
|
warnings = warnings.filter((w) => !w.includes('No serializer found for lexicon'));
|
|
334
348
|
}
|
|
335
349
|
printWarnings(warnings);
|
|
350
|
+
// #4116: errors that are imports of uninstalled packages are led by the dependencies message.
|
|
351
|
+
if (!result.success) reportUninstalledDependencies(result.errors, resolve(args.path));
|
|
336
352
|
printErrors(result.errors);
|
|
337
353
|
|
|
338
354
|
return result.success ? 0 : 1;
|
|
@@ -78,6 +78,18 @@ const ESTATE = [
|
|
|
78
78
|
|
|
79
79
|
let ran: string[] = [];
|
|
80
80
|
let failing: string[] = [];
|
|
81
|
+
/** The error a component's step throws, in place of applying (#3997). */
|
|
82
|
+
let refusal: Record<string, Error> = {};
|
|
83
|
+
|
|
84
|
+
/** A step's refusal of a plan that moved since it was approved, marked `planMoved` as cfn-deploy marks it. */
|
|
85
|
+
function planMovedError(component: string): Error {
|
|
86
|
+
return Object.assign(new Error(`${component}: refusing to apply, the plan moved`), { planMoved: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A step error carrying an `exitCode`, as kubectl-apply's carried one before #3997's follow-up. */
|
|
90
|
+
function exitCodeError(component: string, exitCode: number): Error {
|
|
91
|
+
return Object.assign(new Error(`${component}: refused`), { exitCode });
|
|
92
|
+
}
|
|
81
93
|
/** What each component's step was handed, after wiring resolved. */
|
|
82
94
|
let inputs: Record<string, Record<string, unknown>> = {};
|
|
83
95
|
/** Stack outputs a component's step returns, as cfn-deploy's `outputs` does. */
|
|
@@ -118,6 +130,7 @@ beforeEach(() => {
|
|
|
118
130
|
vi.clearAllMocks();
|
|
119
131
|
ran = [];
|
|
120
132
|
failing = [];
|
|
133
|
+
refusal = {};
|
|
121
134
|
inputs = {};
|
|
122
135
|
stepOutputs = {};
|
|
123
136
|
onStart = () => {};
|
|
@@ -155,6 +168,7 @@ beforeEach(() => {
|
|
|
155
168
|
onStart(runCtx.component);
|
|
156
169
|
ran.push(runCtx.component);
|
|
157
170
|
inputs[runCtx.component] = input;
|
|
171
|
+
if (refusal[runCtx.component]) throw refusal[runCtx.component];
|
|
158
172
|
if (failing.includes(runCtx.component)) throw new Error(`${runCtx.component} failed`);
|
|
159
173
|
return { ok: true, ...(stepOutputs[runCtx.component] ? { outputs: stepOutputs[runCtx.component] } : {}) };
|
|
160
174
|
},
|
|
@@ -310,6 +324,31 @@ describe("proof: a mid-run failure blocks its subtree and leaves the independent
|
|
|
310
324
|
});
|
|
311
325
|
});
|
|
312
326
|
|
|
327
|
+
describe("a step that refuses its plan because the plan moved exits 3 (#3997)", () => {
|
|
328
|
+
test("marked planMoved: exit 3, and its subtree is blocked as for any failure", async () => {
|
|
329
|
+
refusal = { "cluster-a": planMovedError("cluster-a") };
|
|
330
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
|
|
331
|
+
expect(ran).not.toContain("app-one");
|
|
332
|
+
expect(ran).toContain("app-three");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("an exitCode of 3 on the step's error is the same mark", async () => {
|
|
336
|
+
refusal = { "cluster-a": exitCodeError("cluster-a", 3) };
|
|
337
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("any other exitCode is ignored, and the failure exits 1", async () => {
|
|
341
|
+
refusal = { "cluster-a": exitCodeError("cluster-a", 7) };
|
|
342
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(1);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("one moved plan beside another failure still exits 3", async () => {
|
|
346
|
+
refusal = { "cluster-a": planMovedError("cluster-a") };
|
|
347
|
+
failing = ["cluster-b"];
|
|
348
|
+
expect(await runComponentsFanOut(ctx({ base: "main", env: "test" }))).toBe(3);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
313
352
|
describe("proof: the re-run prints already-applied for what it skips, and finishes", () => {
|
|
314
353
|
test("repeating the identical command finishes the fan-out, and the approval is not re-asked", async () => {
|
|
315
354
|
const dir = await mkdtemp(join(tmpdir(), "fan-out-"));
|
|
@@ -523,6 +562,7 @@ describe("gated waves (#3049): one gate per wave, bound to the wave's set digest
|
|
|
523
562
|
},
|
|
524
563
|
async run(runCtx: { component: string }) {
|
|
525
564
|
ran.push(runCtx.component);
|
|
565
|
+
if (refusal[runCtx.component]) throw refusal[runCtx.component];
|
|
526
566
|
return { ok: true };
|
|
527
567
|
},
|
|
528
568
|
} as never);
|
|
@@ -530,6 +570,39 @@ describe("gated waves (#3049): one gate per wave, bound to the wave's set digest
|
|
|
530
570
|
});
|
|
531
571
|
});
|
|
532
572
|
|
|
573
|
+
/** Run the gated fan-out once to record wave 1's pending fact, then approve that digest. */
|
|
574
|
+
async function approveWave1(run: () => Promise<number>): Promise<void> {
|
|
575
|
+
await run();
|
|
576
|
+
const first = gateLedger.appended[0];
|
|
577
|
+
gateLedger = memoryGateLedgerPort({
|
|
578
|
+
pending: [first],
|
|
579
|
+
resolutions: [
|
|
580
|
+
{
|
|
581
|
+
version: 1,
|
|
582
|
+
kind: "resolution",
|
|
583
|
+
op: "fan-out",
|
|
584
|
+
gate: "release-wave-1",
|
|
585
|
+
resolvedBy: "operator",
|
|
586
|
+
timestamp: new Date(Date.parse(first.timestamp) + 1000).toISOString(),
|
|
587
|
+
planDigest: first.planDigest,
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
test.each([
|
|
594
|
+
["marked planMoved", 3, () => planMovedError("net")],
|
|
595
|
+
["with exitCode 3", 3, () => exitCodeError("net", 3)],
|
|
596
|
+
["with exitCode 7", 1, () => exitCodeError("net", 7)],
|
|
597
|
+
["unmarked", 1, () => new Error("net failed")],
|
|
598
|
+
])("an approved wave whose step fails with an error %s exits %i (#3997)", async (_label, code, error) => {
|
|
599
|
+
const run = () => runComponentsFanOut(ctx({ base: "main", env: "test", waveGate: "release" }));
|
|
600
|
+
await approveWave1(run);
|
|
601
|
+
refusal = { net: error() };
|
|
602
|
+
expect(await run()).toBe(code);
|
|
603
|
+
expect(ran).toEqual(["net"]);
|
|
604
|
+
});
|
|
605
|
+
|
|
533
606
|
test("wave 1 stops at its own gate, and the record names its digest", async () => {
|
|
534
607
|
const dir = await mkdtemp(join(tmpdir(), "fan-out-waves-"));
|
|
535
608
|
try {
|
|
@@ -68,9 +68,10 @@ import { approveCommand, gateIsSealed } from "../../op/gate";
|
|
|
68
68
|
import { getHeadCommit } from "../../lifecycle/git";
|
|
69
69
|
import { remainingFanOut, type ChangedUnits, type FanOutProgress } from "../../components/fan-out";
|
|
70
70
|
import { readDeployedOutputs } from "../../components/wave-plan";
|
|
71
|
-
import { deployContext } from "../../components/driver";
|
|
71
|
+
import { deployContext, failedRunExitCode } from "../../components/driver";
|
|
72
72
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
73
73
|
import { formatError, formatInfo, formatWarning } from "../format";
|
|
74
|
+
import { reportUninstalledDependencies } from "../dependencies";
|
|
74
75
|
import { FAN_OUT_GATE_OP } from "../../op/gate-name";
|
|
75
76
|
import { resolveBuildRoot } from "./lifecycle";
|
|
76
77
|
import { GATED_EXIT_CODE, autoReleaseRun, recordAutoReleasesForRun } from "./run";
|
|
@@ -237,10 +238,14 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
237
238
|
} catch (err) {
|
|
238
239
|
// A cycle or an unknown `dependsOn` refuses here, over the whole graph and
|
|
239
240
|
// before anything is selected (#2417). The message names the members.
|
|
240
|
-
|
|
241
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
242
|
+
reportUninstalledDependencies([message], projectPath); // #4116
|
|
243
|
+
console.error(formatError({ message }));
|
|
241
244
|
return 1;
|
|
242
245
|
}
|
|
243
246
|
if (!derived.success) {
|
|
247
|
+
// #4116: component files that import packages the project has not installed.
|
|
248
|
+
if (derived.error) reportUninstalledDependencies([derived.error], projectPath);
|
|
244
249
|
console.error(formatError({ message: derived.error ?? "Could not discover components" }));
|
|
245
250
|
return 1;
|
|
246
251
|
}
|
|
@@ -522,8 +527,10 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
522
527
|
...(await summaryLedgerPrefix()),
|
|
523
528
|
...(pending.environment ? { environment: pending.environment } : {}),
|
|
524
529
|
});
|
|
525
|
-
return result.status === "gated" ? GATED_EXIT_CODE :
|
|
530
|
+
return result.status === "gated" ? GATED_EXIT_CODE : failedRunExitCode(result.results);
|
|
526
531
|
}
|
|
527
532
|
|
|
528
|
-
|
|
533
|
+
// A step that refused the wave's plan because its target moved since the
|
|
534
|
+
// approval exits 3, the code of a wave whose plan changed (#3997).
|
|
535
|
+
return result.status === "ok" ? 0 : failedRunExitCode(result.results);
|
|
529
536
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
2
5
|
import type { ParsedArgs } from "../registry";
|
|
3
6
|
import { DECLARABLE_MARKER, type Declarable } from "../../declarable";
|
|
4
7
|
import { AttrRef } from "../../attrref";
|
|
@@ -468,6 +471,79 @@ describe("runGraph", () => {
|
|
|
468
471
|
expect(discoverMock).not.toHaveBeenCalled();
|
|
469
472
|
});
|
|
470
473
|
|
|
474
|
+
// #4116: a clone of examples/fan-out-estate with no node_modules. Every
|
|
475
|
+
// source file fails to import its lexicon, and lint reports each import.
|
|
476
|
+
describe("lint gate: dependencies not installed (#4116)", () => {
|
|
477
|
+
let project: string;
|
|
478
|
+
beforeEach(() => {
|
|
479
|
+
project = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-graph-")));
|
|
480
|
+
});
|
|
481
|
+
afterEach(() => {
|
|
482
|
+
rmSync(project, { recursive: true, force: true });
|
|
483
|
+
});
|
|
484
|
+
const missing = (pkg: string, file: string) => ({
|
|
485
|
+
file: join(project, file), line: 1, column: 1, ruleId: "LEX002", severity: "error",
|
|
486
|
+
message: `Cannot find package '${pkg}' imported from ${join(project, file)}`,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("every lint error an uninstalled import: the dependencies message, not lint errors", async () => {
|
|
490
|
+
lintMock.mockResolvedValue({
|
|
491
|
+
success: false,
|
|
492
|
+
diagnostics: [
|
|
493
|
+
missing("@intentius/chant-lexicon-aws", "src/network/outputs.ts"),
|
|
494
|
+
{ ...missing("@intentius/chant", "network.component.ts"), ruleId: "COMP000", message: `Component discovery error: ${missing("@intentius/chant", "network.component.ts").message}` },
|
|
495
|
+
{ ...missing("@intentius/chant", "x.ts"), ruleId: "COR004", severity: "warning", message: "a warning" },
|
|
496
|
+
],
|
|
497
|
+
});
|
|
498
|
+
for (const components of [false, true]) {
|
|
499
|
+
stderrBuf.length = 0;
|
|
500
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: project, components }), plugins: [], serializers: [] });
|
|
501
|
+
expect(exit).toBe(1);
|
|
502
|
+
const err = stderrBuf.join("\n");
|
|
503
|
+
expect(err).toContain(`dependencies not installed: @intentius/chant-lexicon-aws does not resolve from ${project}`);
|
|
504
|
+
expect(err).toContain("run npm install (or the project's package manager)");
|
|
505
|
+
expect(err).not.toMatch(/lint errors/i);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test("an uninstalled import beside a real lint error: both are said", async () => {
|
|
510
|
+
lintMock.mockResolvedValue({
|
|
511
|
+
success: false,
|
|
512
|
+
diagnostics: [
|
|
513
|
+
missing("@intentius/chant-lexicon-aws", "src/a.ts"),
|
|
514
|
+
{ file: join(project, "src/b.ts"), line: 3, column: 1, ruleId: "COR001", severity: "error", message: "a real problem" },
|
|
515
|
+
],
|
|
516
|
+
});
|
|
517
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: project }), plugins: [], serializers: [] });
|
|
518
|
+
expect(exit).toBe(1);
|
|
519
|
+
const err = stderrBuf.join("\n");
|
|
520
|
+
expect(err).toContain("dependencies not installed: @intentius/chant-lexicon-aws");
|
|
521
|
+
expect(err).toMatch(/source has lint errors/);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("real lint errors still say lint errors, with no dependencies message", async () => {
|
|
525
|
+
lintMock.mockResolvedValue({
|
|
526
|
+
success: false,
|
|
527
|
+
diagnostics: [{ file: join(project, "src/b.ts"), line: 3, column: 1, ruleId: "COR001", severity: "error", message: "a real problem" }],
|
|
528
|
+
});
|
|
529
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: project }), plugins: [], serializers: [] });
|
|
530
|
+
expect(exit).toBe(1);
|
|
531
|
+
const err = stderrBuf.join("\n");
|
|
532
|
+
expect(err).toMatch(/source has lint errors/);
|
|
533
|
+
expect(err).not.toContain("dependencies not installed");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("an import the project has installed is a lint error, not a missing dependency", async () => {
|
|
537
|
+
mkdirSync(join(project, "node_modules", "@intentius", "chant-lexicon-aws"), { recursive: true });
|
|
538
|
+
lintMock.mockResolvedValue({ success: false, diagnostics: [missing("@intentius/chant-lexicon-aws", "src/a.ts")] });
|
|
539
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir", path: project }), plugins: [], serializers: [] });
|
|
540
|
+
expect(exit).toBe(1);
|
|
541
|
+
const err = stderrBuf.join("\n");
|
|
542
|
+
expect(err).toMatch(/source has lint errors/);
|
|
543
|
+
expect(err).not.toContain("dependencies not installed");
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
471
547
|
test("--format mermaid emits a flowchart", async () => {
|
|
472
548
|
lintClean(); discovered();
|
|
473
549
|
const exit = await runGraph({ args: makeArgs({ format: "mermaid" }), plugins: [], serializers: [] });
|
|
@@ -19,7 +19,8 @@ import { applyLens, parseLens } from "../../graph-lens";
|
|
|
19
19
|
import { toMermaid } from "../../graph-mermaid";
|
|
20
20
|
import { toDot } from "../../graph-dot";
|
|
21
21
|
import { getLayoutEngine, toLayoutInput, type NodeSize } from "../../graph-layout";
|
|
22
|
-
import { lintCommand } from "../commands/lint";
|
|
22
|
+
import { lintCommand, type LintResult } from "../commands/lint";
|
|
23
|
+
import { reportUninstalledDependencies } from "../dependencies";
|
|
23
24
|
import { loadPlugins, resolveProjectLexicons, collectBuildRootContributors } from "../plugins";
|
|
24
25
|
import { isNoLexiconDetected } from "../../detectLexicon";
|
|
25
26
|
import { readFileSync } from "node:fs";
|
|
@@ -555,6 +556,21 @@ async function runComponentGraph(ctx: CommandContext): Promise<number> {
|
|
|
555
556
|
return 0;
|
|
556
557
|
}
|
|
557
558
|
|
|
559
|
+
/**
|
|
560
|
+
* The lint gate's refusal. When the lint errors are imports of packages the
|
|
561
|
+
* project has not installed (#4116), the dependencies message leads, and it
|
|
562
|
+
* stands alone when every error is one: the source is not what is wrong.
|
|
563
|
+
*/
|
|
564
|
+
function refuseLintGate(lint: LintResult, projectPath: string): void {
|
|
565
|
+
const errors = (lint.diagnostics ?? []).filter((d) => d.severity === "error").map((d) => d.message);
|
|
566
|
+
if (reportUninstalledDependencies(errors, projectPath) === "all") return;
|
|
567
|
+
console.error(
|
|
568
|
+
formatError({
|
|
569
|
+
message: "Refusing to emit graph: source has lint errors. Run `chant lint` and fix them first.",
|
|
570
|
+
}),
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
558
574
|
/**
|
|
559
575
|
* `chant graph --components --format ir|mermaid|dot|layout` — the component DAG
|
|
560
576
|
* projected into the renderable IR (nodes = components, `groups.byWave` = the
|
|
@@ -596,11 +612,7 @@ async function runComponentGraphView(
|
|
|
596
612
|
buildParams: componentParams,
|
|
597
613
|
});
|
|
598
614
|
if (!lint.success) {
|
|
599
|
-
|
|
600
|
-
formatError({
|
|
601
|
-
message: "Refusing to emit graph: source has lint errors. Run `chant lint` and fix them first.",
|
|
602
|
-
}),
|
|
603
|
-
);
|
|
615
|
+
refuseLintGate(lint, projectPath);
|
|
604
616
|
return 1;
|
|
605
617
|
}
|
|
606
618
|
|
|
@@ -869,12 +881,7 @@ async function runGraphView(
|
|
|
869
881
|
buildParams,
|
|
870
882
|
});
|
|
871
883
|
if (!lint.success) {
|
|
872
|
-
|
|
873
|
-
formatError({
|
|
874
|
-
message:
|
|
875
|
-
"Refusing to emit graph: source has lint errors. Run `chant lint` and fix them first.",
|
|
876
|
-
}),
|
|
877
|
-
);
|
|
884
|
+
refuseLintGate(lint, projectPath);
|
|
878
885
|
return 1;
|
|
879
886
|
}
|
|
880
887
|
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* #1108 gap in generate mode.
|
|
17
17
|
*/
|
|
18
18
|
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
19
|
+
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
19
22
|
import type { ParsedArgs } from "../registry";
|
|
20
23
|
|
|
21
24
|
const lintCommandMock = vi.fn();
|
|
@@ -105,3 +108,42 @@ describe("runLint build-time parameters (#2251)", () => {
|
|
|
105
108
|
expect(lintCommandMock).not.toHaveBeenCalled();
|
|
106
109
|
});
|
|
107
110
|
});
|
|
111
|
+
|
|
112
|
+
describe("runLint on a project whose dependencies are not installed (#4116)", () => {
|
|
113
|
+
let project: string;
|
|
114
|
+
let stderr: string[];
|
|
115
|
+
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
project = realpathSync(mkdtempSync(join(tmpdir(), "chant-4116-lint-")));
|
|
118
|
+
stderr = [];
|
|
119
|
+
loadChantConfigUpwardMock.mockReset().mockResolvedValue({ config: {} });
|
|
120
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void stderr.push(s));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
vi.restoreAllMocks();
|
|
125
|
+
rmSync(project, { recursive: true, force: true });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const failing = (message: string) =>
|
|
129
|
+
lintCommandMock.mockReset().mockResolvedValue({
|
|
130
|
+
success: false,
|
|
131
|
+
errorCount: 1,
|
|
132
|
+
warningCount: 0,
|
|
133
|
+
diagnostics: [{ file: join(project, "src/a.ts"), line: 1, column: 1, ruleId: "LEX002", severity: "error", message }],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an uninstalled import leads with the dependencies message", async () => {
|
|
137
|
+
failing(`Cannot find package '@intentius/chant-lexicon-aws' imported from ${join(project, "src/a.ts")}`);
|
|
138
|
+
const exit = await runLint({ args: makeArgs({ path: project }), plugins: [], serializers: [] });
|
|
139
|
+
expect(exit).toBe(1);
|
|
140
|
+
expect(stderr.join("\n")).toContain(`dependencies not installed: @intentius/chant-lexicon-aws does not resolve from ${project}`);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a real lint error prints no dependencies message", async () => {
|
|
144
|
+
failing("Exported declarable 'x' is broken");
|
|
145
|
+
const exit = await runLint({ args: makeArgs({ path: project }), plugins: [], serializers: [] });
|
|
146
|
+
expect(exit).toBe(1);
|
|
147
|
+
expect(stderr.join("\n")).not.toContain("dependencies not installed");
|
|
148
|
+
});
|
|
149
|
+
});
|
package/src/cli/handlers/lint.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { lintCommand, lintCommandWatch, printLintResult } from "../commands/lint";
|
|
3
3
|
import { formatError, formatInfo } from "../format";
|
|
4
|
+
import { reportUninstalledDependencies } from "../dependencies";
|
|
4
5
|
import type { CommandContext } from "../registry";
|
|
5
6
|
import { commandBuildParams } from "../build-params-cli";
|
|
6
7
|
import { loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
@@ -63,6 +64,11 @@ export async function runLint(ctx: CommandContext): Promise<number> {
|
|
|
63
64
|
buildParams,
|
|
64
65
|
});
|
|
65
66
|
|
|
67
|
+
// #4116: errors that are imports of uninstalled packages are led by the dependencies message.
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
const errors = result.diagnostics.filter((d) => d.severity === "error").map((d) => d.message);
|
|
70
|
+
reportUninstalledDependencies(errors, resolve(args.path));
|
|
71
|
+
}
|
|
66
72
|
printLintResult(result);
|
|
67
73
|
return result.success ? 0 : 1;
|
|
68
74
|
}
|
|
@@ -221,8 +221,8 @@ export async function runOperator(ctx: CommandContext): Promise<number> {
|
|
|
221
221
|
*
|
|
222
222
|
* Refuses, before ticking anything:
|
|
223
223
|
* - an environment whose form is `fountain`, because there the composite's
|
|
224
|
-
* Agent and
|
|
225
|
-
* writer beside them;
|
|
224
|
+
* Agent and its conversation are the steward and this process would be a
|
|
225
|
+
* second writer beside them;
|
|
226
226
|
* - a steward whose lease another live process holds (a second local
|
|
227
227
|
* operator for it).
|
|
228
228
|
*/
|
|
@@ -242,7 +242,7 @@ export async function runStewardOperator(ctx: CommandContext): Promise<number> {
|
|
|
242
242
|
if (form !== "local") {
|
|
243
243
|
console.error(formatError({
|
|
244
244
|
message: `steward "${steward.name}" runs on ${form} in environment "${env}", so a local operator for it would be a second writer`,
|
|
245
|
-
hint: `Its turns run
|
|
245
|
+
hint: `Its turns run in the fountain steward "${steward.name}"'s conversation. Run this in an environment the declaration's form makes local, or change the form.`,
|
|
246
246
|
}));
|
|
247
247
|
return 1;
|
|
248
248
|
}
|