@intentius/chant 0.124.31 → 0.124.33
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/ci-jobs.d.ts +4 -0
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +9 -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 +38 -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/lifecycle-health.d.ts +16 -0
- package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-health.js +150 -0
- package/dist/cli/handlers/lifecycle.d.ts +26 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +1 -1
- 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 +16 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +610 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +19 -2
- package/dist/cli/handlers/release.d.ts +16 -1
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +83 -11
- 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 +121 -33
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +37 -0
- package/dist/cli/registry.d.ts +29 -6
- 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/deploy-inputs.d.ts +91 -0
- package/dist/components/deploy-inputs.d.ts.map +1 -0
- package/dist/components/deploy-inputs.js +184 -0
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +56 -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/gate-plan.d.ts +8 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +6 -0
- package/dist/components/pr-pipeline.d.ts +98 -12
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +146 -23
- package/dist/components/pr-run.d.ts +128 -30
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/release-compile.d.ts +37 -2
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +64 -8
- package/dist/components/release-promote.d.ts +17 -3
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +79 -18
- package/dist/components/verbs/bom-writer.d.ts +1 -1
- package/dist/components/verbs/bom-writer.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
- package/dist/components/wave-plan.d.ts +15 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +44 -5
- 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/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +32 -7
- package/dist/health.d.ts +110 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +105 -0
- package/dist/health.schema.json +191 -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 +29 -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/merge-record.d.ts +104 -0
- package/dist/lifecycle/merge-record.d.ts.map +1 -0
- package/dist/lifecycle/merge-record.js +102 -0
- package/dist/lifecycle/publish-attachments.d.ts +8 -5
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
- package/dist/lifecycle/publish-attachments.js +27 -13
- package/dist/lifecycle/publish-npm.d.ts +3 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +4 -3
- package/dist/lifecycle/release-at.d.ts +40 -0
- package/dist/lifecycle/release-at.d.ts.map +1 -0
- package/dist/lifecycle/release-at.js +83 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +23 -12
- 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/release-publish.d.ts +28 -12
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +29 -17
- package/dist/lifecycle/release-store.d.ts +25 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +40 -13
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -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/lint/rules/op/index.d.ts +2 -1
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +4 -2
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
- package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
- 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/activity-contracts.d.ts +5 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +3 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/activities/shell.d.ts +38 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +89 -3
- package/dist/op/builders.d.ts +7 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +7 -0
- 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/runtime.d.ts +2 -0
- package/dist/op/runtime.d.ts.map +1 -1
- 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 +155 -7
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +137 -11
- package/dist/pr-report-url.d.ts +4 -4
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- package/dist/runner-start.d.ts +179 -0
- package/dist/runner-start.d.ts.map +1 -0
- package/dist/runner-start.js +277 -0
- 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 +238 -231
- 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/check.schema.json +70 -2
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/checks/security.d.ts +35 -0
- package/dist/workspace/checks/security.d.ts.map +1 -0
- package/dist/workspace/checks/security.js +305 -0
- package/dist/workspace/checks.d.ts +2 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +5 -0
- package/dist/workspace/ci-builds.d.ts +163 -0
- package/dist/workspace/ci-builds.d.ts.map +1 -0
- package/dist/workspace/ci-builds.js +315 -0
- package/dist/workspace/ci-green-cli.d.ts +2 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +25 -5
- package/dist/workspace/ci-green-forge.d.ts +18 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +12 -0
- package/dist/workspace/ci-green-workflow.d.ts +55 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +128 -1
- package/dist/workspace/ci-green.d.ts +117 -16
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +358 -43
- package/dist/workspace/ci-last-green.schema.json +49 -0
- package/dist/workspace/ci-list.schema.json +47 -7
- 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 +13 -1
- 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 +6 -4
- package/dist/workspace/ci-run.d.ts +85 -6
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +262 -64
- 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-runner-start.d.ts +77 -0
- package/dist/workspace/ci-runner-start.d.ts.map +1 -0
- package/dist/workspace/ci-runner-start.js +185 -0
- package/dist/workspace/ci-serve.d.ts +130 -11
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +323 -40
- package/dist/workspace/declaration.d.ts +25 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +14 -2
- package/dist/workspace/declaration.schema.json +30 -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 +8 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -1
- package/dist/workspace/ls.schema.json +9 -2
- package/dist/workspace/member-pipeline.d.ts +1 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +2 -0
- package/dist/workspace/merge-record.schema.json +49 -0
- 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 +75 -8
- package/dist/workspace/reason-codes.d.ts +18 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +20 -2
- package/dist/workspace/release-candidates.d.ts +129 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -0
- package/dist/workspace/release-candidates.js +233 -0
- package/dist/workspace/release-candidates.schema.json +353 -0
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/security.d.ts +40 -0
- package/dist/workspace/security.d.ts.map +1 -0
- package/dist/workspace/security.js +99 -0
- 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 +8 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
- package/src/ci-jobs.ts +11 -2
- 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 +105 -0
- package/src/cli/handlers/build.ts +35 -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/lifecycle-health.test.ts +123 -0
- package/src/cli/handlers/lifecycle-health.ts +157 -0
- package/src/cli/handlers/lifecycle.ts +1 -1
- 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-deploy-inputs.test.ts +209 -0
- package/src/cli/handlers/pr-record.test.ts +201 -0
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +156 -0
- package/src/cli/handlers/pr.ts +648 -33
- package/src/cli/handlers/promote.test.ts +43 -1
- package/src/cli/handlers/promote.ts +19 -3
- package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
- package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
- package/src/cli/handlers/release.e2e.test.ts +19 -28
- package/src/cli/handlers/release.ts +81 -11
- 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 +120 -33
- package/src/cli/parse-args.ts +28 -0
- package/src/cli/registry.ts +35 -8
- 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/deploy-inputs.test.ts +108 -0
- package/src/components/deploy-inputs.ts +225 -0
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +87 -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/gate-plan.ts +9 -0
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +184 -28
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +355 -76
- package/src/components/release-compile.test.ts +34 -0
- package/src/components/release-compile.ts +89 -8
- package/src/components/release-promote.test.ts +55 -2
- package/src/components/release-promote.ts +93 -23
- package/src/components/verbs/bom-writer.ts +1 -1
- package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
- package/src/components/wave-plan.ts +48 -8
- 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/graph-detail.test.ts +57 -0
- package/src/graph-detail.ts +33 -7
- package/src/health.schema.json +191 -0
- package/src/health.test.ts +62 -0
- package/src/health.ts +178 -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 +43 -1
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/merge-record.test.ts +111 -0
- package/src/lifecycle/merge-record.ts +154 -0
- package/src/lifecycle/publish-attachments.ts +25 -13
- package/src/lifecycle/publish-npm.ts +4 -3
- package/src/lifecycle/publish-targets.test.ts +34 -4
- package/src/lifecycle/release-at.test.ts +48 -0
- package/src/lifecycle/release-at.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +41 -0
- package/src/lifecycle/release-cut.ts +22 -11
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/release-publish.test.ts +43 -13
- package/src/lifecycle/release-publish.ts +38 -18
- package/src/lifecycle/release-store.ts +57 -13
- package/src/lifecycle/run-ledger.test.ts +13 -0
- package/src/lifecycle/run-ledger.ts +1 -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/lint/rules/op/index.ts +4 -2
- package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
- package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/activity-contracts.ts +3 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/activities/shell-container.e2e.test.ts +67 -0
- package/src/op/activities/shell.test.ts +44 -1
- package/src/op/activities/shell.ts +110 -3
- package/src/op/builders.ts +7 -0
- 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/runtime.ts +2 -0
- package/src/op/step-console.test.ts +4 -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 +146 -1
- package/src/pr-loop.ts +278 -17
- package/src/pr-report-url.ts +7 -7
- package/src/runner-start-bundle.test.ts +29 -0
- package/src/runner-start.test.ts +179 -0
- package/src/runner-start.ts +329 -0
- 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/check-contract.test.ts +3 -2
- package/src/workspace/check.schema.json +70 -2
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/checks/security.test.ts +138 -0
- package/src/workspace/checks/security.ts +305 -0
- package/src/workspace/checks.ts +5 -0
- package/src/workspace/ci-builds.e2e.test.ts +208 -0
- package/src/workspace/ci-builds.test.ts +174 -0
- package/src/workspace/ci-builds.ts +385 -0
- package/src/workspace/ci-green-builds.test.ts +180 -0
- package/src/workspace/ci-green-cli.test.ts +54 -0
- package/src/workspace/ci-green-cli.ts +26 -5
- package/src/workspace/ci-green-forge.ts +30 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green-workflow.test.ts +84 -1
- package/src/workspace/ci-green-workflow.ts +139 -2
- package/src/workspace/ci-green.test.ts +6 -3
- package/src/workspace/ci-green.ts +408 -48
- package/src/workspace/ci-last-green.schema.json +49 -0
- package/src/workspace/ci-list.schema.json +47 -7
- 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 +6 -4
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +19 -5
- package/src/workspace/ci-run.test.ts +203 -8
- package/src/workspace/ci-run.ts +295 -63
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-runner-start.test.ts +151 -0
- package/src/workspace/ci-runner-start.ts +193 -0
- package/src/workspace/ci-serve-builds.test.ts +86 -0
- package/src/workspace/ci-serve.test.ts +209 -6
- package/src/workspace/ci-serve.ts +341 -42
- package/src/workspace/declaration.schema.json +30 -2
- package/src/workspace/declaration.test.ts +40 -0
- package/src/workspace/declaration.ts +40 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +9 -2
- package/src/workspace/ls.ts +10 -4
- package/src/workspace/member-pipeline.ts +2 -1
- package/src/workspace/merge-record.schema.json +49 -0
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +75 -8
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +20 -2
- package/src/workspace/release-candidates.schema.json +353 -0
- package/src/workspace/release-candidates.test.ts +106 -0
- package/src/workspace/release-candidates.ts +315 -0
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/security.test.ts +70 -0
- package/src/workspace/security.ts +118 -0
- 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
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
2
2
|
import { join, dirname } from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import {
|
|
4
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { discoverLintRules, discoverPostSynthChecks, listRuleFiles } from "./discover";
|
|
5
7
|
|
|
6
8
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
7
9
|
|
|
@@ -67,3 +69,45 @@ describe("discoverPostSynthChecks", () => {
|
|
|
67
69
|
expect(checks.length).toBe(0);
|
|
68
70
|
});
|
|
69
71
|
});
|
|
72
|
+
|
|
73
|
+
describe("rule modules compiled to JS (#3981)", () => {
|
|
74
|
+
let dir: string;
|
|
75
|
+
|
|
76
|
+
beforeAll(() => {
|
|
77
|
+
// A compiled rules directory: ES modules in a "type": "module" package, each with its declarations beside it.
|
|
78
|
+
dir = mkdtempSync(join(tmpdir(), "chant-discover-js-"));
|
|
79
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ type: "module" }));
|
|
80
|
+
writeFileSync(join(dir, "alpha.js"), `export const alpha = { id: "JS002", severity: "warning", category: "style", check: () => [] };\n`);
|
|
81
|
+
writeFileSync(join(dir, "alpha.d.ts"), `export declare const alpha: unknown;\n`);
|
|
82
|
+
writeFileSync(join(dir, "beta.js"), `export const beta = { id: "JS001", severity: "error", category: "security", check: () => [] };\n`);
|
|
83
|
+
writeFileSync(join(dir, "beta.d.ts"), `export declare const beta: unknown;\n`);
|
|
84
|
+
writeFileSync(join(dir, "gamma.js"), `export const gamma = { id: "JSP001", description: "a post-synth check", check: () => [] };\n`);
|
|
85
|
+
writeFileSync(join(dir, "index.js"), `export * from "./alpha.js";\n`);
|
|
86
|
+
writeFileSync(join(dir, "rule-helpers.js"), `export const helper = 1;\n`);
|
|
87
|
+
writeFileSync(join(dir, "alpha.test.js"), `throw new Error("a test is not a rule");\n`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
91
|
+
|
|
92
|
+
test("listRuleFiles lists .js modules and leaves out declarations, tests, helpers and the index", () => {
|
|
93
|
+
expect(listRuleFiles(dir).sort()).toEqual(["alpha.js", "beta.js", "gamma.js"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a source module and its compiled twin list once, as the source", () => {
|
|
97
|
+
const both = mkdtempSync(join(tmpdir(), "chant-discover-both-"));
|
|
98
|
+
try {
|
|
99
|
+
for (const f of ["one.ts", "one.js", "one.d.ts", "two.js"]) writeFileSync(join(both, f), "");
|
|
100
|
+
expect(listRuleFiles(both).sort()).toEqual(["one.ts", "two.js"]);
|
|
101
|
+
} finally {
|
|
102
|
+
rmSync(both, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("discoverLintRules loads compiled rules", () => {
|
|
107
|
+
expect(discoverLintRules(dir, import.meta.url).map((r) => r.id)).toEqual(["JS001", "JS002"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("discoverPostSynthChecks loads compiled checks", () => {
|
|
111
|
+
expect(discoverPostSynthChecks(dir, import.meta.url).map((c) => c.id)).toEqual(["JSP001"]);
|
|
112
|
+
});
|
|
113
|
+
});
|
package/src/lint/discover.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-discovery for lint rules and post-synth checks.
|
|
3
3
|
*
|
|
4
|
-
* Scans directories for
|
|
5
|
-
* eliminating the need to manually list every rule in plugin.ts.
|
|
4
|
+
* Scans directories for rule modules and loads exported rules/checks,
|
|
5
|
+
* eliminating the need to manually list every rule in plugin.ts. A lexicon
|
|
6
|
+
* run from source has `.ts` rule modules; one run compiled has `.js` modules
|
|
7
|
+
* beside `.d.ts` declarations (#3981). Both are found, and tsx is registered
|
|
8
|
+
* only when a module found is TypeScript.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { readdirSync } from "fs";
|
|
@@ -11,6 +14,7 @@ import { createRequire } from "module";
|
|
|
11
14
|
import type { LintRule } from "./rule";
|
|
12
15
|
import type { PostSynthCheck } from "./post-synth";
|
|
13
16
|
import { ensureTypeScriptLoader } from "../ts-loader";
|
|
17
|
+
import { isTypeScriptModule } from "../module-source";
|
|
14
18
|
|
|
15
19
|
let cjsTypeScript = false;
|
|
16
20
|
|
|
@@ -22,7 +26,8 @@ let cjsTypeScript = false;
|
|
|
22
26
|
* From source this runs when the module loads, as it always has: vitest and
|
|
23
27
|
* the source CLI rely on tsx's CommonJS hook being in place from then on. The
|
|
24
28
|
* compiled CLI imports this module on every start and most commands read no
|
|
25
|
-
* rule directory, so there it waits for the first
|
|
29
|
+
* rule directory, so there it waits for the first directory that holds a
|
|
30
|
+
* TypeScript rule module (#3878, #3981).
|
|
26
31
|
*/
|
|
27
32
|
function registerTypeScriptRequire(): void {
|
|
28
33
|
if (cjsTypeScript) return;
|
|
@@ -40,82 +45,83 @@ function registerTypeScriptRequire(): void {
|
|
|
40
45
|
if (!import.meta.url.endsWith(".js")) registerTypeScriptRequire();
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* Scans the directory for .ts files (excluding tests, helpers, and index files),
|
|
46
|
-
* loads each module, and collects any exported objects that look like a LintRule
|
|
47
|
-
* (has `id`, `severity`, `category`, and `check` function).
|
|
48
|
-
*
|
|
49
|
-
* @param dir - Absolute path to the rules directory
|
|
50
|
-
* @param importMetaUrl - The caller's import.meta.url (for createRequire)
|
|
48
|
+
* Load every rule module in `dir` and collect the exports `accept` takes.
|
|
49
|
+
* Registers tsx first only when one of the modules is TypeScript.
|
|
51
50
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
function discoverExports<T>(dir: string, importMetaUrl: string, accept: (value: unknown) => value is T): T[] {
|
|
52
|
+
const files = listRuleFiles(dir);
|
|
53
|
+
if (files.some(isTypeScriptModule)) registerTypeScriptRequire();
|
|
54
54
|
const require = createRequire(importMetaUrl);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
for (const file of listRuleFiles(dir)) {
|
|
55
|
+
const found: T[] = [];
|
|
56
|
+
for (const file of files) {
|
|
58
57
|
try {
|
|
59
|
-
|
|
60
|
-
const modPath = join(dir, file.replace(/\.ts$/, ""));
|
|
61
|
-
const mod = require(modPath);
|
|
58
|
+
const mod = require(join(dir, file)) as Record<string, unknown>;
|
|
62
59
|
for (const exp of Object.values(mod)) {
|
|
63
|
-
if (
|
|
60
|
+
if (accept(exp)) found.push(exp);
|
|
64
61
|
}
|
|
65
62
|
} catch {
|
|
66
63
|
// Skip files that fail to load
|
|
67
64
|
}
|
|
68
65
|
}
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Discover lint rules from a directory.
|
|
71
|
+
*
|
|
72
|
+
* Scans the directory for rule modules (see {@link listRuleFiles}), loads
|
|
73
|
+
* each one, and collects any exported objects that look like a LintRule (has
|
|
74
|
+
* `id`, `severity`, `category`, and `check` function).
|
|
75
|
+
*
|
|
76
|
+
* @param dir - Absolute path to the rules directory
|
|
77
|
+
* @param importMetaUrl - The caller's import.meta.url (for createRequire)
|
|
78
|
+
*/
|
|
79
|
+
export function discoverLintRules(dir: string, importMetaUrl: string): LintRule[] {
|
|
80
|
+
return discoverExports(dir, importMetaUrl, isLintRule).sort((a, b) => a.id.localeCompare(b.id));
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
/**
|
|
74
84
|
* Discover post-synth checks from a directory.
|
|
75
85
|
*
|
|
76
|
-
* Scans the directory for
|
|
77
|
-
*
|
|
78
|
-
* (has `id`, `description`, and `check` function).
|
|
86
|
+
* Scans the directory for rule modules (see {@link listRuleFiles}), loads
|
|
87
|
+
* each one, and collects any exported objects that look like a
|
|
88
|
+
* PostSynthCheck (has `id`, `description`, and `check` function).
|
|
79
89
|
*
|
|
80
90
|
* @param dir - Absolute path to the post-synth rules directory
|
|
81
91
|
* @param importMetaUrl - The caller's import.meta.url (for createRequire)
|
|
82
92
|
*/
|
|
83
93
|
export function discoverPostSynthChecks(dir: string, importMetaUrl: string): PostSynthCheck[] {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const checks: PostSynthCheck[] = [];
|
|
87
|
-
|
|
88
|
-
for (const file of listRuleFiles(dir)) {
|
|
89
|
-
try {
|
|
90
|
-
// Strip .ts extension — require() resolves without it
|
|
91
|
-
const modPath = join(dir, file.replace(/\.ts$/, ""));
|
|
92
|
-
const mod = require(modPath);
|
|
93
|
-
for (const exp of Object.values(mod)) {
|
|
94
|
-
if (isPostSynthCheck(exp)) checks.push(exp);
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
// Skip files that fail to load
|
|
98
|
-
}
|
|
99
|
-
}
|
|
94
|
+
return discoverExports(dir, importMetaUrl, isPostSynthCheck).sort((a, b) => a.id.localeCompare(b.id));
|
|
95
|
+
}
|
|
100
96
|
|
|
101
|
-
|
|
97
|
+
/** A rule module's name without its extension: `foo` for `foo.ts` and `foo.js`. */
|
|
98
|
+
export function ruleModuleBase(file: string): string {
|
|
99
|
+
return file.replace(/\.(?:ts|js)$/, "");
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
/**
|
|
105
|
-
* List rule
|
|
103
|
+
* List rule modules in a directory: `.ts` source or compiled `.js`, leaving
|
|
104
|
+
* out declarations (`.d.ts`), tests, helpers and the index. A directory
|
|
105
|
+
* holding both `foo.ts` and `foo.js` lists `foo.ts` alone, so a rule is
|
|
106
|
+
* never loaded twice.
|
|
106
107
|
*/
|
|
107
108
|
export function listRuleFiles(dir: string): string[] {
|
|
109
|
+
let entries: string[];
|
|
108
110
|
try {
|
|
109
|
-
|
|
110
|
-
if (!f.endsWith(".ts")) return false;
|
|
111
|
-
if (f.endsWith(".test.ts")) return false;
|
|
112
|
-
if (f === "index.ts") return false;
|
|
113
|
-
if (f.includes("helper")) return false;
|
|
114
|
-
return true;
|
|
115
|
-
});
|
|
111
|
+
entries = readdirSync(dir);
|
|
116
112
|
} catch {
|
|
117
113
|
return [];
|
|
118
114
|
}
|
|
115
|
+
const modules = entries.filter((f) => {
|
|
116
|
+
if (!f.endsWith(".ts") && !f.endsWith(".js")) return false;
|
|
117
|
+
if (f.endsWith(".d.ts")) return false;
|
|
118
|
+
if (/\.test\.[tj]s$/.test(f)) return false;
|
|
119
|
+
if (f === "index.ts" || f === "index.js") return false;
|
|
120
|
+
if (f.includes("helper")) return false;
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
const sources = new Set(modules.filter((f) => f.endsWith(".ts")).map(ruleModuleBase));
|
|
124
|
+
return modules.filter((f) => f.endsWith(".ts") || !sources.has(ruleModuleBase(f)));
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
function isLintRule(obj: unknown): obj is LintRule {
|
|
@@ -16,6 +16,7 @@ import { ops014 } from "./ops014-converge-rule-refusals";
|
|
|
16
16
|
import { ops015 } from "./ops015-gate-approval";
|
|
17
17
|
import { ops016 } from "./ops016-gate-resume";
|
|
18
18
|
import { ops017Check } from "./ops017-gate-alias";
|
|
19
|
+
import { ops018 } from "./ops018-shell-image-digest";
|
|
19
20
|
import { sys010Check } from "./sys010-key-over-plain-http";
|
|
20
21
|
import type { DecideBackend } from "../../../op/decide-config";
|
|
21
22
|
|
|
@@ -25,6 +26,7 @@ export { ops014 } from "./ops014-converge-rule-refusals";
|
|
|
25
26
|
export { ops015 } from "./ops015-gate-approval";
|
|
26
27
|
export { ops016 } from "./ops016-gate-resume";
|
|
27
28
|
export { ops017, ops017Check, workspaceIdentityGates } from "./ops017-gate-alias";
|
|
29
|
+
export { ops018 } from "./ops018-shell-image-digest";
|
|
28
30
|
export { sys010, sys010Check } from "./sys010-key-over-plain-http";
|
|
29
31
|
|
|
30
32
|
export interface CoreOpCheckOptions {
|
|
@@ -36,9 +38,9 @@ export interface CoreOpCheckOptions {
|
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* Core's own post-synth checks over the Op model: OPS012, OPS013, OPS014,
|
|
39
|
-
* OPS015, OPS016, OPS017 over the workspace's `identity.gates`, and SYS010
|
|
41
|
+
* OPS015, OPS016, OPS017 over the workspace's `identity.gates`, OPS018, and SYS010
|
|
40
42
|
* over the `decide` steps and the configured decide backends.
|
|
41
43
|
*/
|
|
42
44
|
export function coreOpChecks(opts: CoreOpCheckOptions = {}): PostSynthCheck[] {
|
|
43
|
-
return [ops012, ops013, ops014, ops015, ops016, ops017Check(opts.identityGates), sys010Check(opts.decideBackends)];
|
|
45
|
+
return [ops012, ops013, ops014, ops015, ops016, ops017Check(opts.identityGates), ops018, sys010Check(opts.decideBackends)];
|
|
44
46
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPS018 (#4015): a shell step's image is pinned by digest.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, test, expect } from "vitest";
|
|
5
|
+
import type { PostSynthContext } from "../../post-synth";
|
|
6
|
+
import { DECLARABLE_MARKER } from "../../../declarable";
|
|
7
|
+
import { stepOutput } from "../../../op/step-output-ref";
|
|
8
|
+
import { ops018 } from "./ops018-shell-image-digest";
|
|
9
|
+
import { coreOpChecks } from "./index";
|
|
10
|
+
|
|
11
|
+
const PINNED = `node:24@sha256:${"d".repeat(64)}`;
|
|
12
|
+
|
|
13
|
+
function ctxWith(props: Record<string, unknown>): PostSynthContext {
|
|
14
|
+
const entities = new Map<string, unknown>([["build", {
|
|
15
|
+
[DECLARABLE_MARKER]: true,
|
|
16
|
+
entityType: "Chant::Op",
|
|
17
|
+
lexicon: "chant",
|
|
18
|
+
kind: "resource",
|
|
19
|
+
props: { name: "build", overview: "test", ...props },
|
|
20
|
+
attributes: {},
|
|
21
|
+
}]]);
|
|
22
|
+
return {
|
|
23
|
+
outputs: new Map([["chant", ""]]),
|
|
24
|
+
entities: entities as Map<string, never>,
|
|
25
|
+
buildResult: { outputs: new Map([["chant", ""]]), entities: entities as Map<string, never>, warnings: [], errors: [], sourceFileCount: 1 },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sh = (args: Record<string, unknown>) => ({ kind: "activity", fn: "shellCmd", args: { cmd: "npm test", ...args } });
|
|
30
|
+
|
|
31
|
+
describe("OPS018", () => {
|
|
32
|
+
test("is one of core's Op checks", () => {
|
|
33
|
+
expect(coreOpChecks().map((c) => c.id)).toContain("OPS018");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a pinned image and a step with no image pass", () => {
|
|
37
|
+
expect(ops018.check(ctxWith({ phases: [{ name: "Build", steps: [sh({ image: PINNED }), sh({})] }] }))).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("a tag is refused, naming the Op, the phase and the fix", () => {
|
|
41
|
+
const [diag, ...rest] = ops018.check(ctxWith({ phases: [{ name: "Build", steps: [sh({ image: "node:24" })] }] }));
|
|
42
|
+
expect(rest).toEqual([]);
|
|
43
|
+
expect(diag?.checkId).toBe("OPS018");
|
|
44
|
+
expect(diag?.severity).toBe("error");
|
|
45
|
+
expect(diag?.message).toBe('Op "build", phase "Build", shellCmd: image "node:24" is not pinned by digest: name it as <ref>@sha256:<digest>');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("an image read from another step is refused, since the record could not say what ran", () => {
|
|
49
|
+
const [diag] = ops018.check(ctxWith({ phases: [{ name: "Build", steps: [sh({ image: stepOutput("pick", "stdout") })] }] }));
|
|
50
|
+
expect(diag?.message).toContain("image must be a literal string");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("onFailure and effect steps are checked too", () => {
|
|
54
|
+
const diags = ops018.check(ctxWith({
|
|
55
|
+
phases: [{ name: "Apply", steps: [{ kind: "effect", receipt: {}, steps: [sh({ image: "alpine" })] }] }],
|
|
56
|
+
onFailure: [{ name: "Rollback", steps: [sh({ image: "alpine:latest" })] }],
|
|
57
|
+
}));
|
|
58
|
+
expect(diags.map((d) => d.message.split(",")[1]?.trim())).toEqual(['phase "Apply"', 'phase "Rollback"']);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPS018: a `shellCmd` step that runs in a container (#4015) names its image
|
|
3
|
+
* pinned by digest, `<ref>@sha256:<digest>`, as a literal string. A tag can
|
|
4
|
+
* move between two runs of the same Op, and then the step record's image no
|
|
5
|
+
* longer says what ran. Checked over the main phases, `onFailure`, and the
|
|
6
|
+
* steps inside an effect.
|
|
7
|
+
*/
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "../../post-synth";
|
|
9
|
+
import type { OpConfig, PhaseDefinition, StepDefinition } from "../../../op";
|
|
10
|
+
import { imageProblem } from "../../../op/activities/shell";
|
|
11
|
+
import { isOpEntity } from "./support";
|
|
12
|
+
|
|
13
|
+
function activitySteps(steps: readonly StepDefinition[]): Array<{ fn: string; args?: Record<string, unknown> }> {
|
|
14
|
+
return steps.flatMap((step) =>
|
|
15
|
+
step.kind === "activity" ? [step] : step.kind === "effect" ? step.steps.filter((s) => s.kind === "activity") : [],
|
|
16
|
+
) as Array<{ fn: string; args?: Record<string, unknown> }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ops018: PostSynthCheck = {
|
|
20
|
+
id: "OPS018",
|
|
21
|
+
description: "A shell step with an image names it pinned by digest, as a literal string",
|
|
22
|
+
|
|
23
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
24
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
25
|
+
for (const [entityKey, entity] of ctx.entities) {
|
|
26
|
+
if (!isOpEntity(entity)) continue;
|
|
27
|
+
const rec = entity as unknown as Record<string, unknown>;
|
|
28
|
+
const props = ((entity as { props?: Record<string, unknown> }).props ?? {}) as unknown as OpConfig;
|
|
29
|
+
if (typeof props.name !== "string" || !Array.isArray(props.phases)) continue;
|
|
30
|
+
const phases: PhaseDefinition[] = [...props.phases, ...(props.onFailure ?? [])];
|
|
31
|
+
for (const phase of phases) {
|
|
32
|
+
for (const step of activitySteps(phase.steps ?? [])) {
|
|
33
|
+
if (step.fn !== "shellCmd" || step.args?.image === undefined) continue;
|
|
34
|
+
const problem = imageProblem(step.args.image);
|
|
35
|
+
if (!problem) continue;
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "OPS018",
|
|
38
|
+
severity: "error",
|
|
39
|
+
message: `Op "${props.name}", phase "${phase.name}", shellCmd: ${problem}`,
|
|
40
|
+
entity: entityKey,
|
|
41
|
+
lexicon: typeof rec.lexicon === "string" ? rec.lexicon : undefined,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return diagnostics;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import { isJavaScriptModule, isTypeScriptModule, moduleSourceDir, moduleSourceUrl, SOURCE_EXPORT_CONDITIONS } from "./module-source";
|
|
6
|
+
|
|
7
|
+
describe("module-source (#3981)", () => {
|
|
8
|
+
test("from source, chant's own resolution adds the development condition", () => {
|
|
9
|
+
expect(SOURCE_EXPORT_CONDITIONS).toEqual(["development"]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const pkg = join(sep, "x", "node_modules", "@intentius", "chant-lexicon-aws");
|
|
13
|
+
|
|
14
|
+
test("moduleSourceDir maps a compiled module's directory to the source directory", () => {
|
|
15
|
+
expect(moduleSourceDir(pathToFileURL(join(pkg, "dist", "lint", "post-synth", "ext001.js")).href)).toBe(join(pkg, "src", "lint", "post-synth"));
|
|
16
|
+
expect(moduleSourceDir(pathToFileURL(join(pkg, "dist", "taggable.js")).href)).toBe(join(pkg, "src"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("moduleSourceDir leaves a source module's directory alone, even under a dist/ directory", () => {
|
|
20
|
+
expect(moduleSourceDir(pathToFileURL(join(pkg, "src", "taggable.ts")).href)).toBe(join(pkg, "src"));
|
|
21
|
+
const checkout = join(sep, "home", "dist", "chant", "lexicons", "aws", "src", "taggable.ts");
|
|
22
|
+
expect(moduleSourceDir(pathToFileURL(checkout).href)).toBe(join(sep, "home", "dist", "chant", "lexicons", "aws", "src"));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("moduleSourceUrl is a URL in the source directory, for createRequire", () => {
|
|
26
|
+
const url = moduleSourceUrl(pathToFileURL(join(pkg, "dist", "lsp", "hover.js")).href);
|
|
27
|
+
expect(url).toBe(pathToFileURL(join(pkg, "src", "lsp", "hover.js")).href);
|
|
28
|
+
expect(new URL("../generated/lexicon-aws.json", url).href).toBe(pathToFileURL(join(pkg, "src", "generated", "lexicon-aws.json")).href);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("isTypeScriptModule and isJavaScriptModule", () => {
|
|
32
|
+
expect(isTypeScriptModule("/p/src/index.ts")).toBe(true);
|
|
33
|
+
expect(isTypeScriptModule("file:///p/src/index.mts?x=1")).toBe(true);
|
|
34
|
+
expect(isTypeScriptModule("/p/dist/index.d.ts")).toBe(false);
|
|
35
|
+
expect(isTypeScriptModule("/p/dist/index.js")).toBe(false);
|
|
36
|
+
expect(isJavaScriptModule("file:///p/dist/index.js")).toBe(true);
|
|
37
|
+
expect(isJavaScriptModule("/p/dist/index.mjs")).toBe(true);
|
|
38
|
+
expect(isJavaScriptModule("/p/src/index.ts")).toBe(false);
|
|
39
|
+
// No extension: only tsx's resolver completes it, so it is not plain JavaScript.
|
|
40
|
+
expect(isJavaScriptModule("/p/lexicon/index")).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("package sources run under plain node ESM (#3981)", () => {
|
|
45
|
+
const repo = resolve(import.meta.dirname, "..", "..", "..");
|
|
46
|
+
const SKIP_DIRS = new Set(["node_modules", "generated", "testdata", "fixtures", "__fixtures__", "test-fixtures"]);
|
|
47
|
+
|
|
48
|
+
function sources(dir: string, out: string[] = []): string[] {
|
|
49
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
if (!SKIP_DIRS.has(entry.name)) sources(join(dir, entry.name), out);
|
|
52
|
+
} else if (/\.ts$/.test(entry.name) && !/\.(test|d)\.ts$/.test(entry.name)) {
|
|
53
|
+
out.push(join(dir, entry.name));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test("no lexicon or package source uses the CommonJS __dirname or __filename", () => {
|
|
60
|
+
// Both are undefined in an ES module under plain node, and under tsx too: a
|
|
61
|
+
// read built on one throws, and the catch around it hides the data.
|
|
62
|
+
// moduleSourceDir(import.meta.url) names the source directory from src/ and dist/ alike.
|
|
63
|
+
const roots = ["lexicons", "packages"].flatMap((top) =>
|
|
64
|
+
readdirSync(join(repo, top), { withFileTypes: true })
|
|
65
|
+
.filter((e) => e.isDirectory())
|
|
66
|
+
.map((e) => join(repo, top, e.name, "src")),
|
|
67
|
+
);
|
|
68
|
+
const offenders: string[] = [];
|
|
69
|
+
for (const root of roots) {
|
|
70
|
+
let files: string[];
|
|
71
|
+
try {
|
|
72
|
+
files = sources(root);
|
|
73
|
+
} catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const code = readFileSync(file, "utf-8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
78
|
+
code.split("\n").forEach((line, i) => {
|
|
79
|
+
if (/\b__(?:dirname|filename)\b/.test(line)) offenders.push(`${relative(repo, file)}:${i + 1}: ${line.trim()}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
expect(offenders).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3981 — paths for a module that may run from `src/*.ts` or from compiled
|
|
3
|
+
* `dist/*.js`.
|
|
4
|
+
*
|
|
5
|
+
* A package compiles `src/` to `dist/` with the same layout (`rootDir: src`,
|
|
6
|
+
* `outDir: dist`), so a compiled module sits at the same relative path under
|
|
7
|
+
* `dist/` as its source does under `src/`. tsc compiles only the modules: a
|
|
8
|
+
* file a module reads beside itself (generated JSON, skills markdown,
|
|
9
|
+
* snapshots) stays under `src/`, which every package publishes. A module that
|
|
10
|
+
* reads such a file names it from {@link moduleSourceDir}, not from its own
|
|
11
|
+
* directory, and finds it from either layout.
|
|
12
|
+
*
|
|
13
|
+
* String arithmetic only, no filesystem access, so it is safe in a sandboxed
|
|
14
|
+
* child and costs nothing at module scope.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { dirname, sep } from "node:path";
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
|
|
20
|
+
const DIST = `${sep}dist${sep}`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The path a module's source has: `path` itself for a source module, and the
|
|
24
|
+
* matching path under `<package>/src` for a module under `<package>/dist`.
|
|
25
|
+
*/
|
|
26
|
+
function sourcePathOf(path: string): string {
|
|
27
|
+
if (!/\.[cm]?js$/.test(path)) return path;
|
|
28
|
+
const at = path.lastIndexOf(DIST);
|
|
29
|
+
if (at < 0) return path;
|
|
30
|
+
return path.slice(0, at) + `${sep}src${sep}` + path.slice(at + DIST.length);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* #3981 — the source file a compiled module was built from:
|
|
35
|
+
* `<package>/dist/x/y.js` is `<package>/src/x/y.ts` (`.mjs` from `.mts`, `.cjs`
|
|
36
|
+
* from `.cts`). `undefined` for a path that is not a compiled module under a
|
|
37
|
+
* `dist/`. The file is not checked for.
|
|
38
|
+
*/
|
|
39
|
+
export function compiledModuleSource(path: string): string | undefined {
|
|
40
|
+
const ext = /\.([cm]?)js$/.exec(path);
|
|
41
|
+
if (!ext || path.lastIndexOf(DIST) < 0) return undefined;
|
|
42
|
+
return sourcePathOf(path).slice(0, -ext[0].length) + `.${ext[1]}ts`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The directory a module's source sits in: `dirname` of `moduleUrl` when it
|
|
47
|
+
* runs from source, and the matching directory under `<package>/src` when it
|
|
48
|
+
* runs compiled from `<package>/dist`.
|
|
49
|
+
*/
|
|
50
|
+
export function moduleSourceDir(moduleUrl: string): string {
|
|
51
|
+
return dirname(sourcePathOf(fileURLToPath(moduleUrl)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A URL in the module's source directory, for `createRequire` to read a file
|
|
56
|
+
* that sits beside the source (`require("./generated/lexicon-aws.json")`). The
|
|
57
|
+
* file name keeps the compiled extension; only the directory matters to
|
|
58
|
+
* `createRequire`.
|
|
59
|
+
*/
|
|
60
|
+
export function moduleSourceUrl(moduleUrl: string): string {
|
|
61
|
+
return pathToFileURL(sourcePathOf(fileURLToPath(moduleUrl))).href;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether `pathOrUrl` names a TypeScript module, which plain node cannot
|
|
66
|
+
* import without a loader: `.ts`, `.mts`, `.cts` or `.tsx`, and not a
|
|
67
|
+
* declaration file. A query or hash is ignored.
|
|
68
|
+
*/
|
|
69
|
+
export function isTypeScriptModule(pathOrUrl: string): boolean {
|
|
70
|
+
const path = pathOrUrl.replace(/[?#].*$/, "");
|
|
71
|
+
return /\.(?:[cm]?ts|tsx)$/.test(path) && !/\.d\.[cm]?ts$/.test(path);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether plain node imports `pathOrUrl` with no loader: `.js`, `.mjs`,
|
|
76
|
+
* `.cjs` or `.json`. Anything else, a module with no extension included, is
|
|
77
|
+
* left to tsx, whose resolver completes an extensionless path.
|
|
78
|
+
*/
|
|
79
|
+
export function isJavaScriptModule(pathOrUrl: string): boolean {
|
|
80
|
+
return /\.(?:[cm]?js|json)$/.test(pathOrUrl.replace(/[?#].*$/, ""));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* #3981 — whether this process resolves chant's packages through their
|
|
85
|
+
* `development` export condition: always when chant runs from source (the
|
|
86
|
+
* CLI's `main()` installs a resolve hook that does so, and vitest's resolver
|
|
87
|
+
* adds the condition outside production mode), and otherwise when node runs
|
|
88
|
+
* with `--conditions=development` (or `-C development`, on the command line
|
|
89
|
+
* or in `NODE_OPTIONS`). Not from the environment: a compiled CLI that a test
|
|
90
|
+
* spawns inherits vitest's variables.
|
|
91
|
+
*/
|
|
92
|
+
function resolvesDevelopment(): boolean {
|
|
93
|
+
if (isTypeScriptModule(import.meta.url)) return true;
|
|
94
|
+
if (typeof process === "undefined") return false;
|
|
95
|
+
const flags = [...process.execArgv, ...(process.env.NODE_OPTIONS ?? "").split(/\s+/)];
|
|
96
|
+
return flags.some(
|
|
97
|
+
(flag, i) =>
|
|
98
|
+
flag === "--conditions=development" ||
|
|
99
|
+
flag === "-C=development" ||
|
|
100
|
+
((flag === "--conditions" || flag === "-C") && flags[i + 1] === "development"),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* #3981 — the package export conditions chant adds to `import`, `node` and
|
|
106
|
+
* `default` when it resolves a package itself (fold's import resolution, the
|
|
107
|
+
* sandbox bundle), so it lands on the module this process's `import()` would.
|
|
108
|
+
* Each chant package maps `development` to `src/*.ts` and `default` to the
|
|
109
|
+
* compiled `dist/*.js` it publishes. `["development"]` when the process
|
|
110
|
+
* resolves with it ({@link resolvesDevelopment}), else empty.
|
|
111
|
+
*/
|
|
112
|
+
export const SOURCE_EXPORT_CONDITIONS: readonly string[] = resolvesDevelopment() ? ["development"] : [];
|
|
@@ -59,6 +59,9 @@ export const shellCmdContract = activityContract(
|
|
|
59
59
|
gatedExit: z.number().optional(),
|
|
60
60
|
gate: z.strictObject({ op: z.string(), gate: z.string() }).optional(),
|
|
61
61
|
json: z.boolean().optional(),
|
|
62
|
+
image: z.string().optional(),
|
|
63
|
+
mounts: z.strictObject({ workspace: z.enum(["ro", "rw"]).optional() }).optional(),
|
|
64
|
+
outputs: z.array(z.string()).optional(),
|
|
62
65
|
}),
|
|
63
66
|
// `json` is stdout parsed, when the step sets `json` (#2787): whatever the
|
|
64
67
|
// command printed, so its shape is the author's and only the whole value is
|
|
@@ -357,7 +357,7 @@ function inWorktree(kindFile: string, checkout: string, worktree: string): strin
|
|
|
357
357
|
}
|
|
358
358
|
};
|
|
359
359
|
const [file, top, tree] = [real(kindFile), real(checkout), real(worktree)];
|
|
360
|
-
// Read from the worktree already (it sits
|
|
360
|
+
// Read from the worktree already (it sits in the checkout's .chant/work, #2893), or outside the checkout: as it is.
|
|
361
361
|
if (file.startsWith(`${tree}/`) || !file.startsWith(`${top}/`)) return file;
|
|
362
362
|
return join(tree, file.slice(top.length + 1));
|
|
363
363
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `shell()` step in a container (#4015), run for real with `docker run
|
|
3
|
+
* --rm`. Skipped when Docker is not running. The containers are `--rm` and
|
|
4
|
+
* mount only host directories, so nothing is left behind.
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { shellCmd } from "./shell";
|
|
12
|
+
|
|
13
|
+
// alpine 3.21, pinned by its index digest.
|
|
14
|
+
const IMAGE = process.env.CHANT_SHELL_E2E_IMAGE ?? "alpine:3.21@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507";
|
|
15
|
+
|
|
16
|
+
function available(cmd: string): boolean {
|
|
17
|
+
try {
|
|
18
|
+
execSync(cmd, { stdio: "ignore" });
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const hasDocker = available("docker info");
|
|
26
|
+
const skipReason = hasDocker ? "" : " (skipped: Docker is not running)";
|
|
27
|
+
|
|
28
|
+
describe.skipIf(!hasDocker)(`shellCmd with an image${skipReason}`, () => {
|
|
29
|
+
let dir: string;
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
dir = await mkdtemp(join(tmpdir(), "chant-shell-e2e-"));
|
|
33
|
+
await writeFile(join(dir, "input.txt"), "from the host\n");
|
|
34
|
+
execSync(`docker pull -q ${IMAGE}`, { stdio: "ignore" });
|
|
35
|
+
}, 120_000);
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await rm(dir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("runs in the image, reads the workspace read-only, sees env by name, and writes its output directory on the host", async () => {
|
|
42
|
+
process.env.CHANT_SHELL_E2E_HOST_ONLY = "leaked";
|
|
43
|
+
try {
|
|
44
|
+
const result = await shellCmd({
|
|
45
|
+
cmd: 'cat /etc/alpine-release >/dev/null && echo "$GREETING:$CHANT_SHELL_E2E_HOST_ONLY" && cat input.txt > out/copy.txt; touch input.txt 2>/dev/null || echo read-only',
|
|
46
|
+
image: IMAGE,
|
|
47
|
+
cwd: dir,
|
|
48
|
+
env: { GREETING: "hi" },
|
|
49
|
+
outputs: ["out"],
|
|
50
|
+
});
|
|
51
|
+
expect(result.stdout.split("\n")).toEqual(["hi:", "read-only"]);
|
|
52
|
+
expect(await readFile(join(dir, "out", "copy.txt"), "utf8")).toBe("from the host\n");
|
|
53
|
+
} finally {
|
|
54
|
+
delete process.env.CHANT_SHELL_E2E_HOST_ONLY;
|
|
55
|
+
}
|
|
56
|
+
}, 60_000);
|
|
57
|
+
|
|
58
|
+
test("a declared output directory the command left empty fails the step", async () => {
|
|
59
|
+
await expect(shellCmd({ cmd: "true", image: IMAGE, cwd: dir, outputs: ["empty"] })).rejects.toThrow(/left it empty/);
|
|
60
|
+
}, 60_000);
|
|
61
|
+
|
|
62
|
+
test("keeps the command's exit status, and okExit still applies", async () => {
|
|
63
|
+
await expect(shellCmd({ cmd: "exit 4", image: IMAGE, cwd: dir })).rejects.toThrow(/command exited 4/);
|
|
64
|
+
const ok = await shellCmd({ cmd: "echo done; exit 1", image: IMAGE, cwd: dir, okExit: [0, 1] });
|
|
65
|
+
expect(ok).toMatchObject({ stdout: "done", exitCode: 1 });
|
|
66
|
+
}, 60_000);
|
|
67
|
+
});
|