@intentius/chant 0.13.1 → 0.15.1
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/catalog.d.ts +45 -5
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/proof.d.ts +3 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +4 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts +3 -0
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +8 -2
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +39 -0
- package/dist/cli/handlers/components.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts +3 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts +4 -2
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +35 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +22 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +110 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -0
- package/dist/codegen/pinned-upgrade.d.ts +153 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
- package/dist/codegen/rolling-upgrade.d.ts +138 -0
- package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
- package/dist/codegen/surface-snapshot.d.ts +173 -0
- package/dist/codegen/surface-snapshot.d.ts.map +1 -0
- package/dist/codegen/version-bump.d.ts +21 -0
- package/dist/codegen/version-bump.d.ts.map +1 -0
- package/dist/components/auto-release.d.ts +93 -0
- package/dist/components/auto-release.d.ts.map +1 -0
- package/dist/components/builders.d.ts +37 -0
- package/dist/components/builders.d.ts.map +1 -0
- package/dist/components/capability-plugin-loader.d.ts +111 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -0
- package/dist/components/capability-plugin-schema.d.ts +24 -0
- package/dist/components/capability-plugin-schema.d.ts.map +1 -0
- package/dist/components/capability-plugin.d.ts +85 -0
- package/dist/components/capability-plugin.d.ts.map +1 -0
- package/dist/components/capability.d.ts +89 -0
- package/dist/components/capability.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +204 -0
- package/dist/components/cli-support.d.ts.map +1 -0
- package/dist/components/component.d.ts +179 -0
- package/dist/components/component.d.ts.map +1 -0
- package/dist/components/config-defaults.d.ts +41 -0
- package/dist/components/config-defaults.d.ts.map +1 -0
- package/dist/components/discover.d.ts +60 -0
- package/dist/components/discover.d.ts.map +1 -0
- package/dist/components/driver-output.d.ts +20 -0
- package/dist/components/driver-output.d.ts.map +1 -0
- package/dist/components/driver.d.ts +209 -0
- package/dist/components/driver.d.ts.map +1 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/manifest-persistence.d.ts +79 -0
- package/dist/components/manifest-persistence.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
- package/dist/components/pilots/lambda.pilot.d.ts +35 -0
- package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
- package/dist/components/pilots/project.d.ts +13 -0
- package/dist/components/pilots/project.d.ts.map +1 -0
- package/dist/components/presets/ecs-fargate.d.ts +59 -0
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
- package/dist/components/presets/index.d.ts +29 -0
- package/dist/components/presets/index.d.ts.map +1 -0
- package/dist/components/presets/lambda.d.ts +35 -0
- package/dist/components/presets/lambda.d.ts.map +1 -0
- package/dist/components/presets/single-host-compose.d.ts +43 -0
- package/dist/components/presets/single-host-compose.d.ts.map +1 -0
- package/dist/components/registry.d.ts +29 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/starter-plugin.d.ts +51 -0
- package/dist/components/starter-plugin.d.ts.map +1 -0
- package/dist/components/temporal-codegen-loader.d.ts +27 -0
- package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/bom-writer.d.ts +155 -0
- package/dist/components/verbs/bom-writer.d.ts.map +1 -0
- package/dist/components/verbs/build-archive.d.ts +205 -0
- package/dist/components/verbs/build-archive.d.ts.map +1 -0
- package/dist/components/verbs/build.d.ts +150 -0
- package/dist/components/verbs/build.d.ts.map +1 -0
- package/dist/components/verbs/cloud-executor.d.ts +88 -0
- package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/component-bom.d.ts +103 -0
- package/dist/components/verbs/component-bom.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +49 -0
- package/dist/components/verbs/index.d.ts.map +1 -0
- package/dist/components/verbs/license-policy.d.ts +56 -0
- package/dist/components/verbs/license-policy.d.ts.map +1 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/process-runner.d.ts +64 -0
- package/dist/components/verbs/process-runner.d.ts.map +1 -0
- package/dist/components/verbs/reproducibility.d.ts +80 -0
- package/dist/components/verbs/reproducibility.d.ts.map +1 -0
- package/dist/components/verbs/sbom-generator.d.ts +157 -0
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/sbom.d.ts +79 -0
- package/dist/components/verbs/sbom.d.ts.map +1 -0
- package/dist/components/verbs/shell.d.ts +35 -0
- package/dist/components/verbs/shell.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +222 -0
- package/dist/components/verbs/sign.d.ts.map +1 -0
- package/dist/components/verbs/stub.d.ts +18 -0
- package/dist/components/verbs/stub.d.ts.map +1 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/verify.d.ts +106 -0
- package/dist/components/verbs/verify.d.ts.map +1 -0
- package/dist/components/verbs/vex.d.ts +65 -0
- package/dist/components/verbs/vex.d.ts.map +1 -0
- package/dist/components/verbs/vuln-gate.d.ts +91 -0
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
- package/dist/components/verbs/vuln-scan.d.ts +116 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
- package/dist/components/verbs/wait-verify.d.ts +85 -0
- package/dist/components/verbs/wait-verify.d.ts.map +1 -0
- package/dist/config.d.ts +205 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +78 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts +121 -0
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
- package/dist/lifecycle/build-ledger.d.ts +225 -0
- package/dist/lifecycle/build-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +84 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +5 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
- package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +122 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -0
- package/dist/lifecycle/status.d.ts +150 -0
- package/dist/lifecycle/status.d.ts.map +1 -0
- package/dist/lint/component-checks.d.ts +95 -0
- package/dist/lint/component-checks.d.ts.map +1 -0
- package/dist/lint/engine.d.ts +23 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/presets/strict.json +8 -1
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +18 -0
- package/dist/lint/rules/comp/index.d.ts.map +1 -0
- package/dist/lint/rules/comp/support.d.ts +71 -0
- package/dist/lint/rules/comp/support.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts +11 -6
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +24 -0
- package/dist/op/activity-runtime.d.ts.map +1 -0
- package/dist/op/builders.d.ts +59 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +20 -14
- package/dist/ownership.d.ts.map +1 -1
- package/package.json +14 -1
- package/src/audit/catalog.test.ts +74 -22
- package/src/audit/catalog.ts +74 -471
- package/src/audit/proof.ts +4 -2
- package/src/audit/report-html.test.ts +14 -7
- package/src/audit/report-model.ts +20 -9
- package/src/audit/report.test.ts +36 -13
- package/src/audit/report.ts +2 -0
- package/src/audit/rules-doc.test.ts +8 -6
- package/src/audit/rules-doc.ts +13 -5
- package/src/cli/commands/audit.ts +23 -18
- package/src/cli/commands/component-lint.test.ts +158 -0
- package/src/cli/commands/doctor.test.ts +116 -2
- package/src/cli/commands/doctor.ts +46 -0
- package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
- package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
- package/src/cli/commands/lexicon-surface-diff.ts +185 -0
- package/src/cli/commands/lint.ts +142 -1
- package/src/cli/commands/pinned-upgrade.ts +204 -0
- package/src/cli/handlers/build.ts +58 -1
- package/src/cli/handlers/components.test.ts +504 -0
- package/src/cli/handlers/components.ts +404 -0
- package/src/cli/handlers/dev.ts +60 -1
- package/src/cli/handlers/graph.ts +47 -2
- package/src/cli/handlers/misc.ts +45 -1
- package/src/cli/handlers/run.test.ts +967 -3
- package/src/cli/handlers/run.ts +618 -11
- package/src/cli/main.test.ts +66 -0
- package/src/cli/main.ts +105 -2
- package/src/cli/registry.ts +35 -0
- package/src/codegen/fetch.test.ts +84 -3
- package/src/codegen/fetch.ts +76 -2
- package/src/codegen/lexicon-regen.test.ts +298 -0
- package/src/codegen/lexicon-regen.ts +545 -0
- package/src/codegen/pinned-upgrade.test.ts +399 -0
- package/src/codegen/pinned-upgrade.ts +423 -0
- package/src/codegen/rolling-upgrade.test.ts +333 -0
- package/src/codegen/rolling-upgrade.ts +320 -0
- package/src/codegen/surface-snapshot.test.ts +461 -0
- package/src/codegen/surface-snapshot.ts +546 -0
- package/src/codegen/version-bump.test.ts +47 -0
- package/src/codegen/version-bump.ts +39 -0
- package/src/components/README.md +130 -0
- package/src/components/SPRAWL-VALIDATION.md +181 -0
- package/src/components/__fixtures__/alb-ecs-service.json +51 -0
- package/src/components/__fixtures__/dynamodb-infra.json +32 -0
- package/src/components/__fixtures__/emr-job-consumer.json +26 -0
- package/src/components/__fixtures__/jar-lib-producer.json +16 -0
- package/src/components/__fixtures__/lambda-image-processor.json +28 -0
- package/src/components/__fixtures__/neo4j-fanout.json +34 -0
- package/src/components/__fixtures__/single-host-compose.json +29 -0
- package/src/components/auto-release.test.ts +217 -0
- package/src/components/auto-release.ts +216 -0
- package/src/components/builders.test.ts +45 -0
- package/src/components/builders.ts +71 -0
- package/src/components/capability-plugin-loader.ts +218 -0
- package/src/components/capability-plugin-schema.ts +56 -0
- package/src/components/capability-plugin.test.ts +235 -0
- package/src/components/capability-plugin.ts +108 -0
- package/src/components/capability.ts +117 -0
- package/src/components/cli-support.test.ts +579 -0
- package/src/components/cli-support.ts +481 -0
- package/src/components/component-schema.test.ts +265 -0
- package/src/components/component.schema.json +235 -0
- package/src/components/component.test.ts +251 -0
- package/src/components/component.ts +255 -0
- package/src/components/config-defaults.test.ts +263 -0
- package/src/components/config-defaults.ts +145 -0
- package/src/components/discover.test.ts +352 -0
- package/src/components/discover.ts +167 -0
- package/src/components/driver-output.ts +71 -0
- package/src/components/driver.test.ts +638 -0
- package/src/components/driver.ts +690 -0
- package/src/components/index.ts +113 -0
- package/src/components/manifest-persistence.test.ts +152 -0
- package/src/components/manifest-persistence.ts +164 -0
- package/src/components/pilots/README.md +33 -0
- package/src/components/pilots/alb-ecs.pilot.ts +63 -0
- package/src/components/pilots/dynamodb.pilot.ts +59 -0
- package/src/components/pilots/jar-emr.pilot.ts +69 -0
- package/src/components/pilots/lambda.pilot.ts +55 -0
- package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
- package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
- package/src/components/pilots/pilots.test.ts +126 -0
- package/src/components/pilots/project.ts +13 -0
- package/src/components/presets/comp007-reduction.test.ts +122 -0
- package/src/components/presets/ecs-fargate.test.ts +84 -0
- package/src/components/presets/ecs-fargate.ts +103 -0
- package/src/components/presets/index.ts +32 -0
- package/src/components/presets/lambda.test.ts +35 -0
- package/src/components/presets/lambda.ts +53 -0
- package/src/components/presets/single-host-compose.test.ts +65 -0
- package/src/components/presets/single-host-compose.ts +64 -0
- package/src/components/registry.test.ts +112 -0
- package/src/components/registry.ts +37 -0
- package/src/components/starter-plugin.ts +105 -0
- package/src/components/temporal-codegen-loader.ts +41 -0
- package/src/components/verbs/__fixtures__/package-lock.json +45 -0
- package/src/components/verbs/__fixtures__/pom.xml +37 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
- package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
- package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
- package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
- package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
- package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
- package/src/components/verbs/bom-writer.test.ts +331 -0
- package/src/components/verbs/bom-writer.ts +386 -0
- package/src/components/verbs/build-archive.test.ts +251 -0
- package/src/components/verbs/build-archive.ts +296 -0
- package/src/components/verbs/build.test.ts +222 -0
- package/src/components/verbs/build.ts +252 -0
- package/src/components/verbs/cloud-executor.ts +181 -0
- package/src/components/verbs/component-bom.test.ts +219 -0
- package/src/components/verbs/component-bom.ts +189 -0
- package/src/components/verbs/index.ts +49 -0
- package/src/components/verbs/license-policy.ts +115 -0
- package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
- package/src/components/verbs/process-runner.test.ts +79 -0
- package/src/components/verbs/process-runner.ts +113 -0
- package/src/components/verbs/reproducibility.test.ts +24 -0
- package/src/components/verbs/reproducibility.ts +100 -0
- package/src/components/verbs/sbom-generator.test.ts +96 -0
- package/src/components/verbs/sbom-generator.ts +209 -0
- package/src/components/verbs/sbom.test.ts +184 -0
- package/src/components/verbs/sbom.ts +152 -0
- package/src/components/verbs/shell.test.ts +36 -0
- package/src/components/verbs/shell.ts +51 -0
- package/src/components/verbs/sign.test.ts +210 -0
- package/src/components/verbs/sign.ts +350 -0
- package/src/components/verbs/stub.ts +34 -0
- package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
- package/src/components/verbs/tool-sbom-generator.ts +192 -0
- package/src/components/verbs/verify.test.ts +159 -0
- package/src/components/verbs/verify.ts +179 -0
- package/src/components/verbs/vex.ts +182 -0
- package/src/components/verbs/vuln-gate.test.ts +201 -0
- package/src/components/verbs/vuln-gate.ts +170 -0
- package/src/components/verbs/vuln-scan.test.ts +145 -0
- package/src/components/verbs/vuln-scan.ts +266 -0
- package/src/components/verbs/wait-verify.test.ts +111 -0
- package/src/components/verbs/wait-verify.ts +171 -0
- package/src/config.test.ts +74 -1
- package/src/config.ts +219 -0
- package/src/index.ts +32 -0
- package/src/lexicon.ts +88 -0
- package/src/lifecycle/build-ledger-store.test.ts +217 -0
- package/src/lifecycle/build-ledger-store.ts +165 -0
- package/src/lifecycle/build-ledger.test.ts +349 -0
- package/src/lifecycle/build-ledger.ts +305 -0
- package/src/lifecycle/git.test.ts +118 -0
- package/src/lifecycle/git.ts +149 -14
- package/src/lifecycle/index.ts +5 -0
- package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
- package/src/lifecycle/oras-referrer-lookup.ts +110 -0
- package/src/lifecycle/release-ledger.test.ts +209 -0
- package/src/lifecycle/release-ledger.ts +185 -0
- package/src/lifecycle/status.test.ts +330 -0
- package/src/lifecycle/status.ts +278 -0
- package/src/lint/component-checks.ts +148 -0
- package/src/lint/config.test.ts +21 -0
- package/src/lint/engine.ts +11 -2
- package/src/lint/presets/relaxed.json +8 -1
- package/src/lint/presets/strict.json +8 -1
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
- package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
- package/src/lint/rules/comp/comp.test.ts +378 -0
- package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
- package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
- package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
- package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
- package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
- package/src/lint/rules/comp/index.ts +37 -0
- package/src/lint/rules/comp/support.ts +173 -0
- package/src/lsp/lexicon-providers.test.ts +55 -0
- package/src/lsp/lexicon-providers.ts +13 -3
- package/src/op/activity-registry.test.ts +38 -0
- package/src/op/activity-registry.ts +31 -12
- package/src/op/activity-runtime.ts +73 -0
- package/src/op/builders.ts +99 -0
- package/src/op/index.ts +3 -1
- package/src/ownership.test.ts +24 -26
- package/src/ownership.ts +26 -38
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SBOM document type + the injectable, artifact-type-keyed `SbomGenerator`
|
|
3
|
+
* boundary (#606, epic #551 "Build & deploy observability" follow-up to
|
|
4
|
+
* #564/#568). Mirrors ./cloud-executor.ts's `CloudExecutor` pattern: a real
|
|
5
|
+
* backend may shell out to `syft`/`docker buildx --sbom`/`cyclonedx-maven`,
|
|
6
|
+
* but no test and no default import ever does — every backend here is
|
|
7
|
+
* injected, and tests construct a `MockSbomGenerator`
|
|
8
|
+
* (./__tests__/mock-sbom-generator.ts) instead.
|
|
9
|
+
*
|
|
10
|
+
* **Format-agnostic by construction.** An SBOM is stored as an opaque typed
|
|
11
|
+
* doc: `{ format, mediaType, bytes }`. Nothing downstream (the build-archive
|
|
12
|
+
* manifest, the build ledger, `chant components status`) branches on
|
|
13
|
+
* `format` — every consumer keys off `mediaType`, the same convention
|
|
14
|
+
* `BuildArchiveEntry.mediaType` already uses for image/template/asset
|
|
15
|
+
* entries (./build-archive.ts). SPDX (`application/spdx+json`) and
|
|
16
|
+
* CycloneDX (`application/vnd.cyclonedx+json`) are both first-class; neither
|
|
17
|
+
* is hardcoded as *the* format anywhere in this module or its consumers.
|
|
18
|
+
*
|
|
19
|
+
* **Default format:** SPDX, matching the epic's refinement from the
|
|
20
|
+
* devops-cloud.news SBOM article — BuildKit natively emits SPDX attestations
|
|
21
|
+
* for images, so the image/BuildKit backend defaults to SPDX; the `syft`
|
|
22
|
+
* backend can emit either and defaults to SPDX here too, for one predictable
|
|
23
|
+
* default across artifact types. Override via `chant.config.ts`'s
|
|
24
|
+
* `sbom.format` (see ../../config.ts) and/or a component's own `build.sbom`
|
|
25
|
+
* (see ../component.ts's `BuildSpec`). Documented in
|
|
26
|
+
* docs/components/build-archive.mdx.
|
|
27
|
+
*
|
|
28
|
+
* **Artifact-type-keyed generation.** "Generate an SBOM" has a different
|
|
29
|
+
* real implementation per artifact type — an image asks BuildKit or scans
|
|
30
|
+
* the built image with `syft`; a JAR is scanned with `syft` or
|
|
31
|
+
* `cyclonedx-maven`; a zip/Lambda package is scanned with `syft`; a bare
|
|
32
|
+
* source directory is scanned with `syft`/`cdxgen`. `SbomGenerator` is one
|
|
33
|
+
* interface with one method per artifact type — analogous to
|
|
34
|
+
* `PublishImageBackend` being one interface satisfied by two backends
|
|
35
|
+
* (./publish.ts) — so the *capability* (./sbom.ts) stays artifact-type
|
|
36
|
+
* agnostic and simply calls the method matching the archive entry it was
|
|
37
|
+
* handed.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// `lockfileSbomGenerator` (#630's hermetic default, below) lives in
|
|
41
|
+
// ./lockfile-sbom-generator.ts, which itself imports `DEFAULT_SBOM_FORMAT`/
|
|
42
|
+
// `SbomGeneratorNotImplementedError` from *this* module — a deliberate
|
|
43
|
+
// circular import. Safe here because the only use of `lockfileSbomGenerator`
|
|
44
|
+
// in this file is inside `defaultSbomGenerator()`'s function body (evaluated
|
|
45
|
+
// lazily, on first call), never at this module's own top-level/import time,
|
|
46
|
+
// so it doesn't matter which module's evaluation finishes first.
|
|
47
|
+
import { lockfileSbomGenerator } from "./lockfile-sbom-generator";
|
|
48
|
+
|
|
49
|
+
// ── the SBOM document ────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/** The two SBOM standards this module supports. Never hardcode one — consumers key off `mediaType`. */
|
|
52
|
+
export type SbomFormat = "spdx" | "cyclonedx";
|
|
53
|
+
|
|
54
|
+
/** Canonical media type per format, mirroring `BuildArchiveEntry.mediaType`'s convention (./build-archive.ts). */
|
|
55
|
+
export const SBOM_MEDIA_TYPES: Record<SbomFormat, string> = {
|
|
56
|
+
spdx: "application/spdx+json",
|
|
57
|
+
cyclonedx: "application/vnd.cyclonedx+json",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Project-wide default SBOM format when neither `chant.config.ts`'s `sbom.format` nor a component's `build.sbom.format` says otherwise. Documented in docs/components/build-archive.mdx. */
|
|
61
|
+
export const DEFAULT_SBOM_FORMAT: SbomFormat = "spdx";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The artifact types an `SbomGenerator` backend can be asked to scan —
|
|
65
|
+
* mirrors the build-family's artifact shapes (`docker-build` -> `image`,
|
|
66
|
+
* `jvm-build` -> `jar`, `zip-package` -> `zip`, a bare source tree -> `dir`),
|
|
67
|
+
* never a component/cloud name.
|
|
68
|
+
*/
|
|
69
|
+
export type SbomArtifactType = "image" | "jar" | "zip" | "dir";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* An SBOM as an opaque typed document: format, its media type (what every
|
|
73
|
+
* consumer actually keys off), and the serialized bytes. Never inspected or
|
|
74
|
+
* re-parsed by chant itself — chant's job is to generate, store, and surface
|
|
75
|
+
* it, not to interpret SBOM content (that is the scan-result/VEX policy-gate
|
|
76
|
+
* fast-follow, out of scope here — see #606's "out of scope" section).
|
|
77
|
+
*/
|
|
78
|
+
export interface SbomDocument {
|
|
79
|
+
/** Which standard this document is. Informational — consumers should key off `mediaType`, not this field, for anything that dispatches on the wire format. */
|
|
80
|
+
format: SbomFormat;
|
|
81
|
+
/** The document's IANA/OCI media type (`application/spdx+json` or `application/vnd.cyclonedx+json`). This is the format-agnostic key every consumer (build-archive manifest, build ledger, `chant components status`) actually reads. */
|
|
82
|
+
mediaType: string;
|
|
83
|
+
/** Serialized SBOM content (the actual SPDX/CycloneDX JSON document, as bytes/string). */
|
|
84
|
+
bytes: string;
|
|
85
|
+
/** Number of packages/components the SBOM enumerates, when the generator reports it — surfaced by `chant components status` without parsing `bytes`. */
|
|
86
|
+
packageCount?: number;
|
|
87
|
+
/** Which tool produced this document (e.g. "syft", "buildkit", "cyclonedx-maven") — surfaced alongside format/package count so `chant components status` can report SBOM *source*, not just presence. */
|
|
88
|
+
generator: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── per-artifact-type generator inputs ───────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
export interface GenerateImageSbomInput {
|
|
94
|
+
/** Archive-relative or local path to the image tarball (OCI layout, as `docker save` produces — see ./build.ts's `DockerBuildOutput.archivePath`). */
|
|
95
|
+
imagePath: string;
|
|
96
|
+
/** Image digest the SBOM is being generated for (the join key with the build-archive manifest / build ledger). */
|
|
97
|
+
digest: string;
|
|
98
|
+
format?: SbomFormat;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GenerateJarSbomInput {
|
|
102
|
+
/** Path to the built jar (see ./build.ts's `JvmBuildOutput.archivePath`). */
|
|
103
|
+
jarPath: string;
|
|
104
|
+
digest: string;
|
|
105
|
+
format?: SbomFormat;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface GenerateZipSbomInput {
|
|
109
|
+
/** Path to the built zip/Lambda package (see ./build.ts's `ZipPackageOutput.archivePath`). */
|
|
110
|
+
zipPath: string;
|
|
111
|
+
digest: string;
|
|
112
|
+
format?: SbomFormat;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface GenerateDirSbomInput {
|
|
116
|
+
/** Source directory to scan (a config-only/producer component with no packaged artifact, or a pre-build source scan). */
|
|
117
|
+
path: string;
|
|
118
|
+
format?: SbomFormat;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Injectable, artifact-type-keyed SBOM generation boundary — the SBOM-side
|
|
123
|
+
* analogue of `CloudExecutor` (./cloud-executor.ts). A real implementation
|
|
124
|
+
* shells out (BuildKit `docker buildx build --sbom=true`/`syft <target>
|
|
125
|
+
* -o spdx-json`/`cyclonedx-maven`/`cdxgen`); every method here is injected so
|
|
126
|
+
* tests substitute `MockSbomGenerator`
|
|
127
|
+
* (./__tests__/mock-sbom-generator.ts) and never invoke a real tool or the
|
|
128
|
+
* network.
|
|
129
|
+
*/
|
|
130
|
+
export interface SbomGenerator {
|
|
131
|
+
/** Generate (or attest) an SBOM for a built container image. A real implementation may prefer BuildKit's native `--sbom` attestation (defaults to SPDX, per the epic's refinement) or fall back to a `syft` scan of the saved tarball. */
|
|
132
|
+
forImage(input: GenerateImageSbomInput): Promise<SbomDocument>;
|
|
133
|
+
/** Generate an SBOM for a built JVM jar (`syft`/`cyclonedx-maven`). */
|
|
134
|
+
forJar(input: GenerateJarSbomInput): Promise<SbomDocument>;
|
|
135
|
+
/** Generate an SBOM for a built zip/Lambda deployment package (`syft` on the archive). */
|
|
136
|
+
forZip(input: GenerateZipSbomInput): Promise<SbomDocument>;
|
|
137
|
+
/** Generate an SBOM for a bare source directory (`syft`/`cdxgen`). */
|
|
138
|
+
forDir(input: GenerateDirSbomInput): Promise<SbomDocument>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Thrown by `NotImplementedSbomGenerator` — the "no real backend wired yet"
|
|
143
|
+
* signal, matching `CapabilityNotImplementedError`'s role for verb stubs
|
|
144
|
+
* (../capability.ts). Kept distinct from a generic `Error` so callers (and
|
|
145
|
+
* tests) can assert on it specifically.
|
|
146
|
+
*/
|
|
147
|
+
export class SbomGeneratorNotImplementedError extends Error {
|
|
148
|
+
constructor(public readonly method: string) {
|
|
149
|
+
super(
|
|
150
|
+
`SbomGenerator.${method} has no real implementation configured — inject a real generator ` +
|
|
151
|
+
`(shelling out to syft/buildx/cyclonedx-maven/cdxgen) or use a mock in tests`,
|
|
152
|
+
);
|
|
153
|
+
this.name = "SbomGeneratorNotImplementedError";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* All-throwing `SbomGenerator`: every method throws
|
|
159
|
+
* `SbomGeneratorNotImplementedError`. No longer the process-wide default (see
|
|
160
|
+
* `defaultSbomGenerator` below, #630) — kept exported for callers that
|
|
161
|
+
* deliberately want the loud, no-backend-at-all behavior (e.g. a test
|
|
162
|
+
* asserting on the thrown error itself, or a caller that wants to force
|
|
163
|
+
* "nothing is wired" rather than silently getting the hermetic backend).
|
|
164
|
+
*/
|
|
165
|
+
export const notImplementedSbomGenerator: SbomGenerator = {
|
|
166
|
+
async forImage(): Promise<SbomDocument> {
|
|
167
|
+
throw new SbomGeneratorNotImplementedError("forImage");
|
|
168
|
+
},
|
|
169
|
+
async forJar(): Promise<SbomDocument> {
|
|
170
|
+
throw new SbomGeneratorNotImplementedError("forJar");
|
|
171
|
+
},
|
|
172
|
+
async forZip(): Promise<SbomDocument> {
|
|
173
|
+
throw new SbomGeneratorNotImplementedError("forZip");
|
|
174
|
+
},
|
|
175
|
+
async forDir(): Promise<SbomDocument> {
|
|
176
|
+
throw new SbomGeneratorNotImplementedError("forDir");
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/** Lazily-constructed process-wide default, mirroring ./cloud-executor.ts's `defaultCloudExecutor`. */
|
|
181
|
+
let defaultGenerator: SbomGenerator | undefined;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The default `SbomGenerator` the `generate-sbom` capability falls back to
|
|
185
|
+
* when none is supplied.
|
|
186
|
+
*
|
|
187
|
+
* **Hermetic by default (#630).** This is `./lockfile-sbom-generator.ts`'s
|
|
188
|
+
* `lockfileSbomGenerator` — `forDir`/`forZip`/`forJar` parse a
|
|
189
|
+
* `package-lock.json`/`pom.xml` already on disk (no `syft`, no network, no
|
|
190
|
+
* external tool) and emit a real SPDX/CycloneDX document. That means a
|
|
191
|
+
* newcomer composing `generate-sbom` over a source directory, a zip/Lambda
|
|
192
|
+
* package, or a JAR gets a working SBOM the moment they `chant run
|
|
193
|
+
* --components`, with nothing to install first — the whole point of #630's
|
|
194
|
+
* "runnable proof" bar. `forImage` still throws
|
|
195
|
+
* `SbomGeneratorNotImplementedError`: no hermetic backend can see an image's
|
|
196
|
+
* base layers, so an image SBOM still needs the real deep-scan tool backend
|
|
197
|
+
* (`syft`/`docker buildx --sbom`/`cyclonedx-maven`/`cdxgen`, #610) — a
|
|
198
|
+
* documented, deliberate gap, not an oversight. This was previously
|
|
199
|
+
* `notImplementedSbomGenerator` (every method throwing); switching the
|
|
200
|
+
* default is intentional per #630 — the prior "loud failure by default"
|
|
201
|
+
* traded newcomer runnability for a default nobody exercised without opting
|
|
202
|
+
* in, which #613 already built a hermetic backend to fix but never wired up.
|
|
203
|
+
* A project that truly wants the old all-throwing behavior can construct
|
|
204
|
+
* `createGenerateSbomCapability(notImplementedSbomGenerator)` explicitly.
|
|
205
|
+
*/
|
|
206
|
+
export function defaultSbomGenerator(): SbomGenerator {
|
|
207
|
+
if (!defaultGenerator) defaultGenerator = lockfileSbomGenerator;
|
|
208
|
+
return defaultGenerator;
|
|
209
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createGenerateSbomCapability } from "./sbom";
|
|
3
|
+
import { createMockSbomGenerator } from "./__tests__/mock-sbom-generator";
|
|
4
|
+
import { addArchiveTemplate } from "./build";
|
|
5
|
+
import { findArchiveEntry, findSbomForSubject } from "./build-archive";
|
|
6
|
+
import { DEFAULT_SBOM_FORMAT, SBOM_MEDIA_TYPES } from "./sbom-generator";
|
|
7
|
+
|
|
8
|
+
const ctx = { env: "dev", component: "search-service" };
|
|
9
|
+
|
|
10
|
+
describe("generate-sbom (#606)", () => {
|
|
11
|
+
it("defaults to SPDX when no format is requested", async () => {
|
|
12
|
+
expect(DEFAULT_SBOM_FORMAT).toBe("spdx");
|
|
13
|
+
const mock = createMockSbomGenerator();
|
|
14
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
15
|
+
|
|
16
|
+
const output = await capability.run(ctx, {
|
|
17
|
+
artifactType: "image",
|
|
18
|
+
path: "archive/search.tar",
|
|
19
|
+
digest: "sha256:image1",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(output.sbom.format).toBe("spdx");
|
|
23
|
+
expect(output.sbom.mediaType).toBe("application/spdx+json");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("dispatches image artifacts to forImage (BuildKit/syft)", async () => {
|
|
27
|
+
const mock = createMockSbomGenerator();
|
|
28
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
29
|
+
|
|
30
|
+
await capability.run(ctx, { artifactType: "image", path: "archive/search.tar", digest: "sha256:image1" });
|
|
31
|
+
|
|
32
|
+
expect(mock.calls.map((c) => c.method)).toEqual(["forImage"]);
|
|
33
|
+
expect(mock.calls[0]!.args).toMatchObject({ imagePath: "archive/search.tar", digest: "sha256:image1" });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("dispatches jar artifacts to forJar (syft/cyclonedx-maven)", async () => {
|
|
37
|
+
const mock = createMockSbomGenerator();
|
|
38
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
39
|
+
|
|
40
|
+
const output = await capability.run(ctx, {
|
|
41
|
+
artifactType: "jar",
|
|
42
|
+
path: "archive/lib.jar",
|
|
43
|
+
digest: "sha256:jar1",
|
|
44
|
+
format: "cyclonedx",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(mock.calls.map((c) => c.method)).toEqual(["forJar"]);
|
|
48
|
+
expect(output.sbom.format).toBe("cyclonedx");
|
|
49
|
+
expect(output.sbom.mediaType).toBe("application/vnd.cyclonedx+json");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("dispatches zip artifacts to forZip (syft on the archive)", async () => {
|
|
53
|
+
const mock = createMockSbomGenerator();
|
|
54
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
55
|
+
|
|
56
|
+
await capability.run(ctx, { artifactType: "zip", path: "archive/fn.zip", digest: "sha256:zip1" });
|
|
57
|
+
|
|
58
|
+
expect(mock.calls.map((c) => c.method)).toEqual(["forZip"]);
|
|
59
|
+
expect(mock.calls[0]!.args).toMatchObject({ zipPath: "archive/fn.zip" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("dispatches dir artifacts to forDir (syft/cdxgen) — no artifact digest required", async () => {
|
|
63
|
+
const mock = createMockSbomGenerator();
|
|
64
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
65
|
+
|
|
66
|
+
const output = await capability.run(ctx, { artifactType: "dir", path: "src/" });
|
|
67
|
+
|
|
68
|
+
expect(mock.calls.map((c) => c.method)).toEqual(["forDir"]);
|
|
69
|
+
expect(mock.calls[0]!.args).toMatchObject({ path: "src/" });
|
|
70
|
+
expect(output.manifest.contents[0]!.subjectDigest).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("writes an sbom-kind entry into the build-archive manifest, linked to the subject digest", async () => {
|
|
74
|
+
const mock = createMockSbomGenerator();
|
|
75
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
76
|
+
|
|
77
|
+
const output = await capability.run(ctx, {
|
|
78
|
+
artifactType: "image",
|
|
79
|
+
path: "archive/search.tar",
|
|
80
|
+
digest: "sha256:image1",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(output.manifest.component).toBe("search-service");
|
|
84
|
+
const entry = findArchiveEntry(output.manifest, output.archivePath);
|
|
85
|
+
expect(entry).toMatchObject({
|
|
86
|
+
kind: "sbom",
|
|
87
|
+
digest: output.digest,
|
|
88
|
+
mediaType: SBOM_MEDIA_TYPES.spdx,
|
|
89
|
+
subjectDigest: "sha256:image1",
|
|
90
|
+
});
|
|
91
|
+
expect(findSbomForSubject(output.manifest, "sha256:image1")).toBe(entry);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("defaults the archive path to <artifact path>.sbom.json", async () => {
|
|
95
|
+
const mock = createMockSbomGenerator();
|
|
96
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
97
|
+
const output = await capability.run(ctx, {
|
|
98
|
+
artifactType: "image",
|
|
99
|
+
path: "archive/search.tar",
|
|
100
|
+
digest: "sha256:image1",
|
|
101
|
+
});
|
|
102
|
+
expect(output.archivePath).toBe("archive/search.tar.sbom.json");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("accepts an explicit `into` path", async () => {
|
|
106
|
+
const mock = createMockSbomGenerator();
|
|
107
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
108
|
+
const output = await capability.run(ctx, {
|
|
109
|
+
artifactType: "image",
|
|
110
|
+
path: "archive/search.tar",
|
|
111
|
+
digest: "sha256:image1",
|
|
112
|
+
into: "sbom/search.spdx.json",
|
|
113
|
+
});
|
|
114
|
+
expect(output.archivePath).toBe("sbom/search.spdx.json");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("accepts an archive: wiring reference the same way publish-image's from does", async () => {
|
|
118
|
+
const mock = createMockSbomGenerator();
|
|
119
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
120
|
+
await capability.run(ctx, { artifactType: "image", path: "archive:search.tar", digest: "sha256:image1" });
|
|
121
|
+
expect(mock.calls[0]!.args).toMatchObject({ imagePath: "search.tar" });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("accumulates onto a manifest threaded through from a prior build/template step", async () => {
|
|
125
|
+
const mock = createMockSbomGenerator();
|
|
126
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
127
|
+
|
|
128
|
+
const { manifest: templateManifest } = addArchiveTemplate({
|
|
129
|
+
path: "search.template.json",
|
|
130
|
+
content: JSON.stringify({ Resources: {} }),
|
|
131
|
+
});
|
|
132
|
+
const output = await capability.run(ctx, {
|
|
133
|
+
artifactType: "image",
|
|
134
|
+
path: "archive/search.tar",
|
|
135
|
+
digest: "sha256:image1",
|
|
136
|
+
manifest: templateManifest,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(output.manifest.contents.map((e) => e.kind).sort()).toEqual(["sbom", "template"]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("surfaces package count and generator name from the SbomDocument onto the archive entry", async () => {
|
|
143
|
+
const mock = createMockSbomGenerator({ packageCount: 42, generatorName: "syft-1.2.3" });
|
|
144
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
145
|
+
const output = await capability.run(ctx, {
|
|
146
|
+
artifactType: "image",
|
|
147
|
+
path: "archive/search.tar",
|
|
148
|
+
digest: "sha256:image1",
|
|
149
|
+
});
|
|
150
|
+
const entry = findArchiveEntry(output.manifest, output.archivePath)!;
|
|
151
|
+
expect(entry.packageCount).toBe(42);
|
|
152
|
+
expect(entry.generator).toBe("syft-1.2.3");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("two generations from identical inputs produce the same manifest digest (content-addressed)", async () => {
|
|
156
|
+
const mockA = createMockSbomGenerator();
|
|
157
|
+
const mockB = createMockSbomGenerator();
|
|
158
|
+
const outputA = await createGenerateSbomCapability(mockA.generator).run(ctx, {
|
|
159
|
+
artifactType: "image",
|
|
160
|
+
path: "archive/search.tar",
|
|
161
|
+
digest: "sha256:image1",
|
|
162
|
+
});
|
|
163
|
+
const outputB = await createGenerateSbomCapability(mockB.generator).run(ctx, {
|
|
164
|
+
artifactType: "image",
|
|
165
|
+
path: "archive/search.tar",
|
|
166
|
+
digest: "sha256:image1",
|
|
167
|
+
});
|
|
168
|
+
expect(outputA.digest).toBe(outputB.digest);
|
|
169
|
+
expect(outputA.manifest.manifestDigest).toBe(outputB.manifest.manifestDigest);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("surfaces a generation failure (e.g. scanner crash) as a rejected promise (never swallowed)", async () => {
|
|
173
|
+
const mock = createMockSbomGenerator({ fail: true });
|
|
174
|
+
const capability = createGenerateSbomCapability(mock.generator);
|
|
175
|
+
await expect(
|
|
176
|
+
capability.run(ctx, { artifactType: "image", path: "archive/search.tar", digest: "sha256:image1" }),
|
|
177
|
+
).rejects.toThrow(/sbom generation failed/);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("declares no rollback — an already-generated, content-addressed SBOM is not itself something to undo", () => {
|
|
181
|
+
const capability = createGenerateSbomCapability(createMockSbomGenerator().generator);
|
|
182
|
+
expect(capability.rollback).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `generate-sbom` capability (#606, epic #551 follow-up to #564's build
|
|
3
|
+
* archive / #568's build ledger — closes the "produce" side #568 explicitly
|
|
4
|
+
* deferred).
|
|
5
|
+
*
|
|
6
|
+
* One capability, artifact-type-keyed via the injectable `SbomGenerator`
|
|
7
|
+
* (./sbom-generator.ts — mirrors ./cloud-executor.ts's `CloudExecutor`
|
|
8
|
+
* pattern), so a build-producing component of *any* artifact type — image,
|
|
9
|
+
* JAR, zip/Lambda package, or a bare source directory for a config-only
|
|
10
|
+
* producer — generates its SBOM through the same verb. Never docker-only:
|
|
11
|
+
* dispatch is on `input.artifactType`, not on which build capability
|
|
12
|
+
* produced the artifact.
|
|
13
|
+
*
|
|
14
|
+
* Writes the result into the build-archive manifest as an `sbom`-kind entry
|
|
15
|
+
* (../verbs/build-archive.ts), content-addressed and linked to its subject
|
|
16
|
+
* artifact's digest via `subjectDigest` — the **universal home** for the
|
|
17
|
+
* SBOM regardless of artifact type or registry availability. Registering the
|
|
18
|
+
* SBOM as an OCI referrer for image artifacts at publish time is a separate,
|
|
19
|
+
* later concern (the publish step/registry tooling's job, see
|
|
20
|
+
* ../../lifecycle/build-ledger.ts's `ReferrerLookup` for the consume side);
|
|
21
|
+
* this capability only ever writes the archive-carried copy, so it works
|
|
22
|
+
* unconditionally — including the registry-less `load-image-on-host` path.
|
|
23
|
+
*
|
|
24
|
+
* Config-only / infra components have no built artifact and simply never
|
|
25
|
+
* compose this step into their `deploy`/`build` phase — "skips cleanly" is
|
|
26
|
+
* structural (no step to run), not a special case this module needs to
|
|
27
|
+
* detect. Opt-out is the same: a component that wants no SBOM omits the
|
|
28
|
+
* `generate-sbom` step from its composition.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { Capability } from "../capability";
|
|
32
|
+
import {
|
|
33
|
+
addArchiveEntry,
|
|
34
|
+
archiveRelativePath,
|
|
35
|
+
contentDigest,
|
|
36
|
+
createBuildArchiveManifest,
|
|
37
|
+
type BuildArchiveManifest,
|
|
38
|
+
} from "./build-archive";
|
|
39
|
+
import {
|
|
40
|
+
defaultSbomGenerator,
|
|
41
|
+
DEFAULT_SBOM_FORMAT,
|
|
42
|
+
type SbomArtifactType,
|
|
43
|
+
type SbomDocument,
|
|
44
|
+
type SbomFormat,
|
|
45
|
+
type SbomGenerator,
|
|
46
|
+
} from "./sbom-generator";
|
|
47
|
+
|
|
48
|
+
// ── generate-sbom ────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export interface GenerateSbomInput {
|
|
51
|
+
/** Which artifact type to generate an SBOM for — selects the `SbomGenerator` method dispatched to (image -> BuildKit/syft, jar -> syft/cyclonedx-maven, zip -> syft, dir -> syft/cdxgen). */
|
|
52
|
+
artifactType: SbomArtifactType;
|
|
53
|
+
/** Archive-relative (or local, for `dir`) path to the artifact being scanned — the archive entry's `path` for `image`/`jar`/`zip`, or a source directory for `dir`. An `archive:`-prefixed reference is accepted and stripped, matching `publish-image`'s `from` convention. */
|
|
54
|
+
path: string;
|
|
55
|
+
/** Digest of the artifact this SBOM describes (the `image`/`asset` archive entry's `digest`) — omitted for `dir` scans that precede any build (no artifact digest exists yet), in which case the SBOM entry's `subjectDigest` is left unset. */
|
|
56
|
+
digest?: string;
|
|
57
|
+
/** SBOM format to request. Defaults to `chant.config.ts`'s `sbom.format`, then `DEFAULT_SBOM_FORMAT` (SPDX) when neither this input nor project config says otherwise — see ./sbom-generator.ts. */
|
|
58
|
+
format?: SbomFormat;
|
|
59
|
+
/** Where the SBOM document is written inside the build archive (e.g. `"search.sbom.json"`). Defaults to `<path>.sbom.json`. */
|
|
60
|
+
into?: string;
|
|
61
|
+
/** Manifest to extend with this SBOM's entry, rather than starting a fresh one — the same accumulation convention `docker-build`'s `manifest` input uses (./build.ts), so a component's whole build phase (image + SBOM, or JAR + SBOM) shares one manifest. */
|
|
62
|
+
manifest?: BuildArchiveManifest;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface GenerateSbomOutput {
|
|
66
|
+
/** The generated SBOM document (format, media type, bytes, package count, generator) — see ./sbom-generator.ts. */
|
|
67
|
+
sbom: SbomDocument;
|
|
68
|
+
/** Where the SBOM was written inside the build archive. */
|
|
69
|
+
archivePath: string;
|
|
70
|
+
/** Content-addressed digest of the SBOM document's own bytes. */
|
|
71
|
+
digest: string;
|
|
72
|
+
/** The build archive's manifest, now including this SBOM's entry alongside whatever `input.manifest` already held. */
|
|
73
|
+
manifest: BuildArchiveManifest;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Default archive path for a generated SBOM when `into` is omitted: `<artifact path>.sbom.json`. */
|
|
77
|
+
function defaultSbomPath(artifactPath: string): string {
|
|
78
|
+
return `${archiveRelativePath(artifactPath)}.sbom.json`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Dispatch one `SbomGenerator` call by `artifactType` — the only place this
|
|
83
|
+
* capability branches on artifact type, matching how `selectPublishBackend`
|
|
84
|
+
* (./publish.ts) is the only place the publish family branches on backend
|
|
85
|
+
* kind. Never a component/cloud name, always an artifact shape.
|
|
86
|
+
*/
|
|
87
|
+
async function generate(
|
|
88
|
+
generator: SbomGenerator,
|
|
89
|
+
input: GenerateSbomInput,
|
|
90
|
+
artifactPath: string,
|
|
91
|
+
): Promise<SbomDocument> {
|
|
92
|
+
switch (input.artifactType) {
|
|
93
|
+
case "image":
|
|
94
|
+
return generator.forImage({ imagePath: artifactPath, digest: input.digest ?? "", format: input.format });
|
|
95
|
+
case "jar":
|
|
96
|
+
return generator.forJar({ jarPath: artifactPath, digest: input.digest ?? "", format: input.format });
|
|
97
|
+
case "zip":
|
|
98
|
+
return generator.forZip({ zipPath: artifactPath, digest: input.digest ?? "", format: input.format });
|
|
99
|
+
case "dir":
|
|
100
|
+
return generator.forDir({ path: artifactPath, format: input.format });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate an SBOM for a build-producing component's artifact (image, JAR,
|
|
106
|
+
* zip, or source directory) via the artifact-type-keyed `SbomGenerator`, and
|
|
107
|
+
* fold the result into the build-archive manifest as an `sbom`-kind entry
|
|
108
|
+
* linked to the subject artifact's digest. Format defaults to
|
|
109
|
+
* `DEFAULT_SBOM_FORMAT` (SPDX) when neither `input.format` nor the injected
|
|
110
|
+
* generator overrides it — see ./sbom-generator.ts's module doc for the
|
|
111
|
+
* full default-resolution story. `chant.config.ts`'s `sbom.format` (see
|
|
112
|
+
* ../../config.ts's `ChantConfig.sbom` and `resolveSbomFormat`) is resolved
|
|
113
|
+
* by the caller/orchestrator into `input.format` before this capability
|
|
114
|
+
* runs, the same way env config is resolved before any other capability's
|
|
115
|
+
* `run` (this module never imports ../../config.ts directly, matching how
|
|
116
|
+
* no other verb module does either).
|
|
117
|
+
*
|
|
118
|
+
* No rollback: an already-generated SBOM sitting in the archive (or, later,
|
|
119
|
+
* projected as a registry referrer) is immutable, content-addressed
|
|
120
|
+
* evidence — nothing to compensate, the same opt-out `docker-build` and
|
|
121
|
+
* `publish-image` already take for their own no-mutable-remote-state reason.
|
|
122
|
+
*/
|
|
123
|
+
export function createGenerateSbomCapability(
|
|
124
|
+
generator: SbomGenerator = defaultSbomGenerator(),
|
|
125
|
+
): Capability<GenerateSbomInput, GenerateSbomOutput> {
|
|
126
|
+
return {
|
|
127
|
+
kind: "generate-sbom",
|
|
128
|
+
async run(ctx, input) {
|
|
129
|
+
const artifactPath = archiveRelativePath(input.path);
|
|
130
|
+
const format = input.format ?? DEFAULT_SBOM_FORMAT;
|
|
131
|
+
const sbom = await generate(generator, { ...input, format }, artifactPath);
|
|
132
|
+
|
|
133
|
+
const into = input.into ?? defaultSbomPath(input.path);
|
|
134
|
+
const digest = contentDigest(sbom.bytes);
|
|
135
|
+
const base = input.manifest ?? createBuildArchiveManifest(ctx.component);
|
|
136
|
+
const manifest = addArchiveEntry(base, {
|
|
137
|
+
kind: "sbom",
|
|
138
|
+
path: into,
|
|
139
|
+
digest,
|
|
140
|
+
mediaType: sbom.mediaType,
|
|
141
|
+
subjectDigest: input.digest,
|
|
142
|
+
packageCount: sbom.packageCount,
|
|
143
|
+
generator: sbom.generator,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return { sbom, archivePath: into, digest, manifest };
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Default `generate-sbom` capability, backed by the process-wide default `SbomGenerator` (throws until a real backend is injected — see ./sbom-generator.ts's `notImplementedSbomGenerator`). */
|
|
152
|
+
export const generateSbomCapability: Capability<GenerateSbomInput, GenerateSbomOutput> = createGenerateSbomCapability();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createShellCapability } from "./shell";
|
|
3
|
+
import { createMockProcessRunner } from "./__tests__/mock-process-runner";
|
|
4
|
+
|
|
5
|
+
const ctx = { env: "dev", component: "svc" };
|
|
6
|
+
|
|
7
|
+
describe("shell (#557)", () => {
|
|
8
|
+
it("runs the command and returns its stdout with exit 0", async () => {
|
|
9
|
+
const mock = createMockProcessRunner({ responses: { "echo hi": "hi\n" } });
|
|
10
|
+
const out = await createShellCapability(mock.runner).run(ctx, { cmd: "echo hi", reason: "demo" });
|
|
11
|
+
expect(out).toEqual({ stdout: "hi\n", exitCode: 0 });
|
|
12
|
+
expect(mock.calls.at(-1)?.command).toBe("echo hi");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("prefixes env vars and passes cwd through", async () => {
|
|
16
|
+
const mock = createMockProcessRunner();
|
|
17
|
+
await createShellCapability(mock.runner).run(ctx, {
|
|
18
|
+
cmd: "deploy.sh",
|
|
19
|
+
cwd: "/app",
|
|
20
|
+
env: { FOO: "bar" },
|
|
21
|
+
reason: "vendor CLI not yet wrapped by a capability",
|
|
22
|
+
});
|
|
23
|
+
const call = mock.calls.at(-1)!;
|
|
24
|
+
expect(call.command).toBe("env FOO='bar' deploy.sh");
|
|
25
|
+
expect(call.options?.cwd).toBe("/app");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("rejects when the command exits non-zero (surfaces the tool's error)", async () => {
|
|
29
|
+
const mock = createMockProcessRunner({ failures: { "bad-cmd": "boom" } });
|
|
30
|
+
await expect(createShellCapability(mock.runner).run(ctx, { cmd: "bad-cmd", reason: "x" })).rejects.toThrow("boom");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("declares no rollback — an escape-hatch shell owns its own compensation", () => {
|
|
34
|
+
expect(createShellCapability(createMockProcessRunner().runner).rollback).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* escape hatch family — `shell`. Typed, but flagged: a raw shell step run by
|
|
3
|
+
* exactly one component is the "capability-per-component" smell the model
|
|
4
|
+
* exists to avoid (see docs/components/capabilities.mdx#the-discipline).
|
|
5
|
+
* `reason` is required so lint (COMP006) can flag an escape hatch used without
|
|
6
|
+
* a stated justification.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Capability } from "../capability";
|
|
10
|
+
import { defaultProcessRunner, q, type ProcessRunner } from "./process-runner";
|
|
11
|
+
|
|
12
|
+
export interface ShellInput {
|
|
13
|
+
/** Command to run. */
|
|
14
|
+
cmd: string;
|
|
15
|
+
/** Working directory. Default: process cwd. */
|
|
16
|
+
cwd?: string;
|
|
17
|
+
/** Additional environment variables. */
|
|
18
|
+
env?: Record<string, string>;
|
|
19
|
+
/** Required justification for reaching past the capability set — lint-checked. */
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ShellOutput {
|
|
24
|
+
/** Captured stdout. */
|
|
25
|
+
stdout: string;
|
|
26
|
+
/** Process exit code. Always 0 on success — a non-zero exit rejects the run. */
|
|
27
|
+
exitCode: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run an arbitrary shell command through the injectable {@link ProcessRunner}.
|
|
32
|
+
* The escape hatch — typed, lint-flagged, and requires a `reason`. A non-zero
|
|
33
|
+
* exit rejects (with the command's own stderr), failing the step like any other
|
|
34
|
+
* capability error, so `onFailure`/rollback fires.
|
|
35
|
+
*/
|
|
36
|
+
export function createShellCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<ShellInput, ShellOutput> {
|
|
37
|
+
return {
|
|
38
|
+
kind: "shell",
|
|
39
|
+
async run(_ctx, input) {
|
|
40
|
+
const envPrefix =
|
|
41
|
+
input.env && Object.keys(input.env).length > 0
|
|
42
|
+
? `env ${Object.entries(input.env).map(([k, v]) => `${k}=${q(v)}`).join(" ")} `
|
|
43
|
+
: "";
|
|
44
|
+
const { stdout } = await processRunner.run(`${envPrefix}${input.cmd}`, { cwd: input.cwd });
|
|
45
|
+
return { stdout, exitCode: 0 };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Default `shell` capability, backed by the real process runner. */
|
|
51
|
+
export const shellCapability: Capability<ShellInput, ShellOutput> = createShellCapability();
|