@intentius/chant 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/catalog.d.ts +45 -5
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/fetch.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 +56 -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/fetch.test.ts +112 -8
- package/src/audit/fetch.ts +53 -8
- 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 +92 -0
- package/src/op/index.ts +3 -1
- package/src/ownership.test.ts +24 -26
- package/src/ownership.ts +26 -38
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build family — source -> BuildArchive, keyed by artifact type.
|
|
3
|
+
* Fully cloud-agnostic (see docs/components/cloud-boundary). `docker-build` is
|
|
4
|
+
* a real implementation (#557, epic #551) built over the injectable
|
|
5
|
+
* `CloudExecutor` (./cloud-executor.ts) so tests never shell out to a real
|
|
6
|
+
* `docker` daemon. `zip-package` (via `zip`) and `jvm-build` (via `mvn`/the
|
|
7
|
+
* gradle wrapper) are real, run through the injectable `ProcessRunner`.
|
|
8
|
+
*
|
|
9
|
+
* `docker-build` also assembles the component's `BuildArchiveManifest`
|
|
10
|
+
* (#564, epic #551 "4. Build archive + deferred publish" — see
|
|
11
|
+
* ./build-archive.ts): every build appends an `image`-kind entry recording
|
|
12
|
+
* the archive path and digest it just produced, so the archive is
|
|
13
|
+
* self-describing (content-addressed, with a manifest of contents) rather
|
|
14
|
+
* than a bare tarball. `addArchiveTemplate` lets a caller fold synthesized
|
|
15
|
+
* deploy templates (e.g. CloudFormation JSON, already written into the
|
|
16
|
+
* archive by chant's existing synthesis step) into the same manifest, so one
|
|
17
|
+
* document enumerates every content kind the archive holds.
|
|
18
|
+
*
|
|
19
|
+
* Per #614, both entry points accept an optional `sourceRef` (a git sha, or
|
|
20
|
+
* `"<sha>:<path>"` for a monorepo subpath) that becomes the entry's
|
|
21
|
+
* `provenance` link. `image`/`template` entries otherwise default to their
|
|
22
|
+
* kind-appropriate `reproducibility` basis via `addArchiveEntry` — see
|
|
23
|
+
* ./reproducibility.ts.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { Capability } from "../capability";
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { defaultCloudExecutor, type CloudExecutor } from "./cloud-executor";
|
|
30
|
+
import { defaultProcessRunner, q, type ProcessRunner } from "./process-runner";
|
|
31
|
+
import {
|
|
32
|
+
addArchiveEntry,
|
|
33
|
+
contentDigest,
|
|
34
|
+
createBuildArchiveManifest,
|
|
35
|
+
type BuildArchiveManifest,
|
|
36
|
+
} from "./build-archive";
|
|
37
|
+
import type { ProvenanceLink } from "./reproducibility";
|
|
38
|
+
|
|
39
|
+
// ── docker-build ────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export interface DockerBuildInput {
|
|
42
|
+
/** Build context directory. */
|
|
43
|
+
context: string;
|
|
44
|
+
/** Path to the Dockerfile, relative to `context`. Default: "Dockerfile". */
|
|
45
|
+
dockerfile?: string;
|
|
46
|
+
/** Build args passed to `docker build --build-arg`. */
|
|
47
|
+
buildArgs?: Record<string, string>;
|
|
48
|
+
/** Target stage for a multi-stage build. */
|
|
49
|
+
target?: string;
|
|
50
|
+
/** Where the produced image tarball is written inside the build archive. */
|
|
51
|
+
into: string;
|
|
52
|
+
/**
|
|
53
|
+
* Manifest to extend with this build's `image` entry, rather than starting
|
|
54
|
+
* a fresh one. Pass the manifest returned by a prior build/template step in
|
|
55
|
+
* the same archive (e.g. via `@Build.manifest` wiring) to accumulate one
|
|
56
|
+
* manifest across a component's whole build phase. Omit to start a new
|
|
57
|
+
* single-entry manifest for this build alone.
|
|
58
|
+
*/
|
|
59
|
+
manifest?: BuildArchiveManifest;
|
|
60
|
+
/** Source ref/commit this build was run against (#614) — recorded as the entry's `provenance.sourceRef`. Omit when unknown (e.g. a build run outside a git checkout); no `provenance` is recorded in that case. */
|
|
61
|
+
sourceRef?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface DockerBuildOutput {
|
|
65
|
+
/** Path of the produced image tarball (OCI layout) inside the build archive. */
|
|
66
|
+
archivePath: string;
|
|
67
|
+
/** Content-addressed digest of the built image (`sha256:...`). */
|
|
68
|
+
digest: string;
|
|
69
|
+
/** The build archive's manifest, now including this image's entry — content-addressed via `manifest.manifestDigest`. */
|
|
70
|
+
manifest: BuildArchiveManifest;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build a container image from a Dockerfile into the build archive: `docker
|
|
75
|
+
* build`, tagged with a build-local reference derived from `into`, then
|
|
76
|
+
* `docker save`d to the archive path (OCI layout, per `docker save`'s output
|
|
77
|
+
* format). Records the produced tarball as an `image` entry in the archive's
|
|
78
|
+
* manifest (extending `input.manifest` when the caller threads one through,
|
|
79
|
+
* so a component with more than one image build accumulates a single
|
|
80
|
+
* manifest for the whole archive). No rollback — a local build produces no
|
|
81
|
+
* remote/mutable state to compensate; the archive path is simply not consumed
|
|
82
|
+
* further if a later step fails.
|
|
83
|
+
*/
|
|
84
|
+
export function createDockerBuildCapability(
|
|
85
|
+
executor: CloudExecutor = defaultCloudExecutor(),
|
|
86
|
+
): Capability<DockerBuildInput, DockerBuildOutput> {
|
|
87
|
+
return {
|
|
88
|
+
kind: "docker-build",
|
|
89
|
+
async run(ctx, input) {
|
|
90
|
+
const tag = `chant-build/${input.into.replace(/[^a-zA-Z0-9_.-]/g, "-")}:latest`;
|
|
91
|
+
const { digest } = await executor.docker.build({
|
|
92
|
+
context: input.context,
|
|
93
|
+
dockerfile: input.dockerfile,
|
|
94
|
+
buildArgs: input.buildArgs,
|
|
95
|
+
target: input.target,
|
|
96
|
+
tag,
|
|
97
|
+
});
|
|
98
|
+
await executor.docker.save({ image: tag, outFile: input.into });
|
|
99
|
+
const base = input.manifest ?? createBuildArchiveManifest(ctx.component);
|
|
100
|
+
const provenance: ProvenanceLink | undefined = input.sourceRef
|
|
101
|
+
? { sourceRef: input.sourceRef, artifactDigest: digest }
|
|
102
|
+
: undefined;
|
|
103
|
+
const manifest = addArchiveEntry(base, {
|
|
104
|
+
kind: "image",
|
|
105
|
+
path: input.into,
|
|
106
|
+
digest,
|
|
107
|
+
...(provenance ? { provenance } : {}),
|
|
108
|
+
});
|
|
109
|
+
return { archivePath: input.into, digest, manifest };
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Default `docker-build` capability, backed by the real `CloudExecutor`. */
|
|
115
|
+
export const dockerBuildCapability: Capability<DockerBuildInput, DockerBuildOutput> = createDockerBuildCapability();
|
|
116
|
+
|
|
117
|
+
// ── archive templates ────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export interface AddArchiveTemplateInput {
|
|
120
|
+
/** Where the synthesized template is written inside the build archive (e.g. `"search.template.json"`). */
|
|
121
|
+
path: string;
|
|
122
|
+
/** Serialized template content — its digest is computed from these exact bytes, so an unrelated re-synthesis with identical content keeps the same manifest digest. */
|
|
123
|
+
content: string;
|
|
124
|
+
/** Manifest to extend. Omit to start a new manifest holding just this template. */
|
|
125
|
+
manifest?: BuildArchiveManifest;
|
|
126
|
+
/** Source ref/commit this template was synthesized from (#614) — recorded as the entry's `provenance.sourceRef`. Omit when unknown; no `provenance` is recorded in that case. */
|
|
127
|
+
sourceRef?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface AddArchiveTemplateOutput {
|
|
131
|
+
/** Content-addressed digest computed over `content`. */
|
|
132
|
+
digest: string;
|
|
133
|
+
/** The build archive's manifest, now including this template's entry. */
|
|
134
|
+
manifest: BuildArchiveManifest;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fold one synthesized deploy template (e.g. the CloudFormation JSON chant's
|
|
139
|
+
* existing synthesis already produces) into a build archive's manifest as a
|
|
140
|
+
* `template`-kind entry, alongside the image(s) `docker-build` recorded. Not
|
|
141
|
+
* a registered capability — template synthesis is chant's existing `build`
|
|
142
|
+
* pipeline (see ../../build.ts), not a new deploy-time verb — so this is a
|
|
143
|
+
* plain function a build orchestration step calls directly to keep one
|
|
144
|
+
* manifest across every content kind the archive holds (image + templates).
|
|
145
|
+
* Exported alongside the build-family capabilities since it is the other
|
|
146
|
+
* half of "what goes in the manifest" `docker-build` documents above.
|
|
147
|
+
*/
|
|
148
|
+
export function addArchiveTemplate(input: AddArchiveTemplateInput): AddArchiveTemplateOutput {
|
|
149
|
+
const digest = contentDigest(input.content);
|
|
150
|
+
const base = input.manifest ?? createBuildArchiveManifest("unknown");
|
|
151
|
+
const provenance: ProvenanceLink | undefined = input.sourceRef
|
|
152
|
+
? { sourceRef: input.sourceRef, artifactDigest: digest }
|
|
153
|
+
: undefined;
|
|
154
|
+
const manifest = addArchiveEntry(base, {
|
|
155
|
+
kind: "template",
|
|
156
|
+
path: input.path,
|
|
157
|
+
digest,
|
|
158
|
+
...(provenance ? { provenance } : {}),
|
|
159
|
+
});
|
|
160
|
+
return { digest, manifest };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── zip-package ─────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
export interface ZipPackageInput {
|
|
166
|
+
/** Directory (or file list) to package. */
|
|
167
|
+
source: string;
|
|
168
|
+
/** Glob patterns to exclude from the archive. */
|
|
169
|
+
exclude?: string[];
|
|
170
|
+
/** Where the produced zip is written inside the build archive. */
|
|
171
|
+
into: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ZipPackageOutput {
|
|
175
|
+
/** Path of the produced zip inside the build archive. */
|
|
176
|
+
archivePath: string;
|
|
177
|
+
/** Content hash of the zip (for promote-by-digest style referencing). */
|
|
178
|
+
digest: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Package a directory into a zip artifact (e.g. a Lambda deployment package)
|
|
183
|
+
* inside the build archive, via the `zip` CLI through the injectable
|
|
184
|
+
* `ProcessRunner`. Removes any prior `into` first so the archive is rebuilt from
|
|
185
|
+
* scratch (not appended to), then returns its content digest for
|
|
186
|
+
* promote-by-digest referencing.
|
|
187
|
+
*/
|
|
188
|
+
export function createZipPackageCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<ZipPackageInput, ZipPackageOutput> {
|
|
189
|
+
return {
|
|
190
|
+
kind: "zip-package",
|
|
191
|
+
async run(_ctx, input) {
|
|
192
|
+
const excludes = (input.exclude ?? []).map((p) => `-x ${q(p)}`).join(" ");
|
|
193
|
+
// `-X` drops extra file attributes; `rm -f` first so a re-run rebuilds rather than updates in place.
|
|
194
|
+
await processRunner.run(`rm -f ${q(input.into)} && zip -r -X ${q(input.into)} ${q(input.source)}${excludes ? ` ${excludes}` : ""}`);
|
|
195
|
+
const digest = `sha256:${createHash("sha256").update(readFileSync(input.into)).digest("hex")}`;
|
|
196
|
+
return { archivePath: input.into, digest };
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Default `zip-package` capability, backed by the real process runner. */
|
|
202
|
+
export const zipPackageCapability: Capability<ZipPackageInput, ZipPackageOutput> = createZipPackageCapability();
|
|
203
|
+
|
|
204
|
+
// ── jvm-build ────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
export interface JvmBuildInput {
|
|
207
|
+
/** Build tool driving the compile/package step. */
|
|
208
|
+
tool: "maven" | "gradle";
|
|
209
|
+
/** Project directory containing the build file. */
|
|
210
|
+
path: string;
|
|
211
|
+
/** Build tool goals/tasks to run. Default: tool-appropriate package goal. */
|
|
212
|
+
goals?: string[];
|
|
213
|
+
/** Where the produced jar is written inside the build archive. */
|
|
214
|
+
into: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface JvmBuildOutput {
|
|
218
|
+
/** Path of the produced jar inside the build archive. */
|
|
219
|
+
archivePath: string;
|
|
220
|
+
/** Content hash of the jar. */
|
|
221
|
+
digest: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Compile and package a JVM project (Maven or Gradle) into a jar in the build
|
|
226
|
+
* archive, via the build tool through the injectable `ProcessRunner`. Runs the
|
|
227
|
+
* package goal, copies the produced jar (skipping `-sources`/`-javadoc`
|
|
228
|
+
* classifiers) to `into`, and returns its content digest.
|
|
229
|
+
*/
|
|
230
|
+
export function createJvmBuildCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<JvmBuildInput, JvmBuildOutput> {
|
|
231
|
+
return {
|
|
232
|
+
kind: "jvm-build",
|
|
233
|
+
async run(_ctx, input) {
|
|
234
|
+
if (input.tool === "gradle") {
|
|
235
|
+
const tasks = (input.goals ?? ["build"]).join(" ");
|
|
236
|
+
await processRunner.run(`./gradlew -q ${tasks}`, { cwd: input.path });
|
|
237
|
+
await processRunner.run(`cp "$(ls ${q(input.path)}/build/libs/*.jar | head -1)" ${q(input.into)}`);
|
|
238
|
+
} else {
|
|
239
|
+
const goals = (input.goals ?? ["package"]).join(" ");
|
|
240
|
+
await processRunner.run(`mvn -q -B -f ${q(`${input.path}/pom.xml`)} ${goals}`);
|
|
241
|
+
await processRunner.run(
|
|
242
|
+
`cp "$(ls ${q(`${input.path}/target`)}/*.jar | grep -Ev '(-sources|-javadoc)\\.jar$' | head -1)" ${q(input.into)}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const digest = `sha256:${createHash("sha256").update(readFileSync(input.into)).digest("hex")}`;
|
|
246
|
+
return { archivePath: input.into, digest };
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Default `jvm-build` capability, backed by the real process runner. */
|
|
252
|
+
export const jvmBuildCapability: Capability<JvmBuildInput, JvmBuildOutput> = createJvmBuildCapability();
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable cloud I/O boundary for the *agnostic* capability implementations
|
|
3
|
+
* that remain in core: the `docker` build/push client and the Neo4j bolt-port
|
|
4
|
+
* probe. Every capability here that talks to a real tool goes through a
|
|
5
|
+
* `CloudExecutor` instead of shelling out directly, so production code gets a
|
|
6
|
+
* real, `child_process`/`net`-backed executor and tests get a
|
|
7
|
+
* `MockCloudExecutor` (./__tests__/mock-cloud-executor.ts) that records calls
|
|
8
|
+
* and returns canned results — no live docker, ever, in a test run.
|
|
9
|
+
*
|
|
10
|
+
* The AWS-specific clients (CloudFormation, ECS, Lambda, EMR, CodeDeploy, SSM
|
|
11
|
+
* host, S3, CloudFront, snapshot, ECR) used to live here too. They now live in
|
|
12
|
+
* the aws lexicon (`@intentius/chant-lexicon-aws/components`), contributed
|
|
13
|
+
* through the capability-plugin seam — see docs/components/cloud-boundary. The
|
|
14
|
+
* `docker` client stays in core because it is genuinely cloud-agnostic and
|
|
15
|
+
* shared: `docker-build` (agnostic) uses it here, and the aws lexicon's
|
|
16
|
+
* `publish-image` reuses the exported `realDocker` rather than duplicating it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { exec } from "node:child_process";
|
|
20
|
+
import { createConnection } from "node:net";
|
|
21
|
+
import { promisify } from "node:util";
|
|
22
|
+
|
|
23
|
+
export const execFileAsync = promisify(exec);
|
|
24
|
+
|
|
25
|
+
// ── docker ────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export interface DockerBuildArgs {
|
|
28
|
+
context: string;
|
|
29
|
+
dockerfile?: string;
|
|
30
|
+
buildArgs?: Record<string, string>;
|
|
31
|
+
target?: string;
|
|
32
|
+
/** Tag applied to the built image so it can be referenced by name (e.g. saved to a tarball). */
|
|
33
|
+
tag: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DockerSaveArgs {
|
|
37
|
+
/** Image reference to save (tag or digest). */
|
|
38
|
+
image: string;
|
|
39
|
+
/** Destination tarball path. */
|
|
40
|
+
outFile: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DockerLoadArgs {
|
|
44
|
+
/** Tarball path to load. */
|
|
45
|
+
inFile: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DockerTagArgs {
|
|
49
|
+
source: string;
|
|
50
|
+
target: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DockerPushArgs {
|
|
54
|
+
/** Fully qualified image reference to push (registry/repo:tag). */
|
|
55
|
+
image: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DockerClient {
|
|
59
|
+
/** Build an image from a Dockerfile; returns the built image's content digest. */
|
|
60
|
+
build(args: DockerBuildArgs): Promise<{ digest: string }>;
|
|
61
|
+
/** Save an image to an OCI/tarball archive on disk. */
|
|
62
|
+
save(args: DockerSaveArgs): Promise<void>;
|
|
63
|
+
/** Load an image tarball into the local docker store; returns the loaded image's digest. */
|
|
64
|
+
load(args: DockerLoadArgs): Promise<{ digest: string }>;
|
|
65
|
+
/** Tag a local image with an additional reference (e.g. before push). */
|
|
66
|
+
tag(args: DockerTagArgs): Promise<void>;
|
|
67
|
+
/** Push a tagged image to its registry; returns the pushed image's registry digest. */
|
|
68
|
+
push(args: DockerPushArgs): Promise<{ digest: string }>;
|
|
69
|
+
/** Inspect a remote image's manifest digest without pulling it (used to verify a promoted tag). */
|
|
70
|
+
remoteDigest(image: string): Promise<string>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Neo4j cluster probe (bolt) ───────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export interface Neo4jProbeArgs {
|
|
76
|
+
/** Cluster identifier — a comma-separated list of `host:port` bolt endpoints. */
|
|
77
|
+
cluster: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface Neo4jClusterClient {
|
|
81
|
+
/** Probe every member's bolt port; returns how many are reachable/healthy. */
|
|
82
|
+
probe(args: Neo4jProbeArgs): Promise<{ healthyCount: number; total: number }>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── the agnostic executor surface ────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
export interface CloudExecutor {
|
|
88
|
+
docker: DockerClient;
|
|
89
|
+
neo4j: Neo4jClusterClient;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Real executor — shells out to `docker`; used outside tests ───────────────
|
|
93
|
+
|
|
94
|
+
/** Run a shell command, capturing stdout/stderr. Internal to the docker client; the aws lexicon's executor has its own (endpoint-aware) runner. */
|
|
95
|
+
function run(command: string): Promise<{ stdout: string; stderr: string }> {
|
|
96
|
+
return execFileAsync(command, { maxBuffer: 64 * 1024 * 1024 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Shell-quote a single argument for POSIX shells (wrap in single quotes, escaping embedded ones). Internal; the shared, exported `q` lives in ./process-runner. */
|
|
100
|
+
function q(value: string): string {
|
|
101
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const realDocker: DockerClient = {
|
|
105
|
+
async build(args) {
|
|
106
|
+
const parts = [`docker build`, `-t ${q(args.tag)}`];
|
|
107
|
+
if (args.dockerfile) parts.push(`-f ${q(args.dockerfile)}`);
|
|
108
|
+
if (args.target) parts.push(`--target ${q(args.target)}`);
|
|
109
|
+
for (const [k, v] of Object.entries(args.buildArgs ?? {})) parts.push(`--build-arg ${q(`${k}=${v}`)}`);
|
|
110
|
+
parts.push(q(args.context));
|
|
111
|
+
await run(parts.join(" "));
|
|
112
|
+
const { stdout } = await run(`docker image inspect ${q(args.tag)} --format '{{.Id}}'`);
|
|
113
|
+
return { digest: stdout.trim() };
|
|
114
|
+
},
|
|
115
|
+
async save(args) {
|
|
116
|
+
await run(`docker save ${q(args.image)} -o ${q(args.outFile)}`);
|
|
117
|
+
},
|
|
118
|
+
async load(args) {
|
|
119
|
+
const { stdout } = await run(`docker load -i ${q(args.inFile)}`);
|
|
120
|
+
const match = stdout.match(/[Ll]oaded image(?: ID)?:\s*(\S+)/);
|
|
121
|
+
return { digest: match?.[1] ?? stdout.trim() };
|
|
122
|
+
},
|
|
123
|
+
async tag(args) {
|
|
124
|
+
await run(`docker tag ${q(args.source)} ${q(args.target)}`);
|
|
125
|
+
},
|
|
126
|
+
async push(args) {
|
|
127
|
+
await run(`docker push ${q(args.image)}`);
|
|
128
|
+
const { stdout } = await run(`docker image inspect ${q(args.image)} --format '{{index .RepoDigests 0}}'`);
|
|
129
|
+
return { digest: stdout.trim() };
|
|
130
|
+
},
|
|
131
|
+
async remoteDigest(image) {
|
|
132
|
+
const { stdout } = await run(`docker manifest inspect -v ${q(image)}`);
|
|
133
|
+
return stdout.trim();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const realNeo4j: Neo4jClusterClient = {
|
|
138
|
+
async probe(args) {
|
|
139
|
+
const endpoints = args.cluster
|
|
140
|
+
.split(",")
|
|
141
|
+
.map((s) => s.trim())
|
|
142
|
+
.filter(Boolean);
|
|
143
|
+
const results = await Promise.all(endpoints.map((endpoint) => probeBoltPort(endpoint)));
|
|
144
|
+
return { healthyCount: results.filter(Boolean).length, total: endpoints.length };
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Probe one `host:port` bolt endpoint by opening (and immediately closing) a TCP socket. */
|
|
149
|
+
function probeBoltPort(endpoint: string, timeoutMs = 5000): Promise<boolean> {
|
|
150
|
+
const [host, portStr] = endpoint.split(":");
|
|
151
|
+
const port = Number(portStr ?? 7687);
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const socket = createConnection({ host, port, timeout: timeoutMs });
|
|
154
|
+
const finish = (healthy: boolean) => {
|
|
155
|
+
socket.destroy();
|
|
156
|
+
resolve(healthy);
|
|
157
|
+
};
|
|
158
|
+
socket.once("connect", () => finish(true));
|
|
159
|
+
socket.once("timeout", () => finish(false));
|
|
160
|
+
socket.once("error", () => finish(false));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Build a `CloudExecutor` that shells out to the real `docker` CLI and probes real bolt ports. Never used in tests. */
|
|
165
|
+
export function realCloudExecutor(): CloudExecutor {
|
|
166
|
+
return { docker: realDocker, neo4j: realNeo4j };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Lazily-constructed process-wide default so importing a capability module never shells out at import time. */
|
|
170
|
+
let defaultExecutor: CloudExecutor | undefined;
|
|
171
|
+
|
|
172
|
+
/** The default `CloudExecutor` each capability factory falls back to when none is supplied. */
|
|
173
|
+
export function defaultCloudExecutor(): CloudExecutor {
|
|
174
|
+
if (!defaultExecutor) defaultExecutor = realCloudExecutor();
|
|
175
|
+
return defaultExecutor;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Sleep for `ms`. Shared by every polling capability (`wait-*`) between attempts. */
|
|
179
|
+
export function sleep(ms: number): Promise<void> {
|
|
180
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests `aggregateComponentBom` (#614) — the component-level BOM aggregation
|
|
3
|
+
* that composes a component's leaf BOMs (a software SBOM + an IaC
|
|
4
|
+
* config-BOM) into one component BOM. Structural validation against the
|
|
5
|
+
* real, vendored SPDX-2.3/CycloneDX-1.5 JSON Schemas, same convention as
|
|
6
|
+
* ./bom-writer.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
import Ajv from "ajv";
|
|
13
|
+
import { aggregateComponentBom, componentBomLeavesFromManifest, type ComponentBomLeaf } from "./component-bom";
|
|
14
|
+
import { addArchiveEntry, createBuildArchiveManifest, findArchiveEntry } from "./build-archive";
|
|
15
|
+
import type { BomPackage } from "./bom-writer";
|
|
16
|
+
|
|
17
|
+
const SCHEMAS_DIR = join(import.meta.dirname, "__fixtures__/schemas");
|
|
18
|
+
function loadSchema(name: string): unknown {
|
|
19
|
+
return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), "utf-8"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const FIXED_NOW = () => new Date("2026-01-01T00:00:00.000Z");
|
|
23
|
+
|
|
24
|
+
/** Build a manifest for "search-service": an image + a software SBOM leaf, a template + a config-BOM leaf. */
|
|
25
|
+
function multiArtifactManifest() {
|
|
26
|
+
let manifest = createBuildArchiveManifest("search-service", { now: FIXED_NOW });
|
|
27
|
+
manifest = addArchiveEntry(manifest, { kind: "image", path: "image.tar", digest: "sha256:img1" });
|
|
28
|
+
manifest = addArchiveEntry(manifest, { kind: "template", path: "search.template.json", digest: "sha256:tmpl1" });
|
|
29
|
+
manifest = addArchiveEntry(manifest, {
|
|
30
|
+
kind: "sbom",
|
|
31
|
+
bomKind: "software",
|
|
32
|
+
path: "image.tar.sbom.json",
|
|
33
|
+
digest: "sha256:sbomsoft",
|
|
34
|
+
subjectDigest: "sha256:img1",
|
|
35
|
+
mediaType: "application/spdx+json",
|
|
36
|
+
packageCount: 1,
|
|
37
|
+
generator: "chant-lockfile-sbom/package-lock.json",
|
|
38
|
+
});
|
|
39
|
+
manifest = addArchiveEntry(manifest, {
|
|
40
|
+
kind: "sbom",
|
|
41
|
+
bomKind: "config",
|
|
42
|
+
path: "search.template.json.config-bom.json",
|
|
43
|
+
digest: "sha256:sbomconfig",
|
|
44
|
+
subjectDigest: "sha256:tmpl1",
|
|
45
|
+
mediaType: "application/spdx+json",
|
|
46
|
+
packageCount: 1,
|
|
47
|
+
generator: "chant-config-bom-extractor",
|
|
48
|
+
});
|
|
49
|
+
return manifest;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function multiArtifactLeaves(manifest: ReturnType<typeof multiArtifactManifest>): ComponentBomLeaf[] {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
entry: findArchiveEntry(manifest, "image.tar.sbom.json")!,
|
|
56
|
+
packages: [{ name: "left-pad", version: "1.3.0", type: "npm" } satisfies BomPackage],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
entry: findArchiveEntry(manifest, "search.template.json.config-bom.json")!,
|
|
60
|
+
packages: [{ name: "SearchBucket", version: "AWS::S3::Bucket", type: "config" } satisfies BomPackage],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("aggregateComponentBom (#614)", () => {
|
|
66
|
+
describe("multi-artifact component — a real assembly", () => {
|
|
67
|
+
it("SPDX: composes both leaves into one valid document", () => {
|
|
68
|
+
const manifest = multiArtifactManifest();
|
|
69
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
70
|
+
const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
|
|
71
|
+
|
|
72
|
+
expect(result.leafCount).toBe(2);
|
|
73
|
+
expect(result.totalPackageCount).toBe(2);
|
|
74
|
+
expect(result.bom.format).toBe("spdx");
|
|
75
|
+
|
|
76
|
+
const doc = JSON.parse(result.bom.bytes) as Record<string, unknown>;
|
|
77
|
+
const schema = loadSchema("spdx-2.3.schema.json");
|
|
78
|
+
const ajv = new Ajv({ strict: false, allErrors: true });
|
|
79
|
+
const validate = ajv.compile(schema as object);
|
|
80
|
+
const valid = validate(doc);
|
|
81
|
+
if (!valid) throw new Error(ajv.errorsText(validate.errors));
|
|
82
|
+
expect(valid).toBe(true);
|
|
83
|
+
expect(doc.name).toBe("search-service");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("CycloneDX: composes both leaves into one valid document with nested components + compositions", () => {
|
|
87
|
+
const manifest = multiArtifactManifest();
|
|
88
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
89
|
+
const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
|
|
90
|
+
|
|
91
|
+
const doc = JSON.parse(result.bom.bytes) as {
|
|
92
|
+
metadata: { component: { name: string; components: unknown[] } };
|
|
93
|
+
compositions: Array<{ aggregate: string }>;
|
|
94
|
+
};
|
|
95
|
+
const cdxSchema = loadSchema("cyclonedx-1.5.schema.json");
|
|
96
|
+
const jsfSchema = loadSchema("jsf-0.82.schema.json");
|
|
97
|
+
const spdxLicenseSchema = loadSchema("cyclonedx-spdx-license.schema.json");
|
|
98
|
+
const ajv = new Ajv({ strict: false, allErrors: true });
|
|
99
|
+
ajv.addSchema(jsfSchema as object);
|
|
100
|
+
ajv.addSchema(spdxLicenseSchema as object);
|
|
101
|
+
const validate = ajv.compile(cdxSchema as object);
|
|
102
|
+
const valid = validate(doc);
|
|
103
|
+
if (!valid) throw new Error(ajv.errorsText(validate.errors));
|
|
104
|
+
expect(valid).toBe(true);
|
|
105
|
+
|
|
106
|
+
expect(doc.metadata.component.name).toBe("search-service");
|
|
107
|
+
expect(doc.metadata.component.components).toHaveLength(2);
|
|
108
|
+
expect(doc.compositions[0]!.aggregate).toBe("incomplete");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("preserves which packages came from which leaf — never flattens software and config packages into one anonymous list", () => {
|
|
112
|
+
const manifest = multiArtifactManifest();
|
|
113
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
114
|
+
const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
|
|
115
|
+
const doc = JSON.parse(result.bom.bytes);
|
|
116
|
+
|
|
117
|
+
const softwareLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("software:"));
|
|
118
|
+
const configLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("config:"));
|
|
119
|
+
expect(softwareLeaf.components.map((c: { name: string }) => c.name)).toEqual(["left-pad"]);
|
|
120
|
+
expect(configLeaf.components.map((c: { name: string }) => c.name)).toEqual(["SearchBucket"]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("each sub-document's subjectId traces back to the artifact digest it describes (subjectDigest), not the BOM document's own digest", () => {
|
|
124
|
+
const manifest = multiArtifactManifest();
|
|
125
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
126
|
+
const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
|
|
127
|
+
const doc = JSON.parse(result.bom.bytes);
|
|
128
|
+
|
|
129
|
+
const softwareLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("software:"));
|
|
130
|
+
const configLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("config:"));
|
|
131
|
+
const subjectIdOf = (c: { properties: Array<{ name: string; value: string }> }) =>
|
|
132
|
+
c.properties.find((p) => p.name === "chant:subjectId")!.value;
|
|
133
|
+
expect(subjectIdOf(softwareLeaf)).toBe("sha256:img1");
|
|
134
|
+
expect(subjectIdOf(configLeaf)).toBe("sha256:tmpl1");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("is deterministic — identical leaves + manifest produce byte-identical output", () => {
|
|
138
|
+
const manifest = multiArtifactManifest();
|
|
139
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
140
|
+
const a = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
|
|
141
|
+
const b = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
|
|
142
|
+
expect(a.bom.bytes).toBe(b.bom.bytes);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("single-artifact component — 1:1, still a valid standalone document", () => {
|
|
147
|
+
it("one leaf produces a valid document with no sub-document nesting", () => {
|
|
148
|
+
let manifest = createBuildArchiveManifest("infra-only", { now: FIXED_NOW });
|
|
149
|
+
manifest = addArchiveEntry(manifest, { kind: "template", path: "infra.template.json", digest: "sha256:tmpl1" });
|
|
150
|
+
manifest = addArchiveEntry(manifest, {
|
|
151
|
+
kind: "sbom",
|
|
152
|
+
bomKind: "config",
|
|
153
|
+
path: "infra.template.json.config-bom.json",
|
|
154
|
+
digest: "sha256:sbomconfig",
|
|
155
|
+
subjectDigest: "sha256:tmpl1",
|
|
156
|
+
mediaType: "application/spdx+json",
|
|
157
|
+
packageCount: 1,
|
|
158
|
+
});
|
|
159
|
+
const leaves: ComponentBomLeaf[] = [
|
|
160
|
+
{
|
|
161
|
+
entry: findArchiveEntry(manifest, "infra.template.json.config-bom.json")!,
|
|
162
|
+
packages: [{ name: "SearchTable", version: "AWS::DynamoDB::Table", type: "config" }],
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const result = aggregateComponentBom({ component: "infra-only", manifest, leaves, format: "spdx", now: FIXED_NOW });
|
|
167
|
+
expect(result.leafCount).toBe(1);
|
|
168
|
+
expect(result.totalPackageCount).toBe(1);
|
|
169
|
+
|
|
170
|
+
const doc = JSON.parse(result.bom.bytes);
|
|
171
|
+
const schema = loadSchema("spdx-2.3.schema.json");
|
|
172
|
+
const ajv = new Ajv({ strict: false, allErrors: true });
|
|
173
|
+
const validate = ajv.compile(schema as object);
|
|
174
|
+
expect(validate(doc)).toBe(true);
|
|
175
|
+
// no CONTAINS relationship — nothing nested, the leaf's packages are
|
|
176
|
+
// the aggregate's own flat package list directly.
|
|
177
|
+
expect(doc.relationships.some((r: { relationshipType: string }) => r.relationshipType === "CONTAINS")).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("error handling", () => {
|
|
182
|
+
it("throws when given zero leaves — aggregation over nothing is a caller bug, not a silent no-op", () => {
|
|
183
|
+
const manifest = createBuildArchiveManifest("empty");
|
|
184
|
+
expect(() => aggregateComponentBom({ component: "empty", manifest, leaves: [] })).toThrow(/no leaf BOMs/);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("defaults", () => {
|
|
189
|
+
it("defaults to DEFAULT_SBOM_FORMAT (spdx) when format is omitted", () => {
|
|
190
|
+
const manifest = multiArtifactManifest();
|
|
191
|
+
const leaves = multiArtifactLeaves(manifest);
|
|
192
|
+
const result = aggregateComponentBom({ component: "search-service", manifest, leaves, now: FIXED_NOW });
|
|
193
|
+
expect(result.bom.format).toBe("spdx");
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("componentBomLeavesFromManifest (#614)", () => {
|
|
199
|
+
it("builds ComponentBomLeaf[] for every sbom-kind entry that has a known package list", () => {
|
|
200
|
+
const manifest = multiArtifactManifest();
|
|
201
|
+
const packagesByPath = new Map<string, BomPackage[]>([
|
|
202
|
+
["image.tar.sbom.json", [{ name: "left-pad", version: "1.3.0", type: "npm" }]],
|
|
203
|
+
["search.template.json.config-bom.json", [{ name: "SearchBucket", version: "AWS::S3::Bucket", type: "config" }]],
|
|
204
|
+
]);
|
|
205
|
+
const leaves = componentBomLeavesFromManifest(manifest, packagesByPath);
|
|
206
|
+
expect(leaves).toHaveLength(2);
|
|
207
|
+
expect(leaves.map((l) => l.entry.path).sort()).toEqual(["image.tar.sbom.json", "search.template.json.config-bom.json"]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("skips sbom entries with no known package list rather than failing", () => {
|
|
211
|
+
const manifest = multiArtifactManifest();
|
|
212
|
+
const packagesByPath = new Map<string, BomPackage[]>([
|
|
213
|
+
["image.tar.sbom.json", [{ name: "left-pad", version: "1.3.0", type: "npm" }]],
|
|
214
|
+
]);
|
|
215
|
+
const leaves = componentBomLeavesFromManifest(manifest, packagesByPath);
|
|
216
|
+
expect(leaves).toHaveLength(1);
|
|
217
|
+
expect(leaves[0]!.entry.path).toBe("image.tar.sbom.json");
|
|
218
|
+
});
|
|
219
|
+
});
|