@intentius/chant 0.80.0 → 0.82.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/audit/discover.d.ts +8 -16
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +4 -5
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +25 -9
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +7 -4
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/run-generate.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +12 -7
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +32 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/promote.d.ts +60 -7
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +32 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +10 -5
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +20 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +4 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +51 -6
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon.d.ts +58 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +41 -8
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +18 -2
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +35 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +144 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +271 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +152 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +28 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +2 -1
- package/src/audit/discover.ts +66 -78
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +4 -9
- package/src/audit/terraform-state.ts +4 -5
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +89 -60
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +18 -0
- package/src/cli/commands/import-agents.ts +6 -2
- package/src/cli/commands/init.ts +22 -3
- package/src/cli/commands/lint.test.ts +17 -23
- package/src/cli/commands/lint.ts +24 -84
- package/src/cli/commands/update.ts +13 -4
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +5 -3
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/init.ts +58 -1
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +37 -9
- package/src/cli/handlers/promote.test.ts +118 -2
- package/src/cli/handlers/promote.ts +82 -16
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +6 -8
- package/src/cli/handlers/run-generate.ts +36 -1
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +28 -5
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +105 -1
- package/src/cli/main.ts +232 -17
- package/src/cli/plugins.ts +17 -17
- package/src/cli/registry.ts +32 -0
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/cli-support.ts +6 -1
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +13 -68
- package/src/components/driver.test.ts +78 -1
- package/src/components/driver.ts +43 -7
- package/src/components/gate-plan.ts +53 -0
- package/src/components/promote.test.ts +276 -2
- package/src/components/promote.ts +119 -15
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +2 -2
- package/src/config.ts +48 -15
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +10 -10
- package/src/discovery/files.ts +25 -81
- package/src/discovery/fold-import.ts +49 -5
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +4 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.ts +108 -10
- package/src/lexicon.ts +79 -1
- package/src/lifecycle/build-ledger-store.ts +11 -10
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +115 -23
- package/src/lifecycle/legacy-digest.ts +60 -19
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +23 -7
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -27
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +158 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +134 -0
- package/src/workspace/lineage-init-dir.test.ts +243 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +560 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +365 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +723 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/records-cli.ts +86 -14
- package/src/workspace/records-contract.test.ts +5 -0
- package/src/workspace/records.schema.json +32 -2
- package/src/workspace/records.ts +3 -2
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
- package/dist/components/unbound-gate-approval.d.ts +0 -24
- package/dist/components/unbound-gate-approval.d.ts.map +0 -1
- package/dist/discovery/convergence.d.ts +0 -97
- package/dist/discovery/convergence.d.ts.map +0 -1
- package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
- package/src/components/unbound-gate-approval.test.ts +0 -82
- package/src/components/unbound-gate-approval.ts +0 -51
- package/src/discovery/convergence.test.ts +0 -231
- package/src/discovery/convergence.ts +0 -399
- package/src/op/discover-convergence.test.ts +0 -70
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lineage lock at init (#2540, ws-047): `chant init --from <repo>@<ref>`
|
|
3
|
+
* and `chant init --template <name>` both leave `.chant/workspace.lock.json`
|
|
4
|
+
* with one lineage, scope `"."`, so a project made from a template can be
|
|
5
|
+
* upgraded later without first running adopt-lineage.
|
|
6
|
+
*
|
|
7
|
+
* `--from <repo>@<ref>` fetches with `git`: the one network step, listed in
|
|
8
|
+
* the egress catalogue (`test/egress-catalogue.ts`). The commit and the tree
|
|
9
|
+
* of the chosen directory become the content address.
|
|
10
|
+
*
|
|
11
|
+
* `--from <dir>[#<member>]` copies a template directory already on disk
|
|
12
|
+
* (#2647), for a host that carries the template as plain files with no
|
|
13
|
+
* `.git` and no way to reach its repository. It reaches no network, and the
|
|
14
|
+
* digest of the copied files is the whole content address.
|
|
15
|
+
*
|
|
16
|
+
* Plain `chant init` without `--template` writes no lock. That keeps the
|
|
17
|
+
* output of an existing command unchanged (#2525 rule 2), and this module
|
|
18
|
+
* never loads for it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
22
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
26
|
+
import {
|
|
27
|
+
LOCK_FILE,
|
|
28
|
+
LockError,
|
|
29
|
+
contentDigest,
|
|
30
|
+
declaredFilesAt,
|
|
31
|
+
emptyLock,
|
|
32
|
+
fileEntries,
|
|
33
|
+
readLock,
|
|
34
|
+
writeLock,
|
|
35
|
+
type Lineage,
|
|
36
|
+
} from "./lineage-lock";
|
|
37
|
+
import { MIGRATIONS_DIR } from "./lineage-migrations";
|
|
38
|
+
import { TEMPLATE_MANIFEST, readManifest, resolveParameters, substituteParameters } from "./template-manifest";
|
|
39
|
+
|
|
40
|
+
// ── The template spec ────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export interface TemplateSpec {
|
|
43
|
+
/** The repository as written. */
|
|
44
|
+
repo: string;
|
|
45
|
+
/** What `git fetch` is given: a URL, or an absolute local path. */
|
|
46
|
+
url: string;
|
|
47
|
+
ref: string;
|
|
48
|
+
/** The directory inside the repository to instantiate (`#<member>`). */
|
|
49
|
+
member?: string;
|
|
50
|
+
/** Stable identity: `<host>/<path>` for a remote, the path as written for a local repo, plus `#<member>`. */
|
|
51
|
+
id: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Split `<spec>[#<member>]`, refusing a member that leaves the template. */
|
|
55
|
+
function splitMember(spec: string, where: string): { rest: string; member?: string } {
|
|
56
|
+
let rest = spec.trim();
|
|
57
|
+
let member: string | undefined;
|
|
58
|
+
const hash = rest.lastIndexOf("#");
|
|
59
|
+
if (hash >= 0) {
|
|
60
|
+
member = rest.slice(hash + 1).replace(/^\/+|\/+$/g, "");
|
|
61
|
+
rest = rest.slice(0, hash);
|
|
62
|
+
if (!member || posix.normalize(member).startsWith("..")) throw new LockError(`--from ${spec}: "#${member}" is not a directory in ${where}`);
|
|
63
|
+
member = posix.normalize(member);
|
|
64
|
+
}
|
|
65
|
+
return { rest, member };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse `<repo>@<ref>[#<member>]`. `<repo>` is a git URL, an scp-style
|
|
70
|
+
* `git@host:path`, a local path, or `owner/name` for a GitHub repository.
|
|
71
|
+
*/
|
|
72
|
+
export function parseTemplateSpec(spec: string, cwd: string = process.cwd()): TemplateSpec {
|
|
73
|
+
const { rest, member } = splitMember(spec, "the repository");
|
|
74
|
+
const at = rest.lastIndexOf("@");
|
|
75
|
+
const lastSep = Math.max(rest.lastIndexOf("/"), rest.lastIndexOf(":"));
|
|
76
|
+
if (at <= 0 || at < lastSep || at === rest.length - 1) {
|
|
77
|
+
throw new LockError(`--from ${spec}: expected <repo>@<ref>[#<member>], e.g. acme/starter@v1.2.0, or an existing directory`);
|
|
78
|
+
}
|
|
79
|
+
const repo = rest.slice(0, at);
|
|
80
|
+
const ref = rest.slice(at + 1);
|
|
81
|
+
|
|
82
|
+
let url: string;
|
|
83
|
+
let repoId: string;
|
|
84
|
+
const local = resolve(cwd, repo);
|
|
85
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(repo)) {
|
|
86
|
+
url = repo;
|
|
87
|
+
repoId = repo.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/^[^@/]*@/, "").replace(/\.git$/, "");
|
|
88
|
+
} else if (/^[^/@\s]+@[^:\s]+:/.test(repo)) {
|
|
89
|
+
url = repo;
|
|
90
|
+
repoId = repo.replace(/^[^@]+@/, "").replace(":", "/").replace(/\.git$/, "");
|
|
91
|
+
} else if (existsSync(local)) {
|
|
92
|
+
url = local;
|
|
93
|
+
repoId = repo;
|
|
94
|
+
} else if (/^[\w.-]+\/[\w.-]+$/.test(repo)) {
|
|
95
|
+
url = `https://github.com/${repo}.git`;
|
|
96
|
+
repoId = `github.com/${repo.replace(/\.git$/, "")}`;
|
|
97
|
+
} else if (/^[\w.-]+\.[a-z]{2,}\/[\w./-]+$/i.test(repo)) {
|
|
98
|
+
url = `https://${repo}${repo.endsWith(".git") ? "" : ".git"}`;
|
|
99
|
+
repoId = repo.replace(/\.git$/, "");
|
|
100
|
+
} else {
|
|
101
|
+
throw new LockError(`--from ${spec}: "${repo}" is not a URL, a local repository or owner/name`);
|
|
102
|
+
}
|
|
103
|
+
return { repo, url, ref, member, id: member ? `${repoId}#${member}` : repoId };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Fetch ────────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export interface FetchedTemplate {
|
|
109
|
+
commit: string;
|
|
110
|
+
tree: string;
|
|
111
|
+
/** Files under the chosen directory, relative to it. */
|
|
112
|
+
files: Map<string, { data: Buffer; executable: boolean }>;
|
|
113
|
+
/** Paths that were not copied, with the reason. */
|
|
114
|
+
skipped: Array<{ path: string; reason: string }>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function git(cwd: string, args: string[]): string {
|
|
118
|
+
return execFileSync("git", args, {
|
|
119
|
+
cwd,
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
122
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
123
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
124
|
+
}).trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Read the files of `member` (or the whole tree) at `commit` from a scratch
|
|
129
|
+
* repository that already holds the commit. Symbolic links, submodules and
|
|
130
|
+
* the template's own lineage lock are skipped, with the reason.
|
|
131
|
+
*/
|
|
132
|
+
export function readTemplateTree(
|
|
133
|
+
scratch: string,
|
|
134
|
+
commit: string,
|
|
135
|
+
member: string | undefined,
|
|
136
|
+
label: string,
|
|
137
|
+
): { tree: string; files: FetchedTemplate["files"]; skipped: FetchedTemplate["skipped"] } {
|
|
138
|
+
let tree: string;
|
|
139
|
+
try {
|
|
140
|
+
tree = git(scratch, ["rev-parse", member ? `${commit}:${member}` : `${commit}^{tree}`]);
|
|
141
|
+
if (git(scratch, ["cat-file", "-t", tree]) !== "tree") throw new Error("not a tree");
|
|
142
|
+
} catch {
|
|
143
|
+
throw new LockError(`${label} has no directory ${member}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const files: FetchedTemplate["files"] = new Map();
|
|
147
|
+
const skipped: FetchedTemplate["skipped"] = [];
|
|
148
|
+
const listing = execFileSync("git", ["ls-tree", "-r", "-z", tree], { cwd: scratch, maxBuffer: 256 * 1024 * 1024 }).toString("utf-8");
|
|
149
|
+
for (const row of listing.split("\0")) {
|
|
150
|
+
if (!row) continue;
|
|
151
|
+
const tab = row.indexOf("\t");
|
|
152
|
+
const [mode, type, sha] = row.slice(0, tab).split(" ");
|
|
153
|
+
const path = row.slice(tab + 1);
|
|
154
|
+
if (path === LOCK_FILE) {
|
|
155
|
+
skipped.push({ path, reason: "the template's own lineage lock" });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (type !== "blob") {
|
|
159
|
+
skipped.push({ path, reason: `a ${type === "commit" ? "submodule" : type}` });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (mode === "120000") {
|
|
163
|
+
skipped.push({ path, reason: "a symbolic link" });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const data = execFileSync("git", ["cat-file", "blob", sha], { cwd: scratch, maxBuffer: 256 * 1024 * 1024 });
|
|
167
|
+
files.set(path, { data, executable: mode === "100755" });
|
|
168
|
+
}
|
|
169
|
+
return { tree, files, skipped };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Fetch one commit of the template repository into a scratch repository and
|
|
174
|
+
* read the files of the chosen directory from it. The template's own lock is
|
|
175
|
+
* never copied: it describes the template's lineage, not the new project's.
|
|
176
|
+
*/
|
|
177
|
+
export function fetchTemplate(spec: TemplateSpec): FetchedTemplate {
|
|
178
|
+
const scratch = mkdtempSync(join(tmpdir(), "chant-init-from-"));
|
|
179
|
+
try {
|
|
180
|
+
git(scratch, ["init", "-q"]);
|
|
181
|
+
try {
|
|
182
|
+
// The one network step of `chant init --from`, catalogued in test/egress-catalogue.ts.
|
|
183
|
+
git(scratch, ["fetch", "-q", "--depth", "1", spec.url, spec.ref]);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
const stderr = (err as { stderr?: string }).stderr?.toString().trim();
|
|
186
|
+
throw new LockError(`could not fetch ${spec.repo}@${spec.ref}${stderr ? `: ${stderr}` : ""}`);
|
|
187
|
+
}
|
|
188
|
+
const commit = git(scratch, ["rev-parse", "FETCH_HEAD^{commit}"]);
|
|
189
|
+
return { commit, ...readTemplateTree(scratch, commit, spec.member, `${spec.repo}@${spec.ref}`) };
|
|
190
|
+
} finally {
|
|
191
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── A template directory on disk (#2647) ─────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
export interface DirTemplateSpec {
|
|
198
|
+
kind: "dir";
|
|
199
|
+
/** The directory as written, without `#<member>`. */
|
|
200
|
+
path: string;
|
|
201
|
+
/** The directory, absolute. */
|
|
202
|
+
abs: string;
|
|
203
|
+
/** The directory inside it to instantiate (`#<member>`). */
|
|
204
|
+
member?: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parse `<path>[#<member>]` when `<path>` is an existing directory, or return
|
|
209
|
+
* null for the git form. A spec without `@` that names no directory is
|
|
210
|
+
* refused here, since it cannot be a `<repo>@<ref>` either. A directory whose
|
|
211
|
+
* name happens to parse as `<repo>@<ref>` is a directory: what is on disk wins.
|
|
212
|
+
*/
|
|
213
|
+
export function parseDirSpec(spec: string, cwd: string = process.cwd()): DirTemplateSpec | null {
|
|
214
|
+
const { rest, member } = splitMember(spec, "the directory");
|
|
215
|
+
if (!rest) return null;
|
|
216
|
+
const abs = resolve(cwd, rest);
|
|
217
|
+
let isDir = false;
|
|
218
|
+
try {
|
|
219
|
+
isDir = statSync(abs).isDirectory();
|
|
220
|
+
} catch {
|
|
221
|
+
isDir = false;
|
|
222
|
+
}
|
|
223
|
+
if (isDir) return { kind: "dir", path: rest, abs, ...(member ? { member } : {}) };
|
|
224
|
+
if (rest.includes("@")) return null;
|
|
225
|
+
if (existsSync(abs)) throw new LockError(`--from ${spec}: ${rest} is not a directory`);
|
|
226
|
+
throw new LockError(`--from ${spec}: no directory ${rest}, and not <repo>@<ref>[#<member>] (e.g. acme/starter@v1.2.0)`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The template's directory as `#<member>` names it, relative to its root, for messages. */
|
|
230
|
+
export function dirLabel(path: string, member?: string): string {
|
|
231
|
+
return member ? `${path}#${member}` : path;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The git work tree `dir` sits in, or null when it is in none, git is not installed, or the work tree ignores `dir`. */
|
|
235
|
+
function workTreeOf(dir: string): string | null {
|
|
236
|
+
try {
|
|
237
|
+
if (git(dir, ["rev-parse", "--is-inside-work-tree"]) !== "true") return null;
|
|
238
|
+
const top = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
239
|
+
const rel = relative(top, dir).split(sep).join("/");
|
|
240
|
+
if (rel) {
|
|
241
|
+
try {
|
|
242
|
+
// Exit 0: ignored. A directory the checkout ignores is copied whole.
|
|
243
|
+
git(top, ["check-ignore", "-q", "--", `${rel}/`]);
|
|
244
|
+
return null;
|
|
245
|
+
} catch {
|
|
246
|
+
// Not ignored.
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return top;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Every path under `dir`, relative and posix, except `.git`. Symbolic links are listed, not followed. */
|
|
256
|
+
function walk(dir: string, prefix = ""): string[] {
|
|
257
|
+
const out: string[] = [];
|
|
258
|
+
for (const entry of readdirSync(join(dir, prefix), { withFileTypes: true })) {
|
|
259
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
260
|
+
if (entry.name === ".git") continue;
|
|
261
|
+
if (entry.isDirectory()) out.push(...walk(dir, rel));
|
|
262
|
+
else out.push(rel);
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Read the files of a template directory on disk, the way
|
|
269
|
+
* {@link readTemplateTree} reads a commit: symbolic links, submodules and the
|
|
270
|
+
* template's own lineage lock are skipped with the reason, and so is
|
|
271
|
+
* `node_modules`. Inside a git checkout the files are the ones git would
|
|
272
|
+
* commit, tracked or untracked but not ignored. Elsewhere, or when git is not
|
|
273
|
+
* installed, every file is copied. The executable bit is the owner's.
|
|
274
|
+
*/
|
|
275
|
+
export function readTemplateDir(abs: string, member: string | undefined, label: string): Omit<FetchedTemplate, "commit" | "tree"> {
|
|
276
|
+
const dir = member ? join(abs, member) : abs;
|
|
277
|
+
let isDir = false;
|
|
278
|
+
try {
|
|
279
|
+
isDir = statSync(dir).isDirectory();
|
|
280
|
+
} catch {
|
|
281
|
+
isDir = false;
|
|
282
|
+
}
|
|
283
|
+
if (!isDir) throw new LockError(`${label} has no directory ${member ?? ""}`.trimEnd());
|
|
284
|
+
|
|
285
|
+
let paths: string[];
|
|
286
|
+
if (workTreeOf(dir)) {
|
|
287
|
+
const listing = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."], {
|
|
288
|
+
cwd: dir,
|
|
289
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
290
|
+
}).toString("utf-8");
|
|
291
|
+
paths = [...new Set(listing.split("\0").filter(Boolean))];
|
|
292
|
+
} else {
|
|
293
|
+
paths = walk(dir);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const files: FetchedTemplate["files"] = new Map();
|
|
297
|
+
const skipped: FetchedTemplate["skipped"] = [];
|
|
298
|
+
const skippedModules = new Set<string>();
|
|
299
|
+
for (const path of paths.sort()) {
|
|
300
|
+
const segments = path.split("/");
|
|
301
|
+
const nm = segments.indexOf("node_modules");
|
|
302
|
+
if (nm >= 0) {
|
|
303
|
+
const at = segments.slice(0, nm + 1).join("/");
|
|
304
|
+
if (!skippedModules.has(at)) {
|
|
305
|
+
skippedModules.add(at);
|
|
306
|
+
skipped.push({ path: at, reason: "node_modules" });
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (path === LOCK_FILE) {
|
|
311
|
+
skipped.push({ path, reason: "the template's own lineage lock" });
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
let st;
|
|
315
|
+
try {
|
|
316
|
+
st = lstatSync(join(dir, path));
|
|
317
|
+
} catch {
|
|
318
|
+
// Tracked but deleted from the working files: not part of the template on disk.
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (st.isSymbolicLink()) {
|
|
322
|
+
skipped.push({ path, reason: "a symbolic link" });
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (st.isDirectory()) {
|
|
326
|
+
skipped.push({ path, reason: "a submodule" });
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (!st.isFile()) {
|
|
330
|
+
skipped.push({ path, reason: "not a regular file" });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
files.set(path, { data: readFileSync(join(dir, path)), executable: (st.mode & 0o100) !== 0 });
|
|
334
|
+
}
|
|
335
|
+
return { files, skipped };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* How a directory source is recorded: an absolute path as given, a relative
|
|
340
|
+
* one re-expressed from the project, so it resolves from the lock's directory.
|
|
341
|
+
*/
|
|
342
|
+
export function recordedDirPath(given: string, abs: string, projectDir: string): string {
|
|
343
|
+
if (isAbsolute(given)) return abs;
|
|
344
|
+
return portableUrl(abs, projectDir);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── init --from ──────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
export interface InitFromOptions {
|
|
350
|
+
/** `<repo>@<ref>[#<member>]`, or `<dir>[#<member>]` for a directory on disk (#2647). */
|
|
351
|
+
from: string;
|
|
352
|
+
/** Target directory (defaults to cwd). */
|
|
353
|
+
path?: string;
|
|
354
|
+
force?: boolean;
|
|
355
|
+
/** `--param name=value` values, parsed. Checked against the template's `chant.template.json` (#2627). */
|
|
356
|
+
params?: Record<string, string>;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export interface InitFromResult {
|
|
360
|
+
success: boolean;
|
|
361
|
+
createdFiles: string[];
|
|
362
|
+
warnings: string[];
|
|
363
|
+
error?: string;
|
|
364
|
+
/** The git form's spec. */
|
|
365
|
+
spec?: TemplateSpec;
|
|
366
|
+
/** The directory form's spec (#2647). */
|
|
367
|
+
dir?: DirTemplateSpec;
|
|
368
|
+
commit?: string;
|
|
369
|
+
/** The lineage's `template` id. */
|
|
370
|
+
template?: string;
|
|
371
|
+
/** The parameter values used, recorded in the lock. */
|
|
372
|
+
parameters?: Record<string, string>;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** `chant init --from <repo>@<ref>[#<member>] [path]`, or `--from <dir>[#<member>]`. */
|
|
376
|
+
export async function initFromCommand(options: InitFromOptions): Promise<InitFromResult> {
|
|
377
|
+
const targetDir = resolve(options.path ?? ".");
|
|
378
|
+
const warnings: string[] = [];
|
|
379
|
+
const createdFiles: string[] = [];
|
|
380
|
+
|
|
381
|
+
let spec: TemplateSpec | undefined;
|
|
382
|
+
let dir: DirTemplateSpec | undefined;
|
|
383
|
+
try {
|
|
384
|
+
dir = parseDirSpec(options.from) ?? undefined;
|
|
385
|
+
if (!dir) spec = parseTemplateSpec(options.from);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
return { success: false, createdFiles, warnings, error: (err as Error).message };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (existsSync(targetDir)) {
|
|
391
|
+
const visible = readdirSync(targetDir).filter((f) => !f.startsWith("."));
|
|
392
|
+
if (visible.length > 0 && !options.force) {
|
|
393
|
+
return { success: false, createdFiles, warnings, error: "Directory is not empty. Use --force to initialize anyway." };
|
|
394
|
+
}
|
|
395
|
+
if (visible.length > 0) warnings.push("Initializing in non-empty directory");
|
|
396
|
+
}
|
|
397
|
+
if (existsSync(join(targetDir, LOCK_FILE))) {
|
|
398
|
+
return { success: false, createdFiles, warnings, error: `${LOCK_FILE} already exists; this directory already has a lineage` };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let fetched: Omit<FetchedTemplate, "commit" | "tree"> & { commit?: string; tree?: string };
|
|
402
|
+
try {
|
|
403
|
+
// Every file is read before anything is written, so a target inside the template directory is safe.
|
|
404
|
+
fetched = dir ? readTemplateDir(dir.abs, dir.member, dir.path) : fetchTemplate(spec!);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
return { success: false, createdFiles, warnings, error: (err as Error).message };
|
|
407
|
+
}
|
|
408
|
+
for (const s of fetched.skipped) {
|
|
409
|
+
if (s.path !== LOCK_FILE) warnings.push(`${s.path} is ${s.reason}, not copied`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Parameters (#2627): checked and substituted before anything is written,
|
|
413
|
+
// so a refused --param leaves the target untouched.
|
|
414
|
+
let parameters: Record<string, string>;
|
|
415
|
+
let contents: Map<string, Buffer>;
|
|
416
|
+
try {
|
|
417
|
+
const raw = new Map([...fetched.files].map(([path, f]) => [path, f.data]));
|
|
418
|
+
const manifest = readManifest(raw);
|
|
419
|
+
parameters = resolveParameters(manifest, options.params ?? {});
|
|
420
|
+
contents = substituteParameters(raw, manifest, parameters);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
return { success: false, createdFiles, warnings, error: (err as Error).message };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
mkdirSync(targetDir, { recursive: true });
|
|
426
|
+
const written = new Map<string, Buffer>();
|
|
427
|
+
for (const [path, file] of [...fetched.files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
428
|
+
// The template's migrations (#2550) and its manifest (#2627) are for chant, not part of a project.
|
|
429
|
+
if (path.startsWith(`${MIGRATIONS_DIR}/`) || path === TEMPLATE_MANIFEST) continue;
|
|
430
|
+
const data = contents.get(path)!;
|
|
431
|
+
const abs = join(targetDir, path);
|
|
432
|
+
if (existsSync(abs)) {
|
|
433
|
+
warnings.push(`${path} already exists, skipping`);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
437
|
+
writeFileSync(abs, data);
|
|
438
|
+
if (file.executable) chmodSync(abs, 0o755);
|
|
439
|
+
written.set(path, data);
|
|
440
|
+
createdFiles.push(path);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const common = {
|
|
444
|
+
parameters,
|
|
445
|
+
migrations: [],
|
|
446
|
+
files: fileEntries(written, declaredFilesAt(targetDir)),
|
|
447
|
+
manualSteps: [],
|
|
448
|
+
};
|
|
449
|
+
let lineage: Lineage;
|
|
450
|
+
if (dir) {
|
|
451
|
+
const path = recordedDirPath(dir.path, dir.abs, targetDir);
|
|
452
|
+
lineage = {
|
|
453
|
+
kind: "template",
|
|
454
|
+
template: `dir:${dirLabel(path, dir.member)}`,
|
|
455
|
+
source: { type: "dir", path, ...(dir.member ? { member: dir.member } : {}) },
|
|
456
|
+
address: { digest: contentDigest(written) },
|
|
457
|
+
...common,
|
|
458
|
+
};
|
|
459
|
+
} else {
|
|
460
|
+
lineage = {
|
|
461
|
+
kind: "template",
|
|
462
|
+
template: spec!.id,
|
|
463
|
+
source: { type: "git", repo: spec!.repo, url: portableUrl(spec!.url, targetDir), ...(spec!.member ? { path: spec!.member } : {}) },
|
|
464
|
+
ref: spec!.ref,
|
|
465
|
+
address: { digest: contentDigest(written), commit: fetched.commit, tree: fetched.tree },
|
|
466
|
+
...common,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const lock = emptyLock();
|
|
470
|
+
lock.scopes["."] = lineage;
|
|
471
|
+
writeLock(targetDir, lock);
|
|
472
|
+
createdFiles.push(LOCK_FILE);
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
success: true,
|
|
476
|
+
createdFiles,
|
|
477
|
+
warnings,
|
|
478
|
+
...(spec ? { spec, commit: fetched.commit } : {}),
|
|
479
|
+
...(dir ? { dir } : {}),
|
|
480
|
+
template: lineage.template,
|
|
481
|
+
parameters,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** A local repository is recorded relative to the project, so the lock does not name this machine's paths. */
|
|
486
|
+
function portableUrl(url: string, targetDir: string): string {
|
|
487
|
+
if (!isAbsolute(url)) return url;
|
|
488
|
+
const rel = relative(targetDir, url).split(sep).join("/");
|
|
489
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ── init --template ──────────────────────────────────────────────────────────
|
|
493
|
+
|
|
494
|
+
/** The installed version of a lexicon package, or null when it cannot be found. */
|
|
495
|
+
function lexiconPackageVersion(pkg: string, from: string): string | null {
|
|
496
|
+
for (const base of [join(from, "package.json"), import.meta.url]) {
|
|
497
|
+
try {
|
|
498
|
+
const require = createRequire(base);
|
|
499
|
+
let dir = dirname(require.resolve(pkg));
|
|
500
|
+
for (let i = 0; i < 6; i++) {
|
|
501
|
+
const candidate = join(dir, "package.json");
|
|
502
|
+
if (existsSync(candidate)) {
|
|
503
|
+
const json = JSON.parse(readFileSync(candidate, "utf-8")) as { name?: string; version?: string };
|
|
504
|
+
if (json.name === pkg) return json.version ?? null;
|
|
505
|
+
}
|
|
506
|
+
dir = dirname(dir);
|
|
507
|
+
}
|
|
508
|
+
} catch {
|
|
509
|
+
// Not resolvable from here; try the next base.
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export interface TemplateLockInput {
|
|
516
|
+
targetDir: string;
|
|
517
|
+
lexicon: string;
|
|
518
|
+
template: string;
|
|
519
|
+
/** Files init wrote, relative to `targetDir`. */
|
|
520
|
+
createdFiles: string[];
|
|
521
|
+
chantVersion: string;
|
|
522
|
+
/** Set when the project's config declares the lexicon by path (#2520): the path, recorded instead of a package. */
|
|
523
|
+
lexiconModule?: string;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Record the lineage of a `chant init --template` project. Only files init
|
|
528
|
+
* actually wrote are listed; a file that already existed was not made from
|
|
529
|
+
* the template. `.chant/types/` is gitignored and left out (D14).
|
|
530
|
+
*
|
|
531
|
+
* Returns the lock's path relative to `targetDir`, or null when the target
|
|
532
|
+
* already has a lock (init never overwrites a lineage).
|
|
533
|
+
*/
|
|
534
|
+
export function writeTemplateLock(input: TemplateLockInput): string | null {
|
|
535
|
+
if (readLock(input.targetDir)) return null;
|
|
536
|
+
const files = new Map<string, Buffer>();
|
|
537
|
+
for (const rel of input.createdFiles) {
|
|
538
|
+
if (rel.startsWith(".chant/")) continue;
|
|
539
|
+
files.set(rel, readFileSync(join(input.targetDir, rel)));
|
|
540
|
+
}
|
|
541
|
+
const pkg = input.lexiconModule ?? `@intentius/chant-lexicon-${input.lexicon}`;
|
|
542
|
+
const lock = emptyLock();
|
|
543
|
+
lock.scopes["."] = {
|
|
544
|
+
kind: "template",
|
|
545
|
+
template: `lexicon:${input.lexicon}/${input.template}`,
|
|
546
|
+
source: { type: "lexicon", lexicon: input.lexicon, template: input.template },
|
|
547
|
+
address: {
|
|
548
|
+
digest: contentDigest(files),
|
|
549
|
+
package: pkg,
|
|
550
|
+
version: input.lexiconModule ? null : lexiconPackageVersion(pkg, input.targetDir),
|
|
551
|
+
chant: input.chantVersion,
|
|
552
|
+
},
|
|
553
|
+
parameters: {},
|
|
554
|
+
migrations: [],
|
|
555
|
+
files: fileEntries(files, declaredFilesAt(input.targetDir)),
|
|
556
|
+
manualSteps: [],
|
|
557
|
+
};
|
|
558
|
+
writeLock(input.targetDir, lock);
|
|
559
|
+
return LOCK_FILE;
|
|
560
|
+
}
|