@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
package/src/ts-loader.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
1
3
|
import { join, sep } from "node:path";
|
|
2
4
|
import { pathToFileURL } from "node:url";
|
|
3
5
|
import { describe, expect, test } from "vitest";
|
|
4
|
-
import { chantSourceDir, ensureTypeScriptLoader } from "./ts-loader";
|
|
6
|
+
import { chantPackageSourceTarget, chantSourceDir, lexiconCompiledTwin, ensureTypeScriptLoader, ensureTypeScriptLoaderFor, typeScriptLoaderPending } from "./ts-loader";
|
|
5
7
|
|
|
6
8
|
describe("ts-loader (#3878)", () => {
|
|
7
9
|
const pkg = join(sep, "x", "node_modules", "@intentius", "chant");
|
|
@@ -20,4 +22,90 @@ describe("ts-loader (#3878)", () => {
|
|
|
20
22
|
test("from source, the TypeScript loader is already there and nothing is registered", () => {
|
|
21
23
|
expect(ensureTypeScriptLoader()).toBe(false);
|
|
22
24
|
});
|
|
25
|
+
|
|
26
|
+
test("from source, no module asks for the loader (#3981)", () => {
|
|
27
|
+
expect(typeScriptLoaderPending()).toBe(false);
|
|
28
|
+
expect(ensureTypeScriptLoaderFor("/p/src/index.ts")).toBe(false);
|
|
29
|
+
expect(ensureTypeScriptLoaderFor("/p/dist/index.js")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("compiled, a JavaScript module leaves tsx unregistered (#3981)", () => {
|
|
33
|
+
const STATE = Symbol.for("chant.compiledCli");
|
|
34
|
+
const global = globalThis as { [STATE]?: { tsx: boolean } };
|
|
35
|
+
global[STATE] = { tsx: false };
|
|
36
|
+
try {
|
|
37
|
+
expect(typeScriptLoaderPending()).toBe(true);
|
|
38
|
+
expect(ensureTypeScriptLoaderFor("file:///p/dist/index.js")).toBe(false);
|
|
39
|
+
expect(ensureTypeScriptLoaderFor("/p/dist/meta.json")).toBe(false);
|
|
40
|
+
expect(global[STATE]).toEqual({ tsx: false });
|
|
41
|
+
global[STATE] = { tsx: true };
|
|
42
|
+
expect(typeScriptLoaderPending()).toBe(false);
|
|
43
|
+
expect(ensureTypeScriptLoaderFor("/p/src/index.ts")).toBe(true);
|
|
44
|
+
} finally {
|
|
45
|
+
delete global[STATE];
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("chantPackageSourceTarget (#3981)", () => {
|
|
51
|
+
test("resolves a symlinked lexicon's root and pattern exports to their development source, by real path", () => {
|
|
52
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-source-target-")));
|
|
53
|
+
try {
|
|
54
|
+
const lexicon = join(root, "lexicons", "demo");
|
|
55
|
+
mkdirSync(join(lexicon, "src", "generated"), { recursive: true });
|
|
56
|
+
writeFileSync(join(lexicon, "src", "index.ts"), "");
|
|
57
|
+
writeFileSync(join(lexicon, "src", "generated", "index.ts"), "");
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(lexicon, "package.json"),
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
name: "@intentius/chant-lexicon-demo",
|
|
62
|
+
exports: {
|
|
63
|
+
".": { development: "./src/index.ts", types: "./dist/index.d.ts", default: "./dist/index.js" },
|
|
64
|
+
"./*": { development: "./src/*.ts", types: "./dist/*.d.ts", default: "./dist/*.js" },
|
|
65
|
+
"./meta": "./dist/meta.json",
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
mkdirSync(join(root, "project", "node_modules", "@intentius"), { recursive: true });
|
|
70
|
+
symlinkSync(lexicon, join(root, "project", "node_modules", "@intentius", "chant-lexicon-demo"), "dir");
|
|
71
|
+
mkdirSync(join(root, "project", "src"));
|
|
72
|
+
const parent = join(root, "project", "src", "infra.ts");
|
|
73
|
+
|
|
74
|
+
expect(chantPackageSourceTarget("@intentius/chant-lexicon-demo", parent)).toBe(join(lexicon, "src", "index.ts"));
|
|
75
|
+
expect(chantPackageSourceTarget("@intentius/chant-lexicon-demo/generated/index", parent)).toBe(join(lexicon, "src", "generated", "index.ts"));
|
|
76
|
+
// No development condition, a missing file, another scope or an absent package: left to Node.
|
|
77
|
+
expect(chantPackageSourceTarget("@intentius/chant-lexicon-demo/meta", parent)).toBeUndefined();
|
|
78
|
+
expect(chantPackageSourceTarget("@intentius/chant-lexicon-demo/missing", parent)).toBeUndefined();
|
|
79
|
+
expect(chantPackageSourceTarget("@other/chant-lexicon-demo", parent)).toBeUndefined();
|
|
80
|
+
expect(chantPackageSourceTarget("@intentius/chant-lexicon-absent", parent)).toBeUndefined();
|
|
81
|
+
} finally {
|
|
82
|
+
rmSync(root, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("lexiconCompiledTwin (#3981)", () => {
|
|
88
|
+
test("points a lexicon's src/*.ts at its dist/*.js when that exists, and nothing else", () => {
|
|
89
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-compiled-twin-")));
|
|
90
|
+
try {
|
|
91
|
+
const lexicon = join(root, "node_modules", "@intentius", "chant-lexicon-demo");
|
|
92
|
+
const other = join(root, "node_modules", "other");
|
|
93
|
+
for (const dir of [join(lexicon, "src", "generated"), join(lexicon, "dist", "generated"), join(other, "src"), join(other, "dist")]) {
|
|
94
|
+
mkdirSync(dir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(join(lexicon, "package.json"), JSON.stringify({ name: "@intentius/chant-lexicon-demo" }));
|
|
97
|
+
writeFileSync(join(other, "package.json"), JSON.stringify({ name: "other" }));
|
|
98
|
+
for (const file of ["src/generated/index.ts", "dist/generated/index.js", "src/only-source.ts"]) writeFileSync(join(lexicon, file), "");
|
|
99
|
+
for (const file of ["src/index.ts", "dist/index.js"]) writeFileSync(join(other, file), "");
|
|
100
|
+
const url = (path: string) => pathToFileURL(path).href;
|
|
101
|
+
|
|
102
|
+
expect(lexiconCompiledTwin(url(join(lexicon, "src", "generated", "index.ts")))).toBe(url(join(lexicon, "dist", "generated", "index.js")));
|
|
103
|
+
expect(lexiconCompiledTwin(url(join(lexicon, "src", "only-source.ts")))).toBeUndefined();
|
|
104
|
+
expect(lexiconCompiledTwin(url(join(lexicon, "dist", "generated", "index.js")))).toBeUndefined();
|
|
105
|
+
expect(lexiconCompiledTwin(url(join(other, "src", "index.ts")))).toBeUndefined();
|
|
106
|
+
expect(lexiconCompiledTwin("node:fs")).toBeUndefined();
|
|
107
|
+
} finally {
|
|
108
|
+
rmSync(root, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
23
111
|
});
|
package/src/ts-loader.ts
CHANGED
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
*
|
|
19
19
|
* Run from source (the repository's `bin/chant` through tsx, vitest), this
|
|
20
20
|
* module is itself TypeScript, the TypeScript loader is already there, and
|
|
21
|
-
* both functions do nothing
|
|
21
|
+
* both functions do nothing, except that {@link installCompiledCli} resolves
|
|
22
|
+
* chant's packages to their source (#3981, {@link installSourceResolution}).
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
|
-
import { existsSync } from "node:fs";
|
|
25
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
25
26
|
import * as nodeModule from "node:module";
|
|
26
27
|
import { dirname, join, sep } from "node:path";
|
|
27
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
|
+
import { isJavaScriptModule, moduleSourceDir } from "./module-source";
|
|
28
30
|
|
|
29
31
|
// `module.registerHooks` (Node 22.15+, 24) is newer than the @types/node the
|
|
30
32
|
// repository pins, so its shape is declared here.
|
|
@@ -57,11 +59,15 @@ function runsCompiled(): boolean {
|
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
61
|
* Called once by the CLI's `main()`. Compiled, it records that TypeScript
|
|
60
|
-
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect
|
|
61
|
-
* source it
|
|
62
|
+
* loads lazily and installs the `src/*.ts` to `dist/*.js` redirect. From
|
|
63
|
+
* source it installs {@link installSourceResolution}.
|
|
62
64
|
*/
|
|
63
65
|
export function installCompiledCli(): void {
|
|
64
|
-
if (!runsCompiled()
|
|
66
|
+
if (!runsCompiled()) {
|
|
67
|
+
installSourceResolution();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (compiledState()) return;
|
|
65
71
|
(globalThis as { [STATE]?: CompiledCliState })[STATE] = { tsx: false };
|
|
66
72
|
|
|
67
73
|
const distDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -72,7 +78,10 @@ export function installCompiledCli(): void {
|
|
|
72
78
|
registerHooks({
|
|
73
79
|
resolve(specifier, context, nextResolve) {
|
|
74
80
|
const result = nextResolve(specifier, context);
|
|
75
|
-
if (!result.url.startsWith(srcUrl))
|
|
81
|
+
if (!result.url.startsWith(srcUrl)) {
|
|
82
|
+
const twin = lexiconCompiledTwin(result.url);
|
|
83
|
+
return twin === undefined ? result : { ...result, url: twin, format: "module" };
|
|
84
|
+
}
|
|
76
85
|
const path = result.url.slice(srcUrl.length).replace(/[?#].*$/, "");
|
|
77
86
|
if (!path.endsWith(".ts") || path.endsWith(".d.ts")) return result;
|
|
78
87
|
let target = compiled.get(path);
|
|
@@ -86,6 +95,144 @@ export function installCompiledCli(): void {
|
|
|
86
95
|
});
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
/** Package directories by the directory a module sits in: a lexicon package's root, or null for any other package. */
|
|
99
|
+
const lexiconRoots = new Map<string, string | null>();
|
|
100
|
+
|
|
101
|
+
/** The lexicon package `dir` belongs to: the nearest directory up with a package.json, when it names a chant lexicon. */
|
|
102
|
+
function lexiconRootOf(dir: string): string | undefined {
|
|
103
|
+
const seen: string[] = [];
|
|
104
|
+
let root: string | null = null;
|
|
105
|
+
for (let at = dir; ; ) {
|
|
106
|
+
const cached = lexiconRoots.get(at);
|
|
107
|
+
if (cached !== undefined) {
|
|
108
|
+
root = cached;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
seen.push(at);
|
|
112
|
+
const manifest = join(at, "package.json");
|
|
113
|
+
if (existsSync(manifest)) {
|
|
114
|
+
try {
|
|
115
|
+
const name = (JSON.parse(readFileSync(manifest, "utf-8")) as { name?: unknown }).name;
|
|
116
|
+
root = typeof name === "string" && name.startsWith("@intentius/chant-lexicon-") ? at : null;
|
|
117
|
+
} catch {
|
|
118
|
+
root = null;
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
const parent = dirname(at);
|
|
123
|
+
if (parent === at) break;
|
|
124
|
+
at = parent;
|
|
125
|
+
}
|
|
126
|
+
for (const at of seen) lexiconRoots.set(at, root);
|
|
127
|
+
return root ?? undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* #3981 — the compiled module for a lexicon's `src/*.ts`: `<lexicon>/dist/*.js`
|
|
132
|
+
* when it exists, as a URL, else `undefined`. A lexicon ships both, and its
|
|
133
|
+
* exports name the compiled one; fold reads a composite from the source, and
|
|
134
|
+
* an import that source makes must land on the module the plugin and the
|
|
135
|
+
* project already loaded, with no TypeScript loader.
|
|
136
|
+
*/
|
|
137
|
+
export function lexiconCompiledTwin(url: string): string | undefined {
|
|
138
|
+
if (!url.startsWith("file:")) return undefined;
|
|
139
|
+
const path = fileURLToPath(url.replace(/[?#].*$/, ""));
|
|
140
|
+
if (!/\.[cm]?ts$/.test(path) || /\.d\.[cm]?ts$/.test(path) || !path.includes(`${sep}src${sep}`)) return undefined;
|
|
141
|
+
const root = lexiconRootOf(dirname(path));
|
|
142
|
+
if (root === undefined) return undefined;
|
|
143
|
+
const srcDir = join(root, "src") + sep;
|
|
144
|
+
if (!path.startsWith(srcDir)) return undefined;
|
|
145
|
+
const js = join(root, "dist", path.slice(srcDir.length)).replace(/\.([cm]?)ts$/, ".$1js");
|
|
146
|
+
return existsSync(js) ? pathToFileURL(js).href : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const SOURCE_RESOLUTION = Symbol.for("chant.sourceResolution");
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* #3981 — the `development` target a chant package's `exports` give
|
|
153
|
+
* `specifier`, found from `parentPath`'s directory up through each
|
|
154
|
+
* `node_modules`, or `undefined` when there is none or its file does not
|
|
155
|
+
* exist. Reads exact keys and single-`*` patterns, the shapes chant's
|
|
156
|
+
* packages use; anything else is left to Node.
|
|
157
|
+
*/
|
|
158
|
+
export function chantPackageSourceTarget(specifier: string, parentPath: string): string | undefined {
|
|
159
|
+
const match = /^(@intentius\/chant(?:-[a-z0-9-]+)?)(\/.*)?$/.exec(specifier);
|
|
160
|
+
if (!match) return undefined;
|
|
161
|
+
const subpath = match[2] ? `.${match[2]}` : ".";
|
|
162
|
+
let dir = dirname(parentPath);
|
|
163
|
+
for (;;) {
|
|
164
|
+
const pkgDir = join(dir, "node_modules", match[1]!);
|
|
165
|
+
const manifest = join(pkgDir, "package.json");
|
|
166
|
+
if (existsSync(manifest)) {
|
|
167
|
+
let exportsField: unknown;
|
|
168
|
+
try {
|
|
169
|
+
exportsField = (JSON.parse(readFileSync(manifest, "utf-8")) as { exports?: unknown }).exports;
|
|
170
|
+
} catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (!exportsField || typeof exportsField !== "object") return undefined;
|
|
174
|
+
const map = exportsField as Record<string, unknown>;
|
|
175
|
+
let entry: unknown = map[subpath];
|
|
176
|
+
let star: string | undefined;
|
|
177
|
+
if (entry === undefined) {
|
|
178
|
+
let best = "";
|
|
179
|
+
for (const key of Object.keys(map)) {
|
|
180
|
+
const at = key.indexOf("*");
|
|
181
|
+
if (at < 0 || key.indexOf("*", at + 1) >= 0) continue;
|
|
182
|
+
const prefix = key.slice(0, at);
|
|
183
|
+
const suffix = key.slice(at + 1);
|
|
184
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < key.length || prefix.length <= best.length) continue;
|
|
185
|
+
best = prefix;
|
|
186
|
+
entry = map[key];
|
|
187
|
+
star = subpath.slice(prefix.length, subpath.length - suffix.length);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const development = entry && typeof entry === "object" ? (entry as { development?: unknown }).development : undefined;
|
|
191
|
+
if (typeof development !== "string") return undefined;
|
|
192
|
+
const target = join(pkgDir, star === undefined ? development : development.split("*").join(star));
|
|
193
|
+
// Node keys a module by its real path, so a symlinked package must too.
|
|
194
|
+
return existsSync(target) ? realpathSync(target) : undefined;
|
|
195
|
+
}
|
|
196
|
+
const parent = dirname(dir);
|
|
197
|
+
if (parent === dir) return undefined;
|
|
198
|
+
dir = parent;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* #3981 — from source, resolve chant's own packages through their
|
|
204
|
+
* `development` export condition, as `node --conditions=development` (which
|
|
205
|
+
* the repository's `bin/chant` passes) would. A lexicon's `default` export is
|
|
206
|
+
* its compiled `dist/*.js`, which a checkout may not have built, or built from
|
|
207
|
+
* older source; `development` is its `src/*.ts`, which vitest, a project's
|
|
208
|
+
* tsconfig `paths` and fold's own resolution (`SOURCE_EXPORT_CONDITIONS`)
|
|
209
|
+
* pick. Without this, a CLI run from source by `node --import tsx` would load
|
|
210
|
+
* a plugin from `dist/` and fold the project against `src/`: two copies of the
|
|
211
|
+
* lexicon with their own classes.
|
|
212
|
+
*
|
|
213
|
+
* The hook resolves the specifier itself and short-circuits: conditions added
|
|
214
|
+
* to the context here do not reach tsx's resolver. Nothing is installed under
|
|
215
|
+
* vitest, whose resolver already adds the condition, or on a node without
|
|
216
|
+
* `module.registerHooks`.
|
|
217
|
+
*/
|
|
218
|
+
function installSourceResolution(): void {
|
|
219
|
+
// In a vitest worker (not a CLI it spawns, which inherits its environment), vite resolves.
|
|
220
|
+
if ("__vitest_worker__" in globalThis || typeof registerHooks !== "function") return;
|
|
221
|
+
const flags = globalThis as { [SOURCE_RESOLUTION]?: boolean };
|
|
222
|
+
if (flags[SOURCE_RESOLUTION]) return;
|
|
223
|
+
flags[SOURCE_RESOLUTION] = true;
|
|
224
|
+
registerHooks({
|
|
225
|
+
resolve(specifier, context, nextResolve) {
|
|
226
|
+
const parentURL = (context as { parentURL?: string } | undefined)?.parentURL;
|
|
227
|
+
if (specifier.startsWith("@intentius/chant") && parentURL?.startsWith("file:")) {
|
|
228
|
+
const target = chantPackageSourceTarget(specifier, fileURLToPath(parentURL));
|
|
229
|
+
if (target !== undefined) return { url: pathToFileURL(target).href, shortCircuit: true };
|
|
230
|
+
}
|
|
231
|
+
return nextResolve(specifier, context);
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
89
236
|
/**
|
|
90
237
|
* Register tsx for the ESM and CommonJS loaders, once, before importing
|
|
91
238
|
* anything that may be TypeScript. A no-op from source, where tsx (or vitest)
|
|
@@ -104,19 +251,39 @@ export function ensureTypeScriptLoader(): boolean {
|
|
|
104
251
|
return true;
|
|
105
252
|
}
|
|
106
253
|
|
|
254
|
+
/**
|
|
255
|
+
* #3981 — register tsx only when the module about to be imported is not
|
|
256
|
+
* JavaScript. `pathOrUrl` is the resolved module, a path or a file URL. A
|
|
257
|
+
* compiled lexicon (`dist/*.js`) imports with plain node, so a command that
|
|
258
|
+
* loads only compiled modules never pays for tsx's hooks thread. A `.ts`
|
|
259
|
+
* module, or one with no extension, registers tsx as before. Returns what
|
|
260
|
+
* {@link ensureTypeScriptLoader} does, or whether tsx is already registered
|
|
261
|
+
* when the module is JavaScript.
|
|
262
|
+
*/
|
|
263
|
+
export function ensureTypeScriptLoaderFor(pathOrUrl: string): boolean {
|
|
264
|
+
if (!isJavaScriptModule(pathOrUrl)) return ensureTypeScriptLoader();
|
|
265
|
+
return compiledState()?.tsx ?? false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* #3981 — whether a caller about to import a module of unknown kind should
|
|
270
|
+
* resolve it first: true only in the compiled CLI before tsx is registered.
|
|
271
|
+
* From source, and once tsx is in, every module loads either way.
|
|
272
|
+
*/
|
|
273
|
+
export function typeScriptLoaderPending(): boolean {
|
|
274
|
+
const state = compiledState();
|
|
275
|
+
return state !== undefined && !state.tsx;
|
|
276
|
+
}
|
|
277
|
+
|
|
107
278
|
/**
|
|
108
279
|
* The source directory for a module of chant's own: `dirname` of `moduleUrl`
|
|
109
280
|
* from source, and the matching directory under `<package>/src` when the
|
|
110
281
|
* module runs from `<package>/dist`. The sandbox drivers hand chant's own
|
|
111
282
|
* modules to esbuild by path, and must name the TypeScript source the
|
|
112
283
|
* project's own `@intentius/chant/*` imports resolve to. String arithmetic
|
|
113
|
-
* only: no filesystem access, so it is safe inside a sandboxed child.
|
|
284
|
+
* only: no filesystem access, so it is safe inside a sandboxed child. The
|
|
285
|
+
* same mapping as {@link moduleSourceDir}, which lexicons use.
|
|
114
286
|
*/
|
|
115
287
|
export function chantSourceDir(moduleUrl: string): string {
|
|
116
|
-
|
|
117
|
-
if (!moduleUrl.endsWith(".js")) return dir;
|
|
118
|
-
const marker = `${sep}dist${sep}`;
|
|
119
|
-
const at = (dir + sep).lastIndexOf(marker);
|
|
120
|
-
if (at < 0) return dir;
|
|
121
|
-
return dir.slice(0, at) + `${sep}src` + dir.slice(at + marker.length - 1);
|
|
288
|
+
return moduleSourceDir(moduleUrl);
|
|
122
289
|
}
|
|
@@ -292,7 +292,7 @@ describe("the forward coverage check (#2773)", () => {
|
|
|
292
292
|
});
|
|
293
293
|
|
|
294
294
|
test("on a work branch, the range and the item come from the branch (an Op under changesCheckout)", async () => {
|
|
295
|
-
const tree = join(root, ".
|
|
295
|
+
const tree = join(root, ".chant", "work", "W-001");
|
|
296
296
|
git(root, "worktree", "add", "-q", "-b", "chant/work/W-001", tree, "HEAD");
|
|
297
297
|
writeFiles(tree, { "app/lib/a.mjs": "export const a = 2;\n", "docs/readme.md": "# Docs, again\n" });
|
|
298
298
|
git(tree, "add", "-A");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { rewriteFountainFile } from "./fountain-retired-kinds";
|
|
5
|
+
|
|
6
|
+
const fixture = JSON.parse(readFileSync(join(import.meta.dirname, "../__fixtures__/studio-template-84c8b21.json"), "utf-8")) as { files: Record<string, string> };
|
|
7
|
+
const team = fixture.files["delivery/agents/team.ts"]!;
|
|
8
|
+
|
|
9
|
+
describe("fountain-retired-kinds (#3987)", () => {
|
|
10
|
+
test("the studio template's team.ts loses its Teammates, Schedules and persistent sandbox, and keeps its Agents", () => {
|
|
11
|
+
const r = rewriteFountainFile("delivery/agents/team.ts", team)!;
|
|
12
|
+
expect(r.conflict).toBeUndefined();
|
|
13
|
+
expect(r.text).toContain('import { Agent, Environment, Vault } from "@intentius/chant-lexicon-fountain";');
|
|
14
|
+
expect(r.text).not.toMatch(/new (Teammate|Schedule)\(|sandbox_mode|nightlyMeta|Teammates below/);
|
|
15
|
+
for (const agent of ["formalizer", "splitter", "builderSmall", "builderMedium", "builderLarge", "dispatcher"]) expect(r.text).toContain(`export const ${agent} = new Agent({`);
|
|
16
|
+
expect(r.text.endsWith("});\n")).toBe(true);
|
|
17
|
+
expect(r.notMoved.map((n) => n.what)).toEqual([
|
|
18
|
+
'the Schedule nightly (cron "0 3 * * *") in delivery/agents/team.ts',
|
|
19
|
+
'the Schedule factoryDispatch (cron "*/15 * * * *") in delivery/agents/team.ts',
|
|
20
|
+
]);
|
|
21
|
+
expect(rewriteFountainFile("delivery/agents/team.ts", r.text)).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("a removed declaration the file still uses is a conflict", () => {
|
|
25
|
+
const text = `${team}\nexport const crew = [factory];\n`;
|
|
26
|
+
expect(rewriteFountainFile("delivery/agents/team.ts", text)!.conflict).toMatch(/^factory is still used/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("a file with no retired kind plans nothing", () => {
|
|
30
|
+
const text = 'import { Agent } from "@intentius/chant-lexicon-fountain";\nexport const a = new Agent({\n name: "a",\n});\n';
|
|
31
|
+
expect(rewriteFountainFile("agents.ts", text)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fountain-retired-kinds` (#3987, managoat/managoat#2793): take a project's
|
|
3
|
+
* fountain declarations off the kinds Managoat v0.29.0 removed.
|
|
4
|
+
*
|
|
5
|
+
* Managoat dropped the team, its schedules, webhooks and persistent
|
|
6
|
+
* sandboxes. Since #3987 the fountain lexicon keeps `Teammate`, `Schedule`
|
|
7
|
+
* and `Webhook` only as stubs, and FTN025 fails a build that declares one, or
|
|
8
|
+
* an Agent with `sandbox_mode: "persistent"`. chud's template (and the studio
|
|
9
|
+
* kit's copy of it) declares a Teammate and a Schedule for its night shift
|
|
10
|
+
* and its factory, and gives the dispatcher a persistent sandbox, so a repo
|
|
11
|
+
* migrated off chud stopped building with chant 0.124.32.
|
|
12
|
+
*
|
|
13
|
+
* In each `chant` member, every `.ts` file that imports the fountain lexicon
|
|
14
|
+
* loses its `new Teammate(...)`, `new Schedule(...)` and `new Webhook(...)`
|
|
15
|
+
* declarations, those names from the import, any one-line constant only they
|
|
16
|
+
* used, and `sandbox_mode: "persistent"`. Each removed Schedule is listed as
|
|
17
|
+
* not moved: its clock now lives outside Managoat. A removed declaration whose
|
|
18
|
+
* name the file still uses elsewhere is a conflict, as is a file that still
|
|
19
|
+
* names a retired kind after the edits.
|
|
20
|
+
*
|
|
21
|
+
* The plan is empty once no fountain file declares a retired kind.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
25
|
+
import { join, posix } from "node:path";
|
|
26
|
+
import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, NotMoved, PlanConflict, PlannedChange } from "../chant-migrations";
|
|
27
|
+
import { applyEdits, readText, type Edit } from "./chud-lexicon-exit";
|
|
28
|
+
|
|
29
|
+
export const FOUNTAIN_RETIRED_KINDS = "fountain-retired-kinds";
|
|
30
|
+
|
|
31
|
+
const DESCRIPTION = "drop the fountain kinds Managoat removed: Teammate, Schedule, Webhook and persistent sandboxes (#3987)";
|
|
32
|
+
|
|
33
|
+
const FOUNTAIN = "@intentius/chant-lexicon-fountain";
|
|
34
|
+
const RETIRED = ["Teammate", "Schedule", "Webhook"] as const;
|
|
35
|
+
|
|
36
|
+
const IMPORT = /import\s*\{([^}]*)\}\s*from\s*"@intentius\/chant-lexicon-fountain";/;
|
|
37
|
+
/** A top-level declaration of a retired kind, with the doc comment just above it. */
|
|
38
|
+
const DECLARATION = /(?:\/\*\*(?:(?!\*\/)[\s\S])*\*\/\n)?export const (\w+) = new (Teammate|Schedule|Webhook)\(\{\n[\s\S]*?\n\}\);\n(?:\n)?/g;
|
|
39
|
+
const PERSISTENT = /^[ \t]*sandbox_mode:\s*"persistent",?[ \t]*\n/gm;
|
|
40
|
+
|
|
41
|
+
/** The template's prose about what the edits remove. */
|
|
42
|
+
const TEMPLATE_PROSE: Edit[] = [
|
|
43
|
+
{
|
|
44
|
+
find: " * build by this name (CHUD_FOUNTAIN_VAULT overrides it), and the two\n * Teammates below run with it.\n",
|
|
45
|
+
replace: " * build by this name (CHUD_FOUNTAIN_VAULT overrides it).\n",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
find:
|
|
49
|
+
" * Steward does: a persistent sandbox (its checkout survives a turn) and no\n" +
|
|
50
|
+
" * permission prompts (nobody is at the keyboard; chant's own gates still need\n" +
|
|
51
|
+
" * a person). Its Teammate gives it one thread, so two schedule fires never\n" +
|
|
52
|
+
" * run two dispatchers on one checkout, and the Schedule is what triggers it.\n",
|
|
53
|
+
replace:
|
|
54
|
+
" * Steward does, with no permission prompts (nobody is at the keyboard;\n" +
|
|
55
|
+
" * chant's own gates still need a person). Managoat has no team or scheduler\n" +
|
|
56
|
+
" * any more: a clock outside it runs `chant run dispatch --on fountain`.\n",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** The README row `chud-lexicon-exit` wrote, which names the factory's Schedule. */
|
|
61
|
+
const README_EDITS: Edit[] = [
|
|
62
|
+
{ find: " (the agents that build the app, as fountain specs; its factory Schedule still calls the kit's dispatch Op)", replace: " (the agents that build the app, as fountain specs)" },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
function tsFiles(dir: string, rel = "", out: string[] = []): string[] {
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = readdirSync(join(dir, rel), { withFileTypes: true });
|
|
69
|
+
} catch {
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
for (const e of entries) {
|
|
73
|
+
const path = rel ? posix.join(rel, e.name) : e.name;
|
|
74
|
+
if (e.isDirectory()) {
|
|
75
|
+
if (e.name === "node_modules" || e.name === "dist" || e.name.startsWith(".")) continue;
|
|
76
|
+
tsFiles(dir, path, out);
|
|
77
|
+
} else if (e.name.endsWith(".ts") && !e.name.endsWith(".d.ts")) {
|
|
78
|
+
out.push(path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The text with its comments and string contents blanked, so prose naming a kind is not a use of it. */
|
|
85
|
+
const code = (text: string) => text.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`(?:\\.|[^`\\])*`/g, '""');
|
|
86
|
+
const uses = (text: string, name: string) => new RegExp(`(?<![\\w$.])${name}(?![\\w$])`).test(code(text));
|
|
87
|
+
|
|
88
|
+
interface Rewrite {
|
|
89
|
+
text: string;
|
|
90
|
+
notMoved: NotMoved[];
|
|
91
|
+
conflict?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The file without its retired declarations, or null when it has none. */
|
|
95
|
+
export function rewriteFountainFile(path: string, original: string): Rewrite | null {
|
|
96
|
+
const imported = original.match(IMPORT);
|
|
97
|
+
if (!imported) return null;
|
|
98
|
+
const declared = [...original.matchAll(DECLARATION)];
|
|
99
|
+
const persistent = PERSISTENT.test(original);
|
|
100
|
+
PERSISTENT.lastIndex = 0;
|
|
101
|
+
if (declared.length === 0 && !persistent) return null;
|
|
102
|
+
|
|
103
|
+
const notMoved: NotMoved[] = [];
|
|
104
|
+
let text = original;
|
|
105
|
+
const removed: string[] = [];
|
|
106
|
+
const referenced = new Set<string>();
|
|
107
|
+
for (const [block, name, kind] of declared) {
|
|
108
|
+
text = text.replace(block, "");
|
|
109
|
+
removed.push(name!);
|
|
110
|
+
const body = block.slice(block.indexOf("new "));
|
|
111
|
+
for (const id of body.match(/\b[a-zA-Z_$][\w$]*\b/g) ?? []) referenced.add(id);
|
|
112
|
+
if (kind === "Schedule") {
|
|
113
|
+
const cron = body.match(/cron:\s*"([^"]+)"/)?.[1];
|
|
114
|
+
notMoved.push({
|
|
115
|
+
what: `the Schedule ${name}${cron ? ` (cron "${cron}")` : ""} in ${path}`,
|
|
116
|
+
where: "a clock outside Managoat, which has no scheduler: a CI schedule or a CronJob running `chant run <op> --on fountain`, or `chant operator --steward`",
|
|
117
|
+
});
|
|
118
|
+
} else if (kind === "Webhook") {
|
|
119
|
+
notMoved.push({ what: `the Webhook ${name} in ${path}`, where: "nothing: read a run's outcome from `chant run status` or `log`, or the conversation's event stream" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
text = text.replace(PERSISTENT, "");
|
|
123
|
+
|
|
124
|
+
// A one-line constant only the removed declarations used (the template's `nightlyMeta`).
|
|
125
|
+
for (const id of referenced) {
|
|
126
|
+
const line = new RegExp(`^export const ${id} = [^\\n]*;\\n\\n?`, "m");
|
|
127
|
+
const hit = text.match(line);
|
|
128
|
+
if (hit && !uses(text.replace(hit[0], ""), id)) text = text.replace(hit[0], "");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const names = imported[1]!.split(",").map((n) => n.trim()).filter(Boolean);
|
|
132
|
+
const kept = names.filter((n) => !(RETIRED as readonly string[]).includes(n) || uses(text.replace(IMPORT, ""), n));
|
|
133
|
+
if (kept.length !== names.length) text = text.replace(IMPORT, `import { ${kept.join(", ")} } from "${FOUNTAIN}";`);
|
|
134
|
+
text = applyEdits(text, TEMPLATE_PROSE).text.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
|
|
135
|
+
|
|
136
|
+
const dangling = removed.filter((n) => uses(text, n));
|
|
137
|
+
if (dangling.length > 0) return { text, notMoved, conflict: `${dangling.join(", ")} ${dangling.length === 1 ? "is" : "are"} still used after the retired declarations go. Remove ${dangling.length === 1 ? "that use" : "those uses"} by hand, then run the upgrade again` };
|
|
138
|
+
const still = RETIRED.filter((k) => new RegExp(`\\bnew ${k}\\(`).test(text));
|
|
139
|
+
if (still.length > 0) return { text, notMoved, conflict: `a ${still.join(", ")} declaration this migration cannot read. Remove it by hand, then run the upgrade again` };
|
|
140
|
+
return { text, notMoved };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface Declaration {
|
|
144
|
+
members?: Array<{ dir?: string; kind?: string }>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function planRetiredKinds(ctx: ChantMigrationContext): ChantMigrationPlan | null {
|
|
148
|
+
const { dir } = ctx;
|
|
149
|
+
let decl: Declaration | null = null;
|
|
150
|
+
try {
|
|
151
|
+
decl = JSON.parse(readText(dir, "chant.workspace.json") ?? "null") as Declaration | null;
|
|
152
|
+
} catch {
|
|
153
|
+
decl = null;
|
|
154
|
+
}
|
|
155
|
+
const members = (decl?.members ?? []).filter((m) => m.kind === "chant" && m.dir).map((m) => posix.normalize(m.dir!));
|
|
156
|
+
if (members.length === 0 && existsSync(join(dir, "chant.config.ts"))) members.push(".");
|
|
157
|
+
|
|
158
|
+
const changes: PlannedChange[] = [];
|
|
159
|
+
const notMoved: NotMoved[] = [];
|
|
160
|
+
const conflicts: PlanConflict[] = [];
|
|
161
|
+
for (const member of members) {
|
|
162
|
+
for (const rel of tsFiles(join(dir, member))) {
|
|
163
|
+
const path = posix.join(member, rel);
|
|
164
|
+
const text = readText(dir, path);
|
|
165
|
+
if (text === undefined || !text.includes(FOUNTAIN)) continue;
|
|
166
|
+
const rewrite = rewriteFountainFile(path, text);
|
|
167
|
+
if (!rewrite) continue;
|
|
168
|
+
if (rewrite.conflict) {
|
|
169
|
+
conflicts.push({ path, reason: rewrite.conflict });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
changes.push({ path, action: "write", why: "no Teammate, Schedule, Webhook or persistent sandbox, which Managoat removed and FTN025 fails", data: Buffer.from(rewrite.text) });
|
|
173
|
+
notMoved.push(...rewrite.notMoved);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (changes.length === 0 && conflicts.length === 0) return null;
|
|
177
|
+
|
|
178
|
+
const readme = readText(dir, "README.md");
|
|
179
|
+
if (readme !== undefined && conflicts.length === 0) {
|
|
180
|
+
const edited = applyEdits(readme, README_EDITS).text;
|
|
181
|
+
if (edited !== readme) changes.push({ path: "README.md", action: "write", why: "the agents no longer declare a factory Schedule", data: Buffer.from(edited) });
|
|
182
|
+
}
|
|
183
|
+
return { id: FOUNTAIN_RETIRED_KINDS, description: DESCRIPTION, changes, notMoved, conflicts };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const fountainRetiredKinds: ChantMigration = {
|
|
187
|
+
id: FOUNTAIN_RETIRED_KINDS,
|
|
188
|
+
description: DESCRIPTION,
|
|
189
|
+
plan: planRetiredKinds,
|
|
190
|
+
};
|
|
@@ -34,6 +34,7 @@ import { CHUD_LEXICON_EXIT_ROLLBACK } from "./chant-migrations/chud-lexicon-exit
|
|
|
34
34
|
import { CHUD_LEXICON_EXIT_SHIP_POLICY } from "./chant-migrations/chud-lexicon-exit-ship-policy";
|
|
35
35
|
import { CHUD_LEXICON_EXIT_FLY_SITE, chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
|
|
36
36
|
import { CHUD_LEXICON_EXIT_LIVE_NAMES } from "./chant-migrations/chud-lexicon-exit-live-names";
|
|
37
|
+
import { FOUNTAIN_RETIRED_KINDS } from "./chant-migrations/fountain-retired-kinds";
|
|
37
38
|
import { readerVersion } from "./declaration";
|
|
38
39
|
import { runChecks } from "./lineage-check";
|
|
39
40
|
import { initFromCommand } from "./lineage-init";
|
|
@@ -174,6 +175,10 @@ describe.each(["dir", "git"] as const)("chud-lexicon-exit, from a %s source", (f
|
|
|
174
175
|
expect(out).toContain(`chant migration: ${CHUD_LEXICON_EXIT_ROLLBACK} (applied)`);
|
|
175
176
|
expect(out).toContain("write: delivery/ops/rollback.op.ts");
|
|
176
177
|
expect(out).not.toContain("not moved: the release's steps after the ship gate");
|
|
178
|
+
// #3987: the template's Teammates, Schedules and persistent sandbox fail FTN025, so a migrated repo would not build.
|
|
179
|
+
expect(out).toContain(`chant migration: ${FOUNTAIN_RETIRED_KINDS} (applied)`);
|
|
180
|
+
expect(out).toContain("write: delivery/agents/team.ts");
|
|
181
|
+
expect(out).toMatch(/not moved: the Schedule factoryDispatch \(cron "\*\/15 \* \* \* \*"\) in delivery\/agents\/team\.ts -> a clock outside Managoat/);
|
|
177
182
|
// #2805: the plan lists README.md, CLAUDE.md and design/CLAUDE.md as
|
|
178
183
|
// changed, not silently left describing what the migration just deleted.
|
|
179
184
|
expect(out).toContain("write: README.md");
|
|
@@ -42,6 +42,7 @@ import { chudLexiconExitShipInputs } from "./chant-migrations/chud-lexicon-exit-
|
|
|
42
42
|
import { chudLexiconExitShipPolicy } from "./chant-migrations/chud-lexicon-exit-ship-policy";
|
|
43
43
|
import { chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
|
|
44
44
|
import { chudLexiconExitLiveNames } from "./chant-migrations/chud-lexicon-exit-live-names";
|
|
45
|
+
import { fountainRetiredKinds } from "./chant-migrations/fountain-retired-kinds";
|
|
45
46
|
|
|
46
47
|
/** One file a plan writes or deletes, relative to the scope. */
|
|
47
48
|
export interface PlannedChange {
|
|
@@ -98,6 +99,7 @@ export const CHANT_MIGRATIONS: readonly ChantMigration[] = [
|
|
|
98
99
|
chudLexiconExitShipPolicy,
|
|
99
100
|
chudLexiconExitFlySite,
|
|
100
101
|
chudLexiconExitLiveNames,
|
|
102
|
+
fountainRetiredKinds,
|
|
101
103
|
];
|
|
102
104
|
|
|
103
105
|
/** The plans for a scope: each migration not yet in the lineage that finds something to move. */
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
* listed here after these two.
|
|
29
29
|
*
|
|
30
30
|
* WSP125 (`box-fountain-callback-undeclared`, #2780) is the one credential a
|
|
31
|
-
* box gets without asking. fountain
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* has to say so: the capability `fountain-callback`, brokered by `fountain`,
|
|
31
|
+
* box gets without asking. fountain gives every sandbox a callback token
|
|
32
|
+
* scoped to its owner (`FOUNTAIN_TOKEN`, `sandbox_api_access: owner`) unless
|
|
33
|
+
* its conversation asks for none, and a fountain `Box` runs with it, so its
|
|
34
|
+
* member's block has to say so: the capability `fountain-callback`, brokered by `fountain`,
|
|
35
35
|
* with scope `owner`. It is named apart from `fountain`, the API access a
|
|
36
36
|
* lobby may broker for the same box. A member builds a Box when one of its TypeScript or JavaScript files
|
|
37
37
|
* imports `Box` from `@intentius/chant-lexicon-fountain` and calls it, read as
|
|
38
|
-
* syntax and never run. Upstream, managoat/fountain#2497
|
|
39
|
-
* a
|
|
38
|
+
* syntax and never run. Upstream, managoat/fountain#2497 asked for a way to run
|
|
39
|
+
* a box's agent with no callback token.
|
|
40
40
|
*
|
|
41
41
|
* WSP126 (`box-intent-unknown`) and WSP127 (`box-intent-unconstrained`), #2850,
|
|
42
42
|
* read the decision record a box block names as its intent (`box-intent.ts`).
|
|
@@ -75,7 +75,7 @@ export const WSP_BOX_FOUNTAIN_CALLBACK = "WSP125";
|
|
|
75
75
|
export const WSP_BOX_INTENT_UNKNOWN = "WSP126";
|
|
76
76
|
export const WSP_BOX_INTENT_UNCONSTRAINED = "WSP127";
|
|
77
77
|
|
|
78
|
-
/** The fountain lexicon's package, whose `Box` composite
|
|
78
|
+
/** The fountain lexicon's package, whose `Box` composite declares a box's agent. */
|
|
79
79
|
const FOUNTAIN_LEXICON = "@intentius/chant-lexicon-fountain";
|
|
80
80
|
|
|
81
81
|
/**
|
|
@@ -402,7 +402,7 @@ export const BOX_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
402
402
|
id: WSP_BOX_FOUNTAIN_CALLBACK,
|
|
403
403
|
name: "box-fountain-callback-undeclared",
|
|
404
404
|
description:
|
|
405
|
-
"A box member that builds a fountain Box declares the callback token fountain gives its
|
|
405
|
+
"A box member that builds a fountain Box declares the callback token fountain gives its sandbox: the fountain-callback capability, brokered by fountain, with scope owner.",
|
|
406
406
|
severity: "error",
|
|
407
407
|
configurable: true,
|
|
408
408
|
check(ctx) {
|
|
@@ -442,7 +442,7 @@ export const BOX_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
442
442
|
severity: this.severity,
|
|
443
443
|
code: "box-fountain-callback-undeclared",
|
|
444
444
|
message:
|
|
445
|
-
`box-fountain-callback-undeclared: member ${m.name} builds a fountain Box (${at.path}), and fountain
|
|
445
|
+
`box-fountain-callback-undeclared: member ${m.name} builds a fountain Box (${at.path}), and fountain gives a box's sandbox ` +
|
|
446
446
|
`a callback token scoped to its owner (FOUNTAIN_TOKEN); ${why}. Declare ${expected} in the box block ` +
|
|
447
447
|
`(managoat/fountain#2497 tracks running without it)`,
|
|
448
448
|
entity: m.name,
|