@intentius/chant 0.100.0 → 0.101.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/build-options.d.ts +2 -0
- package/dist/cli/build-options.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +8 -0
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon.d.ts +24 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-digest.d.ts +26 -5
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lint/config.d.ts +4 -4
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +2 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/serializer.d.ts +8 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +77 -0
- package/dist/telemetry-attribution.d.ts.map +1 -0
- package/dist/workspace/agent-cli.d.ts +83 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes.d.ts +8 -1
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/checks/links.d.ts +1 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/live.d.ts +40 -0
- package/dist/workspace/checks/live.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +21 -2
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +63 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +32 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +138 -3
- package/dist/workspace/export-cli.d.ts +12 -0
- package/dist/workspace/export-cli.d.ts.map +1 -0
- package/dist/workspace/export.d.ts +145 -0
- package/dist/workspace/export.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/import.d.ts +73 -0
- package/dist/workspace/import.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +6 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
- package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-adopt.d.ts +106 -0
- package/dist/workspace/lineage-adopt.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +9 -2
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-cli.d.ts +6 -1
- package/dist/workspace/lineage-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts +110 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +10 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +147 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.d.ts +15 -3
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-provenance.d.ts +18 -0
- package/dist/workspace/lineage-provenance.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +30 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-versions.d.ts +116 -0
- package/dist/workspace/lineage-versions.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +31 -5
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/ls-generated.d.ts +37 -0
- package/dist/workspace/ls-generated.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/nested-graph.d.ts +56 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -0
- package/dist/workspace/nesting.d.ts +21 -0
- package/dist/workspace/nesting.d.ts.map +1 -0
- package/dist/workspace/pin-cli.d.ts +10 -0
- package/dist/workspace/pin-cli.d.ts.map +1 -0
- package/dist/workspace/pin-integrity.d.ts +51 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +26 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +7 -11
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +30 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +5 -2
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +22 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +43 -5
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/returns.d.ts +129 -0
- package/dist/workspace/returns.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/template-manifest.d.ts +11 -3
- package/dist/workspace/template-manifest.d.ts.map +1 -1
- package/dist/workspace/trust/attestor.d.ts +8 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/dsse.d.ts +58 -0
- package/dist/workspace/trust/dsse.d.ts.map +1 -0
- package/dist/workspace/trust/evidence-cli.d.ts +66 -0
- package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
- package/dist/workspace/trust/evidence.d.ts +93 -0
- package/dist/workspace/trust/evidence.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +54 -1
- package/dist/workspace/trust/policy.d.ts.map +1 -1
- package/dist/workspace/trust/provenance.d.ts +21 -1
- package/dist/workspace/trust/provenance.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts +132 -0
- package/dist/workspace/trust/rotation.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/signers-cli.d.ts +49 -0
- package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +12 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts +13 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify.d.ts +15 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/write-scope.d.ts +199 -0
- package/dist/workspace/write-scope.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/build.ts +8 -0
- package/src/cli/build-options.ts +4 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/import-live.test.ts +69 -1
- package/src/cli/commands/import.ts +48 -22
- package/src/cli/handlers/fan-out.test.ts +6 -6
- package/src/cli/handlers/fan-out.ts +2 -1
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +22 -0
- package/src/cli/handlers/operator.ts +1 -1
- package/src/cli/main.test.ts +32 -0
- package/src/cli/main.ts +103 -6
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +33 -2
- package/src/cli/registry.ts +22 -0
- package/src/cli/serve-mcp-workspace.test.ts +1 -1
- package/src/codegen/release-wiring.test.ts +5 -1
- package/src/components/fan-out-output.test.ts +1 -1
- package/src/components/fan-out.test.ts +1 -1
- package/src/components/promote.test.ts +1 -1
- package/src/config.ts +12 -0
- package/src/content-digest.test.ts +2 -2
- package/src/lexicon.ts +25 -1
- package/src/lifecycle/gate-ledger.test.ts +14 -0
- package/src/lifecycle/gate-ledger.ts +2 -1
- package/src/lifecycle/plan-digest.test.ts +54 -3
- package/src/lifecycle/plan-digest.ts +38 -8
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/propose-upgrade.ts +8 -5
- package/src/op/gate-approval.test.ts +17 -0
- package/src/op/gate.ts +3 -3
- package/src/op/index.ts +1 -1
- package/src/serializer.ts +9 -0
- package/src/telemetry-attribution.test.ts +91 -0
- package/src/telemetry-attribution.ts +145 -0
- package/src/workspace/agent-cli.ts +134 -0
- package/src/workspace/agent.schema.json +356 -0
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/changes-cli.ts +5 -0
- package/src/workspace/changes.schema.json +179 -1
- package/src/workspace/changes.ts +58 -4
- package/src/workspace/check-live.test.ts +192 -0
- package/src/workspace/check.schema.json +64 -0
- package/src/workspace/checks/links.ts +23 -2
- package/src/workspace/checks/live.ts +113 -0
- package/src/workspace/checks.test.ts +2 -2
- package/src/workspace/checks.ts +20 -3
- package/src/workspace/compose-graph.test.ts +47 -0
- package/src/workspace/compose-graph.ts +120 -3
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
- package/src/workspace/declaration.schema.json +138 -3
- package/src/workspace/declaration.ts +106 -1
- package/src/workspace/declared-kinds.test.ts +33 -0
- package/src/workspace/evidence.schema.json +279 -0
- package/src/workspace/export-cli.ts +180 -0
- package/src/workspace/export-import.test.ts +282 -0
- package/src/workspace/export.ts +486 -0
- package/src/workspace/graph-cli.ts +47 -1
- package/src/workspace/graph-contract.test.ts +89 -4
- package/src/workspace/graph.schema.json +206 -1
- package/src/workspace/import.ts +325 -0
- package/src/workspace/kinds.test.ts +5 -5
- package/src/workspace/kinds.ts +25 -3
- package/src/workspace/lineage-adopt-cli.ts +103 -0
- package/src/workspace/lineage-adopt.test.ts +552 -0
- package/src/workspace/lineage-adopt.ts +452 -0
- package/src/workspace/lineage-check.ts +26 -5
- package/src/workspace/lineage-cli.ts +12 -2
- package/src/workspace/lineage-hash-index.ts +305 -0
- package/src/workspace/lineage-init.test.ts +9 -0
- package/src/workspace/lineage-init.ts +29 -9
- package/src/workspace/lineage-lock.ts +54 -0
- package/src/workspace/lineage-migrations.test.ts +27 -0
- package/src/workspace/lineage-migrations.ts +44 -14
- package/src/workspace/lineage-provenance.ts +40 -0
- package/src/workspace/lineage-upgrade-cli.ts +9 -5
- package/src/workspace/lineage-upgrade.test.ts +92 -1
- package/src/workspace/lineage-upgrade.ts +111 -16
- package/src/workspace/lineage-versions.ts +348 -0
- package/src/workspace/links.test.ts +121 -2
- package/src/workspace/links.ts +97 -6
- package/src/workspace/ls-contract.test.ts +84 -1
- package/src/workspace/ls-generated.ts +111 -0
- package/src/workspace/ls.schema.json +19 -0
- package/src/workspace/ls.ts +8 -1
- package/src/workspace/member-commands.ts +3 -1
- package/src/workspace/nested-graph.test.ts +176 -0
- package/src/workspace/nested-graph.ts +169 -0
- package/src/workspace/nesting.ts +37 -0
- package/src/workspace/pin-cli.test.ts +71 -0
- package/src/workspace/pin-cli.ts +57 -0
- package/src/workspace/pin-integrity.test.ts +121 -0
- package/src/workspace/pin-integrity.ts +104 -0
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/read-contract.test.ts +24 -0
- package/src/workspace/reason-codes.test.ts +10 -0
- package/src/workspace/reason-codes.ts +29 -2
- package/src/workspace/record-sessions.ts +12 -14
- package/src/workspace/records-amend.schema.json +4 -0
- package/src/workspace/records-cli.ts +93 -12
- package/src/workspace/records-close.schema.json +6 -2
- package/src/workspace/records-close.ts +10 -2
- package/src/workspace/records-formats.test.ts +13 -5
- package/src/workspace/records-new.schema.json +4 -0
- package/src/workspace/records-review.schema.json +4 -0
- package/src/workspace/records-sessions.test.ts +23 -6
- package/src/workspace/records-write.test.ts +65 -8
- package/src/workspace/records-write.ts +70 -6
- package/src/workspace/records.schema.json +55 -3
- package/src/workspace/records.ts +96 -5
- package/src/workspace/returns.ts +328 -0
- package/src/workspace/signers.schema.json +206 -0
- package/src/workspace/status-gates.ts +2 -1
- package/src/workspace/template-manifest.ts +22 -4
- package/src/workspace/trust/attestor.ts +15 -0
- package/src/workspace/trust/dsse.ts +134 -0
- package/src/workspace/trust/evidence-cli.ts +195 -0
- package/src/workspace/trust/evidence.test.ts +241 -0
- package/src/workspace/trust/evidence.ts +207 -0
- package/src/workspace/trust/policy.ts +110 -3
- package/src/workspace/trust/provenance.ts +41 -4
- package/src/workspace/trust/record-seal.test.ts +1 -1
- package/src/workspace/trust/rotation.test.ts +258 -0
- package/src/workspace/trust/rotation.ts +336 -0
- package/src/workspace/trust/seal.ts +11 -0
- package/src/workspace/trust/signers-cli.ts +178 -0
- package/src/workspace/trust/ssh-commit.ts +53 -4
- package/src/workspace/trust/test-repo.ts +18 -0
- package/src/workspace/trust/trust.test.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +1 -0
- package/src/workspace/trust/verify.ts +22 -0
- package/src/workspace/work-evidence.schema.json +4 -0
- package/src/workspace/write-scope.test.ts +340 -0
- package/src/workspace/write-scope.ts +448 -0
|
@@ -20,7 +20,7 @@ import { WORKSPACE_UPGRADE_GATE_OP } from "../op/gate-name";
|
|
|
20
20
|
import { LockError } from "./lineage-lock";
|
|
21
21
|
import { applyStagedUpgrade, describeStaged, stageUpgrade, type ChantRunner, type StagedUpgrade } from "./lineage-upgrade";
|
|
22
22
|
|
|
23
|
-
const USAGE = "chant workspace upgrade [<scope>] [--to <ref|dir>] [--allow-code] [--dry-run] [--output <patch file>] [--json]";
|
|
23
|
+
const USAGE = "chant workspace upgrade [<scope>] [--to <ref|dir>] [--source <repo>[#<member>]] [--allow-code] [--dry-run] [--output <patch file>] [--json]";
|
|
24
24
|
|
|
25
25
|
/** The exit code of a gated upgrade: the same as `chant run`'s gated run. */
|
|
26
26
|
export const UPGRADE_GATED_EXIT = 3;
|
|
@@ -29,6 +29,8 @@ export interface UpgradeCommandOptions {
|
|
|
29
29
|
root: string;
|
|
30
30
|
scope?: string;
|
|
31
31
|
to?: string;
|
|
32
|
+
/** Move the scope to another template (#2551). */
|
|
33
|
+
source?: string;
|
|
32
34
|
allowCode?: boolean;
|
|
33
35
|
dryRun?: boolean;
|
|
34
36
|
output?: string;
|
|
@@ -53,6 +55,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
|
|
|
53
55
|
root: opts.root,
|
|
54
56
|
scope: opts.scope,
|
|
55
57
|
to: opts.to,
|
|
58
|
+
...(opts.source !== undefined ? { source: opts.source } : {}),
|
|
56
59
|
allowCode: opts.allowCode,
|
|
57
60
|
runChant: opts.runChant,
|
|
58
61
|
});
|
|
@@ -72,7 +75,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
|
|
|
72
75
|
);
|
|
73
76
|
}
|
|
74
77
|
if (!staged.changed && staged.checksOk) {
|
|
75
|
-
if (!opts.json) console.error(formatSuccess(`scope "${staged.
|
|
78
|
+
if (!opts.json) console.error(formatSuccess(`scope "${staged.gate}" is already at ${staged.to ?? "its source"}; nothing to upgrade`));
|
|
76
79
|
return report("up-to-date", 0);
|
|
77
80
|
}
|
|
78
81
|
if (opts.output) writeFileSync(resolve(opts.output), staged.patch);
|
|
@@ -90,11 +93,11 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
|
|
|
90
93
|
return report("dry-run", 0);
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
const gate = staged.
|
|
96
|
+
const gate = staged.gate;
|
|
94
97
|
const check = await evaluateGate(opts.ledger ?? gitGateLedgerPort({ cwd: staged.repo }), {
|
|
95
98
|
op: WORKSPACE_UPGRADE_GATE_OP,
|
|
96
99
|
gate,
|
|
97
|
-
description: `upgrade ${staged.template} ${staged.from ?? ""} -> ${staged.to ?? ""} in scope ${staged.scope}`.replace(/\s+/g, " "),
|
|
100
|
+
description: `upgrade ${staged.template} ${staged.from ?? ""} -> ${staged.to ?? ""} in scope ${staged.member ?? staged.scope}`.replace(/\s+/g, " "),
|
|
98
101
|
planDigest: staged.digest,
|
|
99
102
|
...(staged.governance ? { approval: staged.governance.approval } : {}),
|
|
100
103
|
...(opts.now ? { now: opts.now } : {}),
|
|
@@ -115,7 +118,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
|
|
|
115
118
|
|
|
116
119
|
applyStagedUpgrade(staged);
|
|
117
120
|
if (!opts.json) {
|
|
118
|
-
console.error(formatSuccess(`applied the approved upgrade of "${staged.
|
|
121
|
+
console.error(formatSuccess(`applied the approved upgrade of "${staged.gate}" (${staged.digest}), approved by ${check.resolution.resolvedBy}. Review and commit it.`));
|
|
119
122
|
if (staged.manualSteps.length > 0) {
|
|
120
123
|
console.error(formatInfo(`${staged.manualSteps.length} manual step(s) are open; \`chant workspace check\` fails until each is merged and resolved.`));
|
|
121
124
|
}
|
|
@@ -137,6 +140,7 @@ export async function runWorkspaceUpgrade(ctx: CommandContext): Promise<number>
|
|
|
137
140
|
root: process.cwd(),
|
|
138
141
|
scope: args.extraPositional,
|
|
139
142
|
to: args.migrateTo,
|
|
143
|
+
source: args.source,
|
|
140
144
|
allowCode: args.allowCode,
|
|
141
145
|
dryRun: args.dryRun,
|
|
142
146
|
output: args.output,
|
|
@@ -110,7 +110,7 @@ describe("chant workspace upgrade", () => {
|
|
|
110
110
|
expect(staged.written.sort()).toEqual(["README.md", "src/new.ts"]);
|
|
111
111
|
expect(staged.manualSteps).toEqual([]);
|
|
112
112
|
expect(staged.changedPaths).toEqual([LOCK_FILE, "README.md", "src/a.ts", "src/new.ts"]);
|
|
113
|
-
expect(staged.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
113
|
+
expect(staged.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
114
114
|
// The project's tree is untouched until the gate is approved.
|
|
115
115
|
expect(read(proj, "README.md")).toBe("starter\n");
|
|
116
116
|
expect(existsSync(join(proj, "src/new.ts"))).toBe(false);
|
|
@@ -571,3 +571,94 @@ describe("chant workspace upgrade with template parameters (#2627)", () => {
|
|
|
571
571
|
await expect(stageUpgrade({ root: proj, to: "v2.0.0", runChant: passing })).rejects.toThrow(/region has no default/);
|
|
572
572
|
});
|
|
573
573
|
});
|
|
574
|
+
|
|
575
|
+
describe("chant workspace upgrade of a member scope (#2550)", () => {
|
|
576
|
+
let ws: string;
|
|
577
|
+
const declaration = {
|
|
578
|
+
name: "ws",
|
|
579
|
+
schema: 1,
|
|
580
|
+
members: [
|
|
581
|
+
{ name: "shop", dir: "shop", kind: "other", because: "made from a template" },
|
|
582
|
+
{ name: "plain", dir: "plain", kind: "other", because: "written by hand" },
|
|
583
|
+
],
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
beforeEach(async () => {
|
|
587
|
+
ws = join(root, "ws");
|
|
588
|
+
mkdirSync(ws);
|
|
589
|
+
git(ws, ["init", "-q", "-b", "main"]);
|
|
590
|
+
put(ws, "chant.workspace.json", JSON.stringify(declaration));
|
|
591
|
+
put(ws, "plain/readme.md", "by hand\n");
|
|
592
|
+
const made = await initFromCommand({ from: `${tpl}@v1.0.0`, path: join(ws, "shop") });
|
|
593
|
+
expect(made.error).toBeUndefined();
|
|
594
|
+
git(ws, ["add", "-A"]);
|
|
595
|
+
git(ws, ["commit", "-q", "-m", "workspace"]);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("upgrades the member from the workspace root, writes only inside it, and gates under the member's name", async () => {
|
|
599
|
+
release("v2.0.0", { "README.md": "starter v2\n", "src/new.ts": "new\n" });
|
|
600
|
+
const l = ledger();
|
|
601
|
+
const result = await upgradeCommand({ root: ws, scope: "shop", to: "v2.0.0", runChant: passing, ledger: l, json: true });
|
|
602
|
+
expect(result.outcome).toBe("gated");
|
|
603
|
+
expect(result.exitCode).toBe(3);
|
|
604
|
+
expect(l.pending[0].gate).toBe("shop");
|
|
605
|
+
const staged = result.staged!;
|
|
606
|
+
expect(staged.member).toBe("shop");
|
|
607
|
+
expect(staged.scope).toBe(".");
|
|
608
|
+
expect(staged.changedPaths.every((p) => p.startsWith("shop/"))).toBe(true);
|
|
609
|
+
|
|
610
|
+
l.approve(staged.digest, new Date().toISOString());
|
|
611
|
+
const applied = await upgradeCommand({ root: ws, scope: "shop", to: "v2.0.0", runChant: passing, ledger: l, json: true });
|
|
612
|
+
expect(applied.outcome).toBe("applied");
|
|
613
|
+
expect(read(ws, "shop/README.md")).toBe("starter v2\n");
|
|
614
|
+
expect(read(ws, "plain/readme.md")).toBe("by hand\n");
|
|
615
|
+
expect(readLock(join(ws, "shop"))!.scopes["."].ref).toBe("v2.0.0");
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
test("a member is named by directory too, and a member with no lock is refused with the reason", async () => {
|
|
619
|
+
release("v2.0.0", { "README.md": "starter v2\n" });
|
|
620
|
+
const staged = await stageUpgrade({ root: ws, scope: "shop/", to: "v2.0.0", runChant: passing });
|
|
621
|
+
try {
|
|
622
|
+
expect(staged.member).toBe("shop");
|
|
623
|
+
} finally {
|
|
624
|
+
staged.dispose();
|
|
625
|
+
}
|
|
626
|
+
await expect(stageUpgrade({ root: ws, scope: "plain", runChant: passing })).rejects.toThrow(/member "plain" \(plain\) has no \.chant\/workspace\.lock\.json, so it was not made from a template/);
|
|
627
|
+
await expect(stageUpgrade({ root: ws, scope: "nobody", runChant: passing })).rejects.toThrow(/no .*workspace\.lock\.json/);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("a scope the root lock holds is used as written, even when a member has the same directory", async () => {
|
|
631
|
+
// The root lock records the member's subtree as a scope: the member's own lock is not consulted.
|
|
632
|
+
const own = readLock(join(ws, "shop"))!;
|
|
633
|
+
// The recorded repository is relative to the lock's directory, so point it at the template as the root sees it.
|
|
634
|
+
const moved = { ...own.scopes["."], source: { ...own.scopes["."].source, url: tpl } } as (typeof own.scopes)["."];
|
|
635
|
+
writeLock(ws, { lockVersion: own.lockVersion, scopes: { shop: moved } });
|
|
636
|
+
git(ws, ["rm", "-rq", "--cached", "shop/.chant"]);
|
|
637
|
+
rmSync(join(ws, "shop/.chant"), { recursive: true });
|
|
638
|
+
commitProject2();
|
|
639
|
+
release("v2.0.0", { "README.md": "starter v2\n" });
|
|
640
|
+
const staged = await stageUpgrade({ root: ws, scope: "shop", to: "v2.0.0", runChant: passing });
|
|
641
|
+
try {
|
|
642
|
+
expect(staged.member).toBeUndefined();
|
|
643
|
+
expect(staged.scope).toBe("shop");
|
|
644
|
+
expect(staged.written).toEqual(["README.md"]);
|
|
645
|
+
expect(staged.changedPaths.sort()).toEqual([".chant/workspace.lock.json", "shop/README.md"]);
|
|
646
|
+
} finally {
|
|
647
|
+
staged.dispose();
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("a scope made by init --template is refused with its reason", async () => {
|
|
652
|
+
const lock = readLock(join(ws, "shop"))!;
|
|
653
|
+
lock.scopes["."] = { ...lock.scopes["."], kind: "template", source: { type: "lexicon", lexicon: "aws", template: "basic" } as never, template: "aws/basic" } as never;
|
|
654
|
+
writeLock(join(ws, "shop"), lock);
|
|
655
|
+
git(ws, ["add", "-A"]);
|
|
656
|
+
git(ws, ["commit", "-q", "-m", "lock"]);
|
|
657
|
+
await expect(stageUpgrade({ root: ws, scope: "shop", runChant: passing })).rejects.toThrow(/made by `chant init --template` \(aws\/basic\) is refused: .*no versioned source/);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
function commitProject2(): void {
|
|
661
|
+
git(ws, ["add", "-A"]);
|
|
662
|
+
git(ws, ["commit", "-q", "-m", "root lock"]);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
@@ -40,7 +40,7 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
|
40
40
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
41
41
|
import type { ResolvedGateApproval } from "../op/gate-approval";
|
|
42
42
|
import { checkLineage, findingKey, type CheckFinding } from "./lineage-check";
|
|
43
|
-
import { dirLabel, git, parseDirSpec, readTemplateDir, readTemplateTree, recordedDirPath } from "./lineage-init";
|
|
43
|
+
import { dirLabel, git, parseDirSpec, parseTemplateSource, portableUrl, readTemplateDir, readTemplateTree, recordedDirPath } from "./lineage-init";
|
|
44
44
|
import {
|
|
45
45
|
LOCK_FILE,
|
|
46
46
|
LockError,
|
|
@@ -60,6 +60,7 @@ import { reportPlan, runChantMigrations, type ChantMigrationReport } from "./cha
|
|
|
60
60
|
import { readDeclaration, readerVersion, WorkspaceReadError } from "./declaration";
|
|
61
61
|
import { assertCodeAllowed, planMigrations, runMigration, splitMigrations, type LoadedMigration } from "./lineage-migrations";
|
|
62
62
|
import { applyUpstream, type UpdateResult } from "./lineage-update";
|
|
63
|
+
import { pathsInsideNested } from "./nesting";
|
|
63
64
|
import { carryParameters, readManifest, substituteParameters } from "./template-manifest";
|
|
64
65
|
import { repinSubstituted, type RepinnedRecord } from "./template-pins";
|
|
65
66
|
import { workingTree } from "./tree";
|
|
@@ -113,7 +114,11 @@ export const spawnChant: ChantRunner = (command, cwd) =>
|
|
|
113
114
|
export interface UpgradeOptions {
|
|
114
115
|
/** The directory holding `.chant/workspace.lock.json`. */
|
|
115
116
|
root: string;
|
|
116
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* The scope to upgrade: `"."`, a scope of the lock at `root` (a vendor
|
|
119
|
+
* scope's directory, or a member subtree recorded in it), or the name or
|
|
120
|
+
* directory of a declared member that keeps a lock of its own (#2550).
|
|
121
|
+
*/
|
|
117
122
|
scope?: string;
|
|
118
123
|
/**
|
|
119
124
|
* The target ref (git) or label (vendor). Defaults to the ref the scope is
|
|
@@ -124,6 +129,12 @@ export interface UpgradeOptions {
|
|
|
124
129
|
to?: string;
|
|
125
130
|
/** Run migrations whose body is code. */
|
|
126
131
|
allowCode?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* `<repo>[#<member>]`: move the scope to this template (#2551). The chain
|
|
134
|
+
* starts with the new template's bridge migration from the scope's current
|
|
135
|
+
* template. Needs `to`, and a git scope.
|
|
136
|
+
*/
|
|
137
|
+
source?: string;
|
|
127
138
|
/** Replaces the child-process build and lint. For tests. */
|
|
128
139
|
runChant?: ChantRunner;
|
|
129
140
|
}
|
|
@@ -138,8 +149,15 @@ export interface GovernanceChange {
|
|
|
138
149
|
}
|
|
139
150
|
|
|
140
151
|
export interface StagedUpgrade {
|
|
152
|
+
/** The scope in the lock that was upgraded: `"."` for a member with a lock of its own. */
|
|
141
153
|
scope: string;
|
|
154
|
+
/** The declared member whose own lock this upgraded, when the scope argument named one. */
|
|
155
|
+
member?: string;
|
|
156
|
+
/** What the gate and the proposal branch are named: the member, else the scope. */
|
|
157
|
+
gate: string;
|
|
142
158
|
template: string;
|
|
159
|
+
/** Set when the upgrade moves the scope to another template (`--source`, #2551): that template's id. */
|
|
160
|
+
switchedTo?: string;
|
|
143
161
|
kind: Lineage["kind"];
|
|
144
162
|
/** The pin before and after. */
|
|
145
163
|
from: string | null;
|
|
@@ -226,11 +244,13 @@ function instantiate(
|
|
|
226
244
|
* fail (a server that refuses fetches by commit); the base is then left empty,
|
|
227
245
|
* and every edited file the template changed becomes a manual step.
|
|
228
246
|
*/
|
|
229
|
-
function fetchGit(root: string, lineage: Lineage, ref: string): Upstream {
|
|
247
|
+
function fetchGit(root: string, lineage: Lineage, ref: string, onto?: Onto): Upstream {
|
|
230
248
|
const source = lineage.source;
|
|
231
249
|
if (source.type !== "git") throw new UpgradeError("not a git source");
|
|
232
|
-
const
|
|
233
|
-
|
|
250
|
+
const baseUrl = source.url.startsWith(".") ? resolve(root, source.url) : source.url;
|
|
251
|
+
// With --source (#2551) the target comes from the other template; the base always from the scope's own.
|
|
252
|
+
const url = onto ? onto.url : baseUrl;
|
|
253
|
+
const label = `${onto ? onto.repo : source.repo}@${ref}`;
|
|
234
254
|
const scratch = mkdtempSync(join(tmpdir(), "chant-upgrade-"));
|
|
235
255
|
try {
|
|
236
256
|
git(scratch, ["init", "-q"]);
|
|
@@ -242,7 +262,7 @@ function fetchGit(root: string, lineage: Lineage, ref: string): Upstream {
|
|
|
242
262
|
throw new UpgradeError(`could not fetch ${label}${stderr ? `: ${stderr}` : ""}`);
|
|
243
263
|
}
|
|
244
264
|
const commit = git(scratch, ["rev-parse", "FETCH_HEAD^{commit}"]);
|
|
245
|
-
const target = readTemplateTree(scratch, commit, source.path, label);
|
|
265
|
+
const target = readTemplateTree(scratch, commit, onto ? onto.path : source.path, label);
|
|
246
266
|
|
|
247
267
|
const raw = new Map<string, Buffer>();
|
|
248
268
|
const executable = new Set<string>();
|
|
@@ -258,7 +278,7 @@ function fetchGit(root: string, lineage: Lineage, ref: string): Upstream {
|
|
|
258
278
|
const baseCommit = lineage.address?.commit;
|
|
259
279
|
if (baseCommit && baseCommit !== commit) {
|
|
260
280
|
try {
|
|
261
|
-
git(scratch, ["fetch", "-q", "--depth", "1",
|
|
281
|
+
git(scratch, ["fetch", "-q", "--depth", "1", baseUrl, baseCommit]);
|
|
262
282
|
const oldLabel = `${source.repo}@${baseCommit.slice(0, 12)}`;
|
|
263
283
|
const old = readTemplateTree(scratch, baseCommit, source.path, oldLabel);
|
|
264
284
|
// The base is rebuilt the way init wrote it: the old version's
|
|
@@ -297,7 +317,7 @@ function dirRefusal(lineage: Lineage, why: string): UpgradeError {
|
|
|
297
317
|
const source = lineage.source as Extract<LineageSource, { type: "dir" }>;
|
|
298
318
|
return new UpgradeError(
|
|
299
319
|
`${why}. The scope was made from the directory ${dirLabel(source.path, source.member)}, which has no git history to rebuild a merge base from. ` +
|
|
300
|
-
`Upgrade with --to <dir> while ${source.path} still holds the files the scope was made from, or
|
|
320
|
+
`Upgrade with --to <dir> while ${source.path} still holds the files the scope was made from, or move the scope onto the git repository the directory was copied from with \`chant workspace adopt-lineage --from <repo>[#<member>]\`.`,
|
|
301
321
|
);
|
|
302
322
|
}
|
|
303
323
|
|
|
@@ -359,11 +379,19 @@ function readDir(root: string, lineage: Lineage, to: string | undefined): Upstre
|
|
|
359
379
|
};
|
|
360
380
|
}
|
|
361
381
|
|
|
362
|
-
|
|
382
|
+
/** The template an upgrade moves the scope to (`--source`, #2551). */
|
|
383
|
+
interface Onto {
|
|
384
|
+
url: string;
|
|
385
|
+
repo: string;
|
|
386
|
+
path?: string;
|
|
387
|
+
id: string;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function fetchUpstream(root: string, lineage: Lineage, ref: string | undefined, onto?: Onto): Promise<Upstream> {
|
|
363
391
|
const source = lineage.source;
|
|
364
392
|
if (source.type === "git") {
|
|
365
393
|
if (!ref) throw new UpgradeError("the scope records no ref; pass --to <ref>");
|
|
366
|
-
return fetchGit(root, lineage, ref);
|
|
394
|
+
return fetchGit(root, lineage, ref, onto);
|
|
367
395
|
}
|
|
368
396
|
if (source.type === "local" || source.type === "archive") {
|
|
369
397
|
const { resolveVendorSource } = await import("../cli/commands/vendor");
|
|
@@ -372,7 +400,7 @@ async function fetchUpstream(root: string, lineage: Lineage, ref: string | undef
|
|
|
372
400
|
}
|
|
373
401
|
if (source.type === "dir") return readDir(root, lineage, ref);
|
|
374
402
|
throw new UpgradeError(
|
|
375
|
-
`scope made by \`chant init --template\` (${lineage.template})
|
|
403
|
+
`scope made by \`chant init --template\` (${lineage.template}) is refused: it came from a lexicon's built-in template, which has no versioned source to fetch, and the merge base would be the render of the chant version that wrote it, which chant cannot rebuild offline. Scopes made by \`chant init --from\` (a git repository or a directory) and \`chant vendor\` scopes upgrade.`,
|
|
376
404
|
);
|
|
377
405
|
}
|
|
378
406
|
|
|
@@ -486,6 +514,40 @@ function chantMemberDirs(dir: string): string[] {
|
|
|
486
514
|
}
|
|
487
515
|
}
|
|
488
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Resolve the scope argument against `root` (#2550). A scope the lock at
|
|
519
|
+
* `root` holds is used as it is. Otherwise a member the workspace
|
|
520
|
+
* declaration names (by name or directory) that keeps a lock of its own is
|
|
521
|
+
* upgraded from its directory, as the project it is, so its patch, gate and
|
|
522
|
+
* build stay inside it. A member without a lock was not made from a template.
|
|
523
|
+
*/
|
|
524
|
+
export function resolveUpgradeTarget(root: string, scopeArg: string | undefined): { root: string; scope: string; member?: string } {
|
|
525
|
+
const scope = scopeKey(scopeArg ?? ".");
|
|
526
|
+
const lock = readLock(root);
|
|
527
|
+
if (lock?.scopes[scope]) return { root, scope };
|
|
528
|
+
if (scope !== ".") {
|
|
529
|
+
let declared: Array<{ name: string; dir: string; kind: string }> = [];
|
|
530
|
+
try {
|
|
531
|
+
declared = readDeclaration(workingTree(root)).members;
|
|
532
|
+
} catch (err) {
|
|
533
|
+
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
534
|
+
}
|
|
535
|
+
const hit = declared.find((m) => m.dir !== "." && (m.name === scopeArg || scopeKey(m.dir) === scope));
|
|
536
|
+
if (hit?.kind === "workspace") {
|
|
537
|
+
// A nested workspace upgrades itself; the outer one never writes inside it (D2, #2551).
|
|
538
|
+
throw new UpgradeError(`member "${hit.name}" is a nested workspace, which upgrades itself: run chant workspace upgrade in ${hit.dir}`);
|
|
539
|
+
}
|
|
540
|
+
if (hit) {
|
|
541
|
+
const memberRoot = join(root, hit.dir);
|
|
542
|
+
if (readLock(memberRoot)) return { root: memberRoot, scope: ".", member: hit.name };
|
|
543
|
+
throw new UpgradeError(
|
|
544
|
+
`member "${hit.name}" (${hit.dir}) has no ${LOCK_FILE}, so it was not made from a template and has nothing to upgrade. \`chant init --from <template> ${hit.dir}\` writes one; \`chant workspace adopt-lineage\` is for an older member.`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return { root, scope };
|
|
549
|
+
}
|
|
550
|
+
|
|
489
551
|
/**
|
|
490
552
|
* Stage an upgrade: steps 1 to 5 and the digest of step 6. Nothing in the
|
|
491
553
|
* project's tree changes. The caller decides the gate, then applies the patch
|
|
@@ -493,8 +555,7 @@ function chantMemberDirs(dir: string): string[] {
|
|
|
493
555
|
* and always calls `dispose()`.
|
|
494
556
|
*/
|
|
495
557
|
export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgrade> {
|
|
496
|
-
const root = resolve(options.root);
|
|
497
|
-
const scope = scopeKey(options.scope ?? ".");
|
|
558
|
+
const { root, scope, member } = resolveUpgradeTarget(resolve(options.root), options.scope);
|
|
498
559
|
const lock = readLock(root);
|
|
499
560
|
if (!lock) throw new UpgradeError(`no ${LOCK_FILE} in ${root}`);
|
|
500
561
|
const lineage = lock.scopes[scope];
|
|
@@ -534,16 +595,29 @@ export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgra
|
|
|
534
595
|
);
|
|
535
596
|
}
|
|
536
597
|
|
|
598
|
+
// A move to another template (#2551): a git scope only, and to a named ref.
|
|
599
|
+
let onto: Onto | undefined;
|
|
600
|
+
if (options.source !== undefined) {
|
|
601
|
+
const spec = parseTemplateSource(options.source, root, "--source");
|
|
602
|
+
if (spec.ref !== undefined) throw new UpgradeError(`--source ${options.source}: name the version with --to, not "@${spec.ref}"`);
|
|
603
|
+
if (options.to === undefined) throw new UpgradeError("--source moves the scope to another template, so it needs --to <ref>: a version of that template");
|
|
604
|
+
if (lineage.source.type !== "git") {
|
|
605
|
+
throw new UpgradeError(`scope "${scope}" has a ${lineage.source.type} source; only a git scope moves to another template. A directory scope moves onto git with chant workspace adopt-lineage first.`);
|
|
606
|
+
}
|
|
607
|
+
onto = { url: spec.url, repo: spec.repo, ...(spec.member ? { path: spec.member } : {}), id: spec.id };
|
|
608
|
+
}
|
|
609
|
+
const switching = onto !== undefined && onto.id !== lineage.template;
|
|
610
|
+
|
|
537
611
|
// 1. Fetch. A directory source has no ref: `--to` names the directory.
|
|
538
612
|
const fromDir = lineage.source.type === "dir";
|
|
539
613
|
const ref = fromDir ? undefined : options.to ?? lineage.ref;
|
|
540
|
-
const upstream = await fetchUpstream(root, lineage, fromDir ? options.to : ref);
|
|
614
|
+
const upstream = await fetchUpstream(root, lineage, fromDir ? options.to : ref, onto);
|
|
541
615
|
|
|
542
616
|
// 2. The merge base, offline from here on.
|
|
543
617
|
const base = rebuildBase(join(root, scope), lineage, upstream.base);
|
|
544
618
|
|
|
545
619
|
// Plan the chain before anything is staged, so a gap refuses early.
|
|
546
|
-
const plan = lineage.kind === "template" ? planMigrations(lineage, ref, upstream.migrations) : { chain: [] };
|
|
620
|
+
const plan = lineage.kind === "template" ? planMigrations(lineage, ref, upstream.migrations, onto?.id) : { chain: [] };
|
|
547
621
|
assertCodeAllowed(plan.chain, !!options.allowCode);
|
|
548
622
|
|
|
549
623
|
// 3. The worktree, inside the project so the project's packages resolve.
|
|
@@ -613,6 +687,10 @@ export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgra
|
|
|
613
687
|
}
|
|
614
688
|
if (upstream.commit) staged.address = { ...staged.address!, commit: upstream.commit, tree: upstream.tree };
|
|
615
689
|
if (upstream.source) staged.source = upstream.source;
|
|
690
|
+
if (onto) {
|
|
691
|
+
staged.template = onto.id;
|
|
692
|
+
staged.source = { type: "git", repo: onto.repo, url: portableUrl(onto.url, root), ...(onto.path ? { path: onto.path } : {}) };
|
|
693
|
+
}
|
|
616
694
|
writeLock(worktreeProject, next);
|
|
617
695
|
const lockText = renderLock(next);
|
|
618
696
|
|
|
@@ -621,10 +699,23 @@ export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgra
|
|
|
621
699
|
if (!lockTracked) gitOut(worktree, ["rm", "--cached", "-q", "--ignore-unmatch", "--", lockRepoPath]);
|
|
622
700
|
const patch = gitOut(worktree, ["diff", "--cached", "--binary", "--full-index", "--no-color", "--no-ext-diff", head]);
|
|
623
701
|
const changedPaths = gitOut(worktree, ["diff", "--cached", "--name-only", "-z", head]).split("\0").filter(Boolean).sort();
|
|
702
|
+
// An upgrade writes inside its scope and the lock that records it, and nowhere else.
|
|
703
|
+
const outside = changedPaths.filter((p) => scopeRepoPath !== "." && p !== lockRepoPath && p !== scopeRepoPath && !p.startsWith(`${scopeRepoPath}/`));
|
|
704
|
+
if (outside.length > 0) {
|
|
705
|
+
throw new UpgradeError(`the upgrade would change ${outside.slice(0, 5).join(", ")}${outside.length > 5 ? ", ..." : ""}, outside the scope "${scopeRepoPath}". An upgrade writes only inside its own scope.`);
|
|
706
|
+
}
|
|
624
707
|
const lockChanged = !lockTracked && lockText !== readFileSync(join(root, LOCK_FILE), "utf-8");
|
|
625
708
|
const changed = patch.length > 0 || lockChanged;
|
|
626
709
|
const digest = computePlanDigest(UPGRADE_PLAN_KIND, lockTracked ? { scope, patch } : { scope, patch, lock: lockText });
|
|
627
710
|
|
|
711
|
+
// The outer workspace never writes inside a nested one (D2, #2551): that one upgrades itself.
|
|
712
|
+
const inNested = pathsInsideNested(worktreeProject, projectRel, changedPaths);
|
|
713
|
+
if (inNested.length > 0) {
|
|
714
|
+
throw new UpgradeError(
|
|
715
|
+
`the upgrade would change files inside the nested workspace ${inNested[0].member} (${inNested.slice(0, 5).map((p) => p.path).join(", ")}). A nested workspace upgrades itself with its own chant workspace upgrade, and the outer one never writes inside it.`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
628
719
|
// Governance: decided by the rules at HEAD, never by the upgraded files.
|
|
629
720
|
const governed = changedPaths.filter(isGovernancePath);
|
|
630
721
|
let governance: GovernanceChange | null = null;
|
|
@@ -682,7 +773,10 @@ export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgra
|
|
|
682
773
|
|
|
683
774
|
return {
|
|
684
775
|
scope,
|
|
776
|
+
...(member !== undefined ? { member } : {}),
|
|
777
|
+
gate: member ?? scope,
|
|
685
778
|
template: lineage.template,
|
|
779
|
+
...(switching ? { switchedTo: onto!.id } : {}),
|
|
686
780
|
kind: lineage.kind,
|
|
687
781
|
from: fromDir ? sourcePin(lineage.source) : lineage.ref ?? null,
|
|
688
782
|
to: fromDir ? sourcePin(staged.source) : staged.ref ?? null,
|
|
@@ -781,7 +875,8 @@ export function commitStagedUpgrade(staged: StagedUpgrade, branch: string, messa
|
|
|
781
875
|
export function describeStaged(staged: StagedUpgrade): string[] {
|
|
782
876
|
const lines: string[] = [];
|
|
783
877
|
const at = (p: string) => (staged.scope === "." ? p : `${staged.scope}/${p}`);
|
|
784
|
-
lines.push(`${staged.
|
|
878
|
+
lines.push(`${staged.gate} ${staged.template} ${staged.from ?? "(no ref)"} -> ${staged.to ?? "(no ref)"}`);
|
|
879
|
+
if (staged.switchedTo) lines.push(` template: ${staged.template} -> ${staged.switchedTo}`);
|
|
785
880
|
const chantIds = new Set(staged.chantMigrations.map((m) => m.id));
|
|
786
881
|
for (const id of staged.migrations) if (!chantIds.has(id)) lines.push(` migration: ${id}`);
|
|
787
882
|
for (const m of staged.chantMigrations) {
|