@mannyc1/ts-release 0.0.7 → 0.3.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/ARCHITECTURE.md +184 -92
- package/CHANGELOG.md +197 -0
- package/LICENSE +21 -0
- package/README.md +404 -327
- package/SPEC.md +257 -310
- package/apps/release-ts/README.md +39 -0
- package/apps/release-ts/src/cli/command.ts +164 -0
- package/apps/release-ts/src/cli/commands.ts +351 -0
- package/apps/release-ts/src/cli/main.ts +43 -0
- package/apps/release-ts/src/cli/node-main.ts +49 -0
- package/apps/ts-release-agents/LICENSE +21 -0
- package/apps/ts-release-agents/README.md +21 -0
- package/apps/ts-release-agents/evals/cases.json +11 -0
- package/apps/ts-release-agents/manifests/claude.json +5 -0
- package/apps/ts-release-agents/manifests/codex.json +5 -0
- package/apps/ts-release-agents/package.json +11 -0
- package/apps/ts-release-agents/skills/release/SKILL.md +48 -0
- package/apps/ts-release-agents/skills/release/references/configuration.md +18 -0
- package/apps/ts-release-agents/skills/release/references/recovery.md +10 -0
- package/apps/ts-release-agents/skills/release/references/staged-workflow.md +12 -0
- package/apps/ts-release-agents/skills/release/references/target-selection.md +13 -0
- package/apps/ts-release-agents/skills/release/references/verification.md +14 -0
- package/apps/ts-release-agents/src/build.ts +105 -0
- package/apps/ts-release-agents/src/check.ts +122 -0
- package/apps/ts-release-agents/src/install.ts +121 -0
- package/apps/ts-release-agents/test/contract.test.ts +56 -0
- package/apps/ts-release-agents/tsconfig.json +5 -0
- package/dist/api/api.d.ts +15 -0
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +244 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/errors.d.ts +39 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +32 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/input.d.ts +10 -0
- package/dist/api/input.d.ts.map +1 -0
- package/dist/api/input.js +73 -0
- package/dist/api/input.js.map +1 -0
- package/dist/api/runtime.d.ts +18 -0
- package/dist/api/runtime.d.ts.map +1 -0
- package/dist/api/runtime.js +7 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/types.d.ts +76 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +17 -0
- package/dist/api/types.js.map +1 -0
- package/dist/bin/ts-release.js +39346 -0
- package/dist/capabilities/bun-targets.d.ts +3 -0
- package/dist/capabilities/bun-targets.d.ts.map +1 -0
- package/dist/capabilities/bun-targets.js +3 -0
- package/dist/capabilities/bun-targets.js.map +1 -0
- package/dist/capabilities/field-ownership.d.ts +17 -0
- package/dist/capabilities/field-ownership.d.ts.map +1 -0
- package/dist/capabilities/field-ownership.js +40 -0
- package/dist/capabilities/field-ownership.js.map +1 -0
- package/dist/capabilities/module.d.ts +91 -0
- package/dist/capabilities/module.d.ts.map +1 -0
- package/dist/capabilities/module.js +2 -0
- package/dist/capabilities/module.js.map +1 -0
- package/dist/capabilities/registry.d.ts +829 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +291 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/config/config.d.ts +6 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +42 -0
- package/dist/config/config.js.map +1 -0
- package/dist/correction/coordinator.d.ts +248 -0
- package/dist/correction/coordinator.d.ts.map +1 -0
- package/dist/correction/coordinator.js +274 -0
- package/dist/correction/coordinator.js.map +1 -0
- package/dist/correction/index.d.ts +3 -0
- package/dist/correction/index.d.ts.map +1 -0
- package/dist/correction/index.js +3 -0
- package/dist/correction/index.js.map +1 -0
- package/dist/correction/intent.d.ts +111 -0
- package/dist/correction/intent.d.ts.map +1 -0
- package/dist/correction/intent.js +146 -0
- package/dist/correction/intent.js.map +1 -0
- package/dist/drivers/archive.d.ts +9 -0
- package/dist/drivers/archive.d.ts.map +1 -0
- package/dist/drivers/archive.js +105 -0
- package/dist/drivers/archive.js.map +1 -0
- package/dist/drivers/contain.d.ts +6 -0
- package/dist/drivers/contain.d.ts.map +1 -0
- package/dist/drivers/contain.js +17 -0
- package/dist/drivers/contain.js.map +1 -0
- package/dist/drivers/environment.d.ts +4 -0
- package/dist/drivers/environment.d.ts.map +1 -0
- package/dist/drivers/environment.js +18 -0
- package/dist/drivers/environment.js.map +1 -0
- package/dist/drivers/errors.d.ts +9 -0
- package/dist/drivers/errors.d.ts.map +1 -0
- package/dist/drivers/errors.js +6 -0
- package/dist/drivers/errors.js.map +1 -0
- package/dist/drivers/glob.d.ts +2 -0
- package/dist/drivers/glob.d.ts.map +1 -0
- package/dist/drivers/glob.js +162 -0
- package/dist/drivers/glob.js.map +1 -0
- package/dist/drivers/process.d.ts +48 -0
- package/dist/drivers/process.d.ts.map +1 -0
- package/dist/drivers/process.js +257 -0
- package/dist/drivers/process.js.map +1 -0
- package/dist/drivers/python-wheel.d.ts +21 -0
- package/dist/drivers/python-wheel.d.ts.map +1 -0
- package/dist/drivers/python-wheel.js +109 -0
- package/dist/drivers/python-wheel.js.map +1 -0
- package/dist/drivers/redact.d.ts +4 -0
- package/dist/drivers/redact.d.ts.map +1 -0
- package/dist/drivers/redact.js +24 -0
- package/dist/drivers/redact.js.map +1 -0
- package/dist/drivers/seccomp-helper-source.d.ts +7 -0
- package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
- package/dist/drivers/seccomp-helper-source.js +121 -0
- package/dist/drivers/seccomp-helper-source.js.map +1 -0
- package/dist/drivers/utils.d.ts +4 -0
- package/dist/drivers/utils.d.ts.map +1 -0
- package/dist/drivers/utils.js +5 -0
- package/dist/drivers/utils.js.map +1 -0
- package/dist/drivers/workspace.d.ts +9 -0
- package/dist/drivers/workspace.d.ts.map +1 -0
- package/dist/drivers/workspace.js +53 -0
- package/dist/drivers/workspace.js.map +1 -0
- package/dist/extensions/provider-adapter.d.ts +48 -0
- package/dist/extensions/provider-adapter.d.ts.map +1 -0
- package/dist/extensions/provider-adapter.js +64 -0
- package/dist/extensions/provider-adapter.js.map +1 -0
- package/dist/host.d.ts +33 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +20 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/model/artifact-collection.d.ts +84 -0
- package/dist/model/artifact-collection.d.ts.map +1 -0
- package/dist/model/artifact-collection.js +185 -0
- package/dist/model/artifact-collection.js.map +1 -0
- package/dist/model/authority.d.ts +89 -0
- package/dist/model/authority.d.ts.map +1 -0
- package/dist/model/authority.js +80 -0
- package/dist/model/authority.js.map +1 -0
- package/dist/model/bun-targets.d.ts +34 -0
- package/dist/model/bun-targets.d.ts.map +1 -0
- package/dist/model/bun-targets.js +17 -0
- package/dist/model/bun-targets.js.map +1 -0
- package/dist/model/canonical.d.ts +8 -0
- package/dist/model/canonical.d.ts.map +1 -0
- package/dist/model/canonical.js +205 -0
- package/dist/model/canonical.js.map +1 -0
- package/dist/model/catalog.d.ts +76 -0
- package/dist/model/catalog.d.ts.map +1 -0
- package/dist/model/catalog.js +174 -0
- package/dist/model/catalog.js.map +1 -0
- package/dist/model/digest.d.ts +53 -0
- package/dist/model/digest.d.ts.map +1 -0
- package/dist/model/digest.js +127 -0
- package/dist/model/digest.js.map +1 -0
- package/dist/model/errors.d.ts +14 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/errors.js +8 -0
- package/dist/model/errors.js.map +1 -0
- package/dist/model/primitives.d.ts +19 -0
- package/dist/model/primitives.d.ts.map +1 -0
- package/dist/model/primitives.js +18 -0
- package/dist/model/primitives.js.map +1 -0
- package/dist/model/pypi.d.ts +12 -0
- package/dist/model/pypi.d.ts.map +1 -0
- package/dist/model/pypi.js +20 -0
- package/dist/model/pypi.js.map +1 -0
- package/dist/model/python-distribution.d.ts +33 -0
- package/dist/model/python-distribution.d.ts.map +1 -0
- package/dist/model/python-distribution.js +367 -0
- package/dist/model/python-distribution.js.map +1 -0
- package/dist/model/secret-patterns.d.ts +2 -0
- package/dist/model/secret-patterns.d.ts.map +1 -0
- package/dist/model/secret-patterns.js +12 -0
- package/dist/model/secret-patterns.js.map +1 -0
- package/dist/operation-journal/authority.d.ts +3 -0
- package/dist/operation-journal/authority.d.ts.map +1 -0
- package/dist/operation-journal/authority.js +147 -0
- package/dist/operation-journal/authority.js.map +1 -0
- package/dist/operation-journal/aws/deadline.d.ts +12 -0
- package/dist/operation-journal/aws/deadline.d.ts.map +1 -0
- package/dist/operation-journal/aws/deadline.js +38 -0
- package/dist/operation-journal/aws/deadline.js.map +1 -0
- package/dist/operation-journal/aws/oidc.d.ts +27 -0
- package/dist/operation-journal/aws/oidc.d.ts.map +1 -0
- package/dist/operation-journal/aws/oidc.js +361 -0
- package/dist/operation-journal/aws/oidc.js.map +1 -0
- package/dist/operation-journal/aws/policy.d.ts +42 -0
- package/dist/operation-journal/aws/policy.d.ts.map +1 -0
- package/dist/operation-journal/aws/policy.js +404 -0
- package/dist/operation-journal/aws/policy.js.map +1 -0
- package/dist/operation-journal/aws/s3-boundary.d.ts +17 -0
- package/dist/operation-journal/aws/s3-boundary.d.ts.map +1 -0
- package/dist/operation-journal/aws/s3-boundary.js +417 -0
- package/dist/operation-journal/aws/s3-boundary.js.map +1 -0
- package/dist/operation-journal/aws.d.ts +11 -0
- package/dist/operation-journal/aws.d.ts.map +1 -0
- package/dist/operation-journal/aws.js +50 -0
- package/dist/operation-journal/aws.js.map +1 -0
- package/dist/operation-journal/canonical.d.ts +52 -0
- package/dist/operation-journal/canonical.d.ts.map +1 -0
- package/dist/operation-journal/canonical.js +282 -0
- package/dist/operation-journal/canonical.js.map +1 -0
- package/dist/operation-journal/model.d.ts +251 -0
- package/dist/operation-journal/model.d.ts.map +1 -0
- package/dist/operation-journal/model.js +56 -0
- package/dist/operation-journal/model.js.map +1 -0
- package/dist/operation-journal/reducer.d.ts +28 -0
- package/dist/operation-journal/reducer.d.ts.map +1 -0
- package/dist/operation-journal/reducer.js +73 -0
- package/dist/operation-journal/reducer.js.map +1 -0
- package/dist/operation-journal/s3.d.ts +7 -0
- package/dist/operation-journal/s3.d.ts.map +1 -0
- package/dist/operation-journal/s3.js +667 -0
- package/dist/operation-journal/s3.js.map +1 -0
- package/dist/operation-journal.d.ts +14 -0
- package/dist/operation-journal.d.ts.map +1 -0
- package/dist/operation-journal.js +12 -0
- package/dist/operation-journal.js.map +1 -0
- package/dist/platform/bun.d.ts +7 -0
- package/dist/platform/bun.d.ts.map +1 -0
- package/dist/platform/bun.js +15 -0
- package/dist/platform/bun.js.map +1 -0
- package/dist/platform/credentials.d.ts +32 -0
- package/dist/platform/credentials.d.ts.map +1 -0
- package/dist/platform/credentials.js +834 -0
- package/dist/platform/credentials.js.map +1 -0
- package/dist/platform/host-support.d.ts +2 -0
- package/dist/platform/host-support.d.ts.map +1 -0
- package/dist/platform/host-support.js +4 -0
- package/dist/platform/host-support.js.map +1 -0
- package/dist/platform/node.d.ts +7 -0
- package/dist/platform/node.d.ts.map +1 -0
- package/dist/platform/node.js +15 -0
- package/dist/platform/node.js.map +1 -0
- package/dist/platform/release-runtime.d.ts +11 -0
- package/dist/platform/release-runtime.d.ts.map +1 -0
- package/dist/platform/release-runtime.js +34 -0
- package/dist/platform/release-runtime.js.map +1 -0
- package/dist/platform/services.d.ts +15 -0
- package/dist/platform/services.d.ts.map +1 -0
- package/dist/platform/services.js +30 -0
- package/dist/platform/services.js.map +1 -0
- package/dist/platform/source-observer.d.ts +5 -0
- package/dist/platform/source-observer.d.ts.map +1 -0
- package/dist/platform/source-observer.js +159 -0
- package/dist/platform/source-observer.js.map +1 -0
- package/dist/provider-sdk.d.ts +14 -0
- package/dist/provider-sdk.d.ts.map +1 -0
- package/dist/provider-sdk.js +10 -0
- package/dist/provider-sdk.js.map +1 -0
- package/dist/publication/adapter.d.ts +212 -0
- package/dist/publication/adapter.d.ts.map +1 -0
- package/dist/publication/adapter.js +83 -0
- package/dist/publication/adapter.js.map +1 -0
- package/dist/publication/authority.d.ts +137 -0
- package/dist/publication/authority.d.ts.map +1 -0
- package/dist/publication/authority.js +233 -0
- package/dist/publication/authority.js.map +1 -0
- package/dist/publication/catalog-git.d.ts +37 -0
- package/dist/publication/catalog-git.d.ts.map +1 -0
- package/dist/publication/catalog-git.js +451 -0
- package/dist/publication/catalog-git.js.map +1 -0
- package/dist/publication/claim.d.ts +39 -0
- package/dist/publication/claim.d.ts.map +1 -0
- package/dist/publication/claim.js +31 -0
- package/dist/publication/claim.js.map +1 -0
- package/dist/publication/coordinator.d.ts +246 -0
- package/dist/publication/coordinator.d.ts.map +1 -0
- package/dist/publication/coordinator.js +370 -0
- package/dist/publication/coordinator.js.map +1 -0
- package/dist/publication/github.d.ts +36 -0
- package/dist/publication/github.d.ts.map +1 -0
- package/dist/publication/github.js +695 -0
- package/dist/publication/github.js.map +1 -0
- package/dist/publication/http.d.ts +69 -0
- package/dist/publication/http.d.ts.map +1 -0
- package/dist/publication/http.js +29 -0
- package/dist/publication/http.js.map +1 -0
- package/dist/publication/index.d.ts +5 -0
- package/dist/publication/index.d.ts.map +1 -0
- package/dist/publication/index.js +5 -0
- package/dist/publication/index.js.map +1 -0
- package/dist/publication/npm.d.ts +40 -0
- package/dist/publication/npm.d.ts.map +1 -0
- package/dist/publication/npm.js +359 -0
- package/dist/publication/npm.js.map +1 -0
- package/dist/publication/profiles.d.ts +136 -0
- package/dist/publication/profiles.d.ts.map +1 -0
- package/dist/publication/profiles.js +90 -0
- package/dist/publication/profiles.js.map +1 -0
- package/dist/publication/publisher.d.ts +82 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/publisher.js +53 -0
- package/dist/publication/publisher.js.map +1 -0
- package/dist/publication/pypi.d.ts +45 -0
- package/dist/publication/pypi.d.ts.map +1 -0
- package/dist/publication/pypi.js +369 -0
- package/dist/publication/pypi.js.map +1 -0
- package/dist/publication/recovery.d.ts +663 -0
- package/dist/publication/recovery.d.ts.map +1 -0
- package/dist/publication/recovery.js +252 -0
- package/dist/publication/recovery.js.map +1 -0
- package/dist/publication/report.d.ts +1061 -0
- package/dist/publication/report.d.ts.map +1 -0
- package/dist/publication/report.js +441 -0
- package/dist/publication/report.js.map +1 -0
- package/dist/recipes/config.d.ts +296 -0
- package/dist/recipes/config.d.ts.map +1 -0
- package/dist/recipes/config.js +325 -0
- package/dist/recipes/config.js.map +1 -0
- package/dist/release/capabilities.d.ts +12 -0
- package/dist/release/capabilities.d.ts.map +1 -0
- package/dist/release/capabilities.js +395 -0
- package/dist/release/capabilities.js.map +1 -0
- package/dist/release/compiler.d.ts +5 -0
- package/dist/release/compiler.d.ts.map +1 -0
- package/dist/release/compiler.js +19 -0
- package/dist/release/compiler.js.map +1 -0
- package/dist/release/config.d.ts +5 -0
- package/dist/release/config.d.ts.map +1 -0
- package/dist/release/config.js +10 -0
- package/dist/release/config.js.map +1 -0
- package/dist/release/context.d.ts +96 -0
- package/dist/release/context.d.ts.map +1 -0
- package/dist/release/context.js +168 -0
- package/dist/release/context.js.map +1 -0
- package/dist/release/graph.d.ts +271 -0
- package/dist/release/graph.d.ts.map +1 -0
- package/dist/release/graph.js +766 -0
- package/dist/release/graph.js.map +1 -0
- package/dist/release/inspect.d.ts +88 -0
- package/dist/release/inspect.d.ts.map +1 -0
- package/dist/release/inspect.js +75 -0
- package/dist/release/inspect.js.map +1 -0
- package/dist/release/prepare.d.ts +24 -0
- package/dist/release/prepare.d.ts.map +1 -0
- package/dist/release/prepare.js +1115 -0
- package/dist/release/prepare.js.map +1 -0
- package/dist/release/prepared-ref.d.ts +60 -0
- package/dist/release/prepared-ref.d.ts.map +1 -0
- package/dist/release/prepared-ref.js +131 -0
- package/dist/release/prepared-ref.js.map +1 -0
- package/dist/release/prepared-store.d.ts +90 -0
- package/dist/release/prepared-store.d.ts.map +1 -0
- package/dist/release/prepared-store.js +339 -0
- package/dist/release/prepared-store.js.map +1 -0
- package/dist/release/prepared.d.ts +172 -0
- package/dist/release/prepared.d.ts.map +1 -0
- package/dist/release/prepared.js +268 -0
- package/dist/release/prepared.js.map +1 -0
- package/dist/release/staging.d.ts +31 -0
- package/dist/release/staging.d.ts.map +1 -0
- package/dist/release/staging.js +218 -0
- package/dist/release/staging.js.map +1 -0
- package/dist/resolve/authored.d.ts +53 -0
- package/dist/resolve/authored.d.ts.map +1 -0
- package/dist/resolve/authored.js +55 -0
- package/dist/resolve/authored.js.map +1 -0
- package/dist/resolve/encode.d.ts +3 -0
- package/dist/resolve/encode.d.ts.map +1 -0
- package/dist/resolve/encode.js +21 -0
- package/dist/resolve/encode.js.map +1 -0
- package/dist/resolve/errors.d.ts +7 -0
- package/dist/resolve/errors.d.ts.map +1 -0
- package/dist/resolve/errors.js +17 -0
- package/dist/resolve/errors.js.map +1 -0
- package/dist/resolve/facts.d.ts +12 -0
- package/dist/resolve/facts.d.ts.map +1 -0
- package/dist/resolve/facts.js +18 -0
- package/dist/resolve/facts.js.map +1 -0
- package/dist/resolve/resolve.d.ts +10 -0
- package/dist/resolve/resolve.d.ts.map +1 -0
- package/dist/resolve/resolve.js +242 -0
- package/dist/resolve/resolve.js.map +1 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +9 -0
- package/dist/store.js.map +1 -0
- package/examples/README.md +20 -30
- package/examples/github-release/release.config.json +11 -18
- package/examples/homebrew-tap/README.md +11 -0
- package/examples/homebrew-tap/release.config.json +40 -28
- package/examples/multi-target/release.config.json +28 -53
- package/examples/npm-first-publish/release.config.json +23 -25
- package/examples/npm-only/release.config.json +22 -30
- package/examples/portable-cli/README.md +17 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
- package/examples/portable-cli/package.json +9 -0
- package/examples/portable-cli/release.config.json +53 -0
- package/examples/portable-cli/src/cli.ts +3 -0
- package/examples/pypi-registry/README.md +8 -0
- package/examples/scoop-bucket/README.md +10 -0
- package/examples/scoop-bucket/release.config.json +40 -30
- package/package.json +73 -154
- package/src/api/api.ts +365 -0
- package/src/api/errors.ts +36 -0
- package/src/api/input.ts +92 -0
- package/src/api/runtime.ts +14 -0
- package/src/api/types.ts +103 -0
- package/src/capabilities/bun-targets.ts +2 -0
- package/src/capabilities/field-ownership.ts +60 -0
- package/src/capabilities/module.ts +158 -0
- package/src/capabilities/registry.ts +333 -0
- package/src/config/config.ts +48 -0
- package/src/correction/coordinator.ts +336 -0
- package/src/correction/index.ts +2 -0
- package/src/correction/intent.ts +172 -0
- package/src/drivers/archive.ts +111 -0
- package/src/drivers/contain.ts +20 -0
- package/src/drivers/environment.ts +28 -0
- package/src/drivers/errors.ts +6 -0
- package/src/drivers/glob.ts +137 -0
- package/src/drivers/process.ts +339 -0
- package/src/drivers/python-wheel.ts +145 -0
- package/src/drivers/redact.ts +31 -0
- package/src/drivers/seccomp-helper-source.ts +121 -0
- package/src/drivers/utils.ts +10 -0
- package/src/drivers/workspace.ts +46 -0
- package/src/extensions/provider-adapter.ts +99 -0
- package/src/host.ts +140 -0
- package/src/index.ts +64 -0
- package/src/model/artifact-collection.ts +237 -0
- package/src/model/authority.ts +116 -0
- package/src/model/bun-targets.ts +34 -0
- package/src/model/canonical.ts +205 -0
- package/src/model/catalog.ts +220 -0
- package/src/model/digest.ts +172 -0
- package/src/model/errors.ts +6 -0
- package/src/model/primitives.ts +37 -0
- package/src/model/pypi.ts +30 -0
- package/src/model/python-distribution.ts +360 -0
- package/src/model/secret-patterns.ts +11 -0
- package/src/operation-journal/authority.ts +162 -0
- package/src/operation-journal/aws/deadline.ts +41 -0
- package/src/operation-journal/aws/oidc.ts +439 -0
- package/src/operation-journal/aws/policy.ts +491 -0
- package/src/operation-journal/aws/s3-boundary.ts +562 -0
- package/src/operation-journal/aws.ts +60 -0
- package/src/operation-journal/canonical.ts +373 -0
- package/src/operation-journal/model.ts +301 -0
- package/src/operation-journal/reducer.ts +112 -0
- package/src/operation-journal/s3.ts +884 -0
- package/src/operation-journal.ts +51 -0
- package/src/platform/bun.ts +38 -0
- package/src/platform/credentials.ts +1151 -0
- package/src/platform/host-support.ts +4 -0
- package/src/platform/node.ts +38 -0
- package/src/platform/release-runtime.ts +47 -0
- package/src/platform/services.ts +57 -0
- package/src/platform/source-observer.ts +193 -0
- package/src/provider-sdk.ts +48 -0
- package/src/publication/adapter.ts +132 -0
- package/src/publication/authority.ts +397 -0
- package/src/publication/catalog-git.ts +603 -0
- package/src/publication/claim.ts +51 -0
- package/src/publication/coordinator.ts +578 -0
- package/src/publication/github.ts +1022 -0
- package/src/publication/http.ts +101 -0
- package/src/publication/index.ts +4 -0
- package/src/publication/npm.ts +525 -0
- package/src/publication/profiles.ts +94 -0
- package/src/publication/publisher.ts +130 -0
- package/src/publication/pypi.ts +463 -0
- package/src/publication/recovery.ts +349 -0
- package/src/publication/report.ts +628 -0
- package/src/recipes/config.ts +395 -0
- package/src/release/capabilities.ts +460 -0
- package/src/release/compiler.ts +28 -0
- package/src/release/config.ts +12 -0
- package/src/release/context.ts +223 -0
- package/src/release/graph.ts +866 -0
- package/src/release/inspect.ts +83 -0
- package/src/release/prepare.ts +1329 -0
- package/src/release/prepared-ref.ts +193 -0
- package/src/release/prepared-store.ts +378 -0
- package/src/release/prepared.ts +299 -0
- package/src/release/staging.ts +257 -0
- package/src/resolve/authored.ts +64 -0
- package/src/resolve/encode.ts +22 -0
- package/src/resolve/errors.ts +15 -0
- package/src/resolve/facts.ts +18 -0
- package/src/resolve/resolve.ts +328 -0
- package/src/store.ts +28 -0
- package/templates/README.md +55 -72
- package/templates/bun-cli-github/README.md +7 -9
- package/templates/bun-cli-github/release.config.json +35 -73
- package/templates/github-actions/release.yml +74 -0
- package/templates/github-actions/reviewed-release.yml +115 -0
- package/templates/multi-target-homebrew/README.md +10 -0
- package/templates/multi-target-homebrew/release.config.json +43 -60
- package/templates/multi-target-scoop/README.md +10 -0
- package/templates/multi-target-scoop/release.config.json +43 -62
- package/templates/npm-github/release.config.json +25 -38
- package/templates/npm-github/reviewed-release.config.json +37 -0
- package/templates/npm-only/README.md +10 -0
- package/templates/npm-only/release.config.json +15 -30
- package/templates/portable-cli/README.md +13 -0
- package/templates/portable-cli/release.config.json +53 -0
- package/dist/artifacts/adapter.d.ts +0 -39
- package/dist/artifacts/adapter.d.ts.map +0 -1
- package/dist/artifacts/adapter.js +0 -23
- package/dist/artifacts/adapter.js.map +0 -1
- package/dist/artifacts/registry.d.ts +0 -21
- package/dist/artifacts/registry.d.ts.map +0 -1
- package/dist/artifacts/registry.js +0 -21
- package/dist/artifacts/registry.js.map +0 -1
- package/dist/config/errors.d.ts +0 -24
- package/dist/config/errors.d.ts.map +0 -1
- package/dist/config/errors.js +0 -19
- package/dist/config/errors.js.map +0 -1
- package/dist/config/load.d.ts +0 -9
- package/dist/config/load.d.ts.map +0 -1
- package/dist/config/load.js +0 -29
- package/dist/config/load.js.map +0 -1
- package/dist/config/schema.d.ts +0 -12
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/config/schema.js +0 -19
- package/dist/config/schema.js.map +0 -1
- package/dist/domain/artifact.d.ts +0 -118
- package/dist/domain/artifact.d.ts.map +0 -1
- package/dist/domain/artifact.js +0 -197
- package/dist/domain/artifact.js.map +0 -1
- package/dist/domain/evidence.d.ts +0 -59
- package/dist/domain/evidence.d.ts.map +0 -1
- package/dist/domain/evidence.js +0 -51
- package/dist/domain/evidence.js.map +0 -1
- package/dist/domain/operation.d.ts +0 -143
- package/dist/domain/operation.d.ts.map +0 -1
- package/dist/domain/operation.js +0 -198
- package/dist/domain/operation.js.map +0 -1
- package/dist/domain/release.d.ts +0 -93
- package/dist/domain/release.d.ts.map +0 -1
- package/dist/domain/release.js +0 -82
- package/dist/domain/release.js.map +0 -1
- package/dist/domain/remote-state.d.ts +0 -58
- package/dist/domain/remote-state.d.ts.map +0 -1
- package/dist/domain/remote-state.js +0 -55
- package/dist/domain/remote-state.js.map +0 -1
- package/dist/domain/target.d.ts +0 -148
- package/dist/domain/target.d.ts.map +0 -1
- package/dist/domain/target.js +0 -151
- package/dist/domain/target.js.map +0 -1
- package/dist/host/host.d.ts +0 -32
- package/dist/host/host.d.ts.map +0 -1
- package/dist/host/host.js +0 -24
- package/dist/host/host.js.map +0 -1
- package/dist/host/http-live.d.ts +0 -6
- package/dist/host/http-live.d.ts.map +0 -1
- package/dist/host/http-live.js +0 -86
- package/dist/host/http-live.js.map +0 -1
- package/dist/host/http.d.ts +0 -43
- package/dist/host/http.d.ts.map +0 -1
- package/dist/host/http.js +0 -59
- package/dist/host/http.js.map +0 -1
- package/dist/host/platform.d.ts +0 -10
- package/dist/host/platform.d.ts.map +0 -1
- package/dist/host/platform.js +0 -114
- package/dist/host/platform.js.map +0 -1
- package/dist/host/test.d.ts +0 -23
- package/dist/host/test.d.ts.map +0 -1
- package/dist/host/test.js +0 -222
- package/dist/host/test.js.map +0 -1
- package/dist/internal/workspace-path.d.ts +0 -15
- package/dist/internal/workspace-path.d.ts.map +0 -1
- package/dist/internal/workspace-path.js +0 -36
- package/dist/internal/workspace-path.js.map +0 -1
- package/dist/planner/artifact-inventory.d.ts +0 -9
- package/dist/planner/artifact-inventory.d.ts.map +0 -1
- package/dist/planner/artifact-inventory.js +0 -86
- package/dist/planner/artifact-inventory.js.map +0 -1
- package/dist/planner/create-release-plan.d.ts +0 -5
- package/dist/planner/create-release-plan.d.ts.map +0 -1
- package/dist/planner/create-release-plan.js +0 -25
- package/dist/planner/create-release-plan.js.map +0 -1
- package/dist/planner/errors.d.ts +0 -67
- package/dist/planner/errors.d.ts.map +0 -1
- package/dist/planner/errors.js +0 -58
- package/dist/planner/errors.js.map +0 -1
- package/dist/planner/evidence-recorder.d.ts +0 -28
- package/dist/planner/evidence-recorder.d.ts.map +0 -1
- package/dist/planner/evidence-recorder.js +0 -362
- package/dist/planner/evidence-recorder.js.map +0 -1
- package/dist/planner/executor.d.ts +0 -58
- package/dist/planner/executor.d.ts.map +0 -1
- package/dist/planner/executor.js +0 -147
- package/dist/planner/executor.js.map +0 -1
- package/dist/planner/normalize-release.d.ts +0 -19
- package/dist/planner/normalize-release.d.ts.map +0 -1
- package/dist/planner/normalize-release.js +0 -342
- package/dist/planner/normalize-release.js.map +0 -1
- package/dist/planner/reconcile.d.ts +0 -17
- package/dist/planner/reconcile.d.ts.map +0 -1
- package/dist/planner/reconcile.js +0 -259
- package/dist/planner/reconcile.js.map +0 -1
- package/dist/planner/render-plan.d.ts +0 -15
- package/dist/planner/render-plan.d.ts.map +0 -1
- package/dist/planner/render-plan.js +0 -231
- package/dist/planner/render-plan.js.map +0 -1
- package/dist/targets/adapter-helpers.d.ts +0 -79
- package/dist/targets/adapter-helpers.d.ts.map +0 -1
- package/dist/targets/adapter-helpers.js +0 -194
- package/dist/targets/adapter-helpers.js.map +0 -1
- package/dist/targets/adapter.d.ts +0 -17
- package/dist/targets/adapter.d.ts.map +0 -1
- package/dist/targets/adapter.js +0 -2
- package/dist/targets/adapter.js.map +0 -1
- package/dist/targets/github-release.d.ts +0 -25
- package/dist/targets/github-release.d.ts.map +0 -1
- package/dist/targets/github-release.js +0 -82
- package/dist/targets/github-release.js.map +0 -1
- package/dist/targets/github.d.ts +0 -11
- package/dist/targets/github.d.ts.map +0 -1
- package/dist/targets/github.js +0 -117
- package/dist/targets/github.js.map +0 -1
- package/dist/targets/homebrew.d.ts +0 -11
- package/dist/targets/homebrew.d.ts.map +0 -1
- package/dist/targets/homebrew.js +0 -210
- package/dist/targets/homebrew.js.map +0 -1
- package/dist/targets/live.d.ts +0 -5
- package/dist/targets/live.d.ts.map +0 -1
- package/dist/targets/live.js +0 -50
- package/dist/targets/live.js.map +0 -1
- package/dist/targets/npm.d.ts +0 -10
- package/dist/targets/npm.d.ts.map +0 -1
- package/dist/targets/npm.js +0 -112
- package/dist/targets/npm.js.map +0 -1
- package/dist/targets/pypi.d.ts +0 -11
- package/dist/targets/pypi.d.ts.map +0 -1
- package/dist/targets/pypi.js +0 -141
- package/dist/targets/pypi.js.map +0 -1
- package/dist/targets/registry.d.ts +0 -25
- package/dist/targets/registry.d.ts.map +0 -1
- package/dist/targets/registry.js +0 -34
- package/dist/targets/registry.js.map +0 -1
- package/dist/targets/scoop.d.ts +0 -11
- package/dist/targets/scoop.d.ts.map +0 -1
- package/dist/targets/scoop.js +0 -88
- package/dist/targets/scoop.js.map +0 -1
- package/dist/types/effect-internal.d.ts +0 -5
- package/dist/types/effect-internal.d.ts.map +0 -1
- package/dist/types/effect-internal.js +0 -2
- package/dist/types/effect-internal.js.map +0 -1
- package/dist/workflows/config.d.ts +0 -202
- package/dist/workflows/config.d.ts.map +0 -1
- package/dist/workflows/config.js +0 -420
- package/dist/workflows/config.js.map +0 -1
- package/dist/workflows/diagnostics.d.ts +0 -62
- package/dist/workflows/diagnostics.d.ts.map +0 -1
- package/dist/workflows/diagnostics.js +0 -530
- package/dist/workflows/diagnostics.js.map +0 -1
- package/dist/workflows/distribution.d.ts +0 -25
- package/dist/workflows/distribution.d.ts.map +0 -1
- package/dist/workflows/distribution.js +0 -24
- package/dist/workflows/distribution.js.map +0 -1
- package/dist/workflows/evidence.d.ts +0 -17
- package/dist/workflows/evidence.d.ts.map +0 -1
- package/dist/workflows/evidence.js +0 -32
- package/dist/workflows/evidence.js.map +0 -1
- package/dist/workflows/index.d.ts +0 -8
- package/dist/workflows/index.d.ts.map +0 -1
- package/dist/workflows/index.js +0 -7
- package/dist/workflows/index.js.map +0 -1
- package/dist/workflows/init.d.ts +0 -84
- package/dist/workflows/init.d.ts.map +0 -1
- package/dist/workflows/init.js +0 -487
- package/dist/workflows/init.js.map +0 -1
- package/dist/workflows/live.d.ts +0 -9
- package/dist/workflows/live.d.ts.map +0 -1
- package/dist/workflows/live.js +0 -7
- package/dist/workflows/live.js.map +0 -1
- package/dist/workflows/options.d.ts +0 -30
- package/dist/workflows/options.d.ts.map +0 -1
- package/dist/workflows/options.js +0 -16
- package/dist/workflows/options.js.map +0 -1
- package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
- package/examples/non-strict-skips/release.config.json +0 -31
- package/examples/pypi-registry/release.config.json +0 -31
- package/templates/github-actions/plan-and-approved-execute.yml +0 -58
- package/templates/github-actions/plan-only.yml +0 -33
- package/templates/github-actions/trusted-publishing.yml +0 -61
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { chmod, copyFile, mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import * as Config from "effect/Config";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Exit from "effect/Exit";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Redacted from "effect/Redacted";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import * as Stream from "effect/Stream";
|
|
13
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
14
|
+
import * as Semver from "semver";
|
|
15
|
+
import { EnvironmentName } from "../model/authority.js";
|
|
16
|
+
import { redactOutput } from "../drivers/redact.js";
|
|
17
|
+
import { CredentialAudienceMismatch, CredentialProvider, CredentialPurposeMismatch, CredentialStrategyUnsupported, CredentialSubjectMismatch, CredentialUnavailable, makeCredentialProvider, validateGrantForOperation } from "../publication/authority.js";
|
|
18
|
+
import { AuthorizedMutationHttp, HttpAuthorizer } from "../publication/http.js";
|
|
19
|
+
import { CertifiedPublisherSpawn, NpmUserConfigResource, PublisherExited, PublisherOutcomeUnknown, RejectedBeforeStart, makeNpmUserConfigHandle, npmPublishArgv } from "../publication/publisher.js";
|
|
20
|
+
import { canonicalizeRegistryUrl } from "../release/graph.js";
|
|
21
|
+
export { AuthorizedMutationHttp, CertifiedPublisherSpawn, HttpAuthorizer, NpmUserConfigResource, PublisherExited, PublisherOutcomeUnknown, RejectedBeforeStart };
|
|
22
|
+
export class CredentialPlatformError extends Schema.TaggedErrorClass()("CredentialPlatformError", {
|
|
23
|
+
phase: Schema.Literals(["observe", "mutate", "resource", "spawn"]),
|
|
24
|
+
commitment: Schema.Literals(["before-dispatch", "unknown"]),
|
|
25
|
+
reason: Schema.NonEmptyString
|
|
26
|
+
}) {
|
|
27
|
+
}
|
|
28
|
+
// These are deliberately the only literal spellings in production code. The
|
|
29
|
+
// import-rules AST gate keeps OIDC request material in this host module.
|
|
30
|
+
const oidcRequestUrlName = EnvironmentName.make("ACTIONS_ID_TOKEN_REQUEST_URL");
|
|
31
|
+
const oidcRequestTokenName = EnvironmentName.make("ACTIONS_ID_TOKEN_REQUEST_TOKEN");
|
|
32
|
+
const oidcNames = [oidcRequestUrlName, oidcRequestTokenName];
|
|
33
|
+
const githubActionsName = "GITHUB_ACTIONS";
|
|
34
|
+
const githubRepositoryName = "GITHUB_REPOSITORY";
|
|
35
|
+
const githubWorkflowRefName = "GITHUB_WORKFLOW_REF";
|
|
36
|
+
const githubServerUrlName = "GITHUB_SERVER_URL";
|
|
37
|
+
const githubEventName = "GITHUB_EVENT_NAME";
|
|
38
|
+
const githubRepositoryIdName = "GITHUB_REPOSITORY_ID";
|
|
39
|
+
const githubRepositoryOwnerIdName = "GITHUB_REPOSITORY_OWNER_ID";
|
|
40
|
+
const githubRefName = "GITHUB_REF";
|
|
41
|
+
const githubShaName = "GITHUB_SHA";
|
|
42
|
+
const runnerEnvironmentName = "RUNNER_ENVIRONMENT";
|
|
43
|
+
const githubRunIdName = "GITHUB_RUN_ID";
|
|
44
|
+
const githubRunAttemptName = "GITHUB_RUN_ATTEMPT";
|
|
45
|
+
const portableEnvironmentName = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
46
|
+
const certifiedWorkflow = /^\.github\/workflows\/[A-Za-z0-9_.-]+\.ya?ml$/u;
|
|
47
|
+
const certifiedRepository = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u;
|
|
48
|
+
const certifiedWorkflowRef = /^(?:refs\/(?:heads|tags)\/[A-Za-z0-9][A-Za-z0-9._/-]*|[a-f0-9]{40}(?:[a-f0-9]{24})?)$/u;
|
|
49
|
+
const certifiedSourceCommit = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u;
|
|
50
|
+
const certifiedEvent = /^[a-z][a-z0-9_]*$/u;
|
|
51
|
+
const certifiedDecimalId = /^[1-9][0-9]*$/u;
|
|
52
|
+
const certifiedSourceRef = /^refs\/(?:heads|tags)\/[A-Za-z0-9][A-Za-z0-9._/-]*$/u;
|
|
53
|
+
const rememberObservationGrant = (observationGrants, grant) => {
|
|
54
|
+
observationGrants.set(grant, grant._tag === "AnonymousAccess"
|
|
55
|
+
? {
|
|
56
|
+
_tag: grant._tag,
|
|
57
|
+
subject: grant.subject,
|
|
58
|
+
audience: grant.audience,
|
|
59
|
+
purposes: new Set(grant.purposes)
|
|
60
|
+
}
|
|
61
|
+
: {
|
|
62
|
+
_tag: grant._tag,
|
|
63
|
+
subject: grant.subject,
|
|
64
|
+
audience: grant.audience,
|
|
65
|
+
purposes: new Set(grant.purposes),
|
|
66
|
+
ref: grant.ref
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const vaultKey = (subject, name) => `${subject}\u0000${name}`;
|
|
70
|
+
const platformError = (phase, commitment, reason) => new CredentialPlatformError({ phase, commitment, reason });
|
|
71
|
+
const unavailable = (request, reason) => new CredentialUnavailable({
|
|
72
|
+
subject: request.subject,
|
|
73
|
+
provider: request.provider,
|
|
74
|
+
purpose: request.purpose,
|
|
75
|
+
reason
|
|
76
|
+
});
|
|
77
|
+
const unsupported = (request, reason) => new CredentialStrategyUnsupported({
|
|
78
|
+
subject: request.subject,
|
|
79
|
+
provider: request.provider,
|
|
80
|
+
strategy: request.strategy.kind,
|
|
81
|
+
reason
|
|
82
|
+
});
|
|
83
|
+
const readSecret = (request, name) => Config.redacted(name).pipe(Effect.mapError(() => unavailable(request, `Credential reference ${name} is unavailable.`)));
|
|
84
|
+
const trustedStrategyIssue = (request) => {
|
|
85
|
+
const strategy = request.strategy;
|
|
86
|
+
if (strategy.kind !== "trusted-publishing")
|
|
87
|
+
return "The request does not carry trusted-publishing intent.";
|
|
88
|
+
if (request.provider !== "npm" || request.audience !== "https://registry.npmjs.org/" ||
|
|
89
|
+
request.purpose !== "publish") {
|
|
90
|
+
return "Trusted publishing is certified only for npm publish at the canonical npm registry.";
|
|
91
|
+
}
|
|
92
|
+
if (strategy.identityProvider !== "github-actions" || strategy.runnerClass !== "github-hosted" ||
|
|
93
|
+
!certifiedRepository.test(strategy.repository) || !certifiedWorkflow.test(strategy.workflow) ||
|
|
94
|
+
!certifiedWorkflowRef.test(strategy.workflowRef) ||
|
|
95
|
+
!certifiedSourceCommit.test(strategy.sourceCommit) ||
|
|
96
|
+
strategy.provenanceEnvironmentContract !== "github-actions-npm-provenance-v1" ||
|
|
97
|
+
strategy.allowedAction !== "npm-publish-direct" || strategy.publisherSink !== "certified-npm-cli") {
|
|
98
|
+
return "Trusted publishing requires one exact GitHub-hosted workflow/ref, source commit, provenance environment contract, and certified direct npm sink.";
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
/** Validate non-secret GitHub host facts before either OIDC request value is read. */
|
|
103
|
+
const verifyTrustedHostIdentity = Effect.fn("EnvironmentCredentialProvider.verifyTrustedHostIdentity")(function* (request) {
|
|
104
|
+
const issue = trustedStrategyIssue(request);
|
|
105
|
+
if (issue !== undefined)
|
|
106
|
+
return yield* unsupported(request, issue);
|
|
107
|
+
if (request.strategy.kind !== "trusted-publishing")
|
|
108
|
+
return yield* unsupported(request, issue ?? "Invalid strategy.");
|
|
109
|
+
const expected = [
|
|
110
|
+
[githubActionsName, "true"],
|
|
111
|
+
[githubRepositoryName, request.strategy.repository],
|
|
112
|
+
[githubWorkflowRefName,
|
|
113
|
+
`${request.strategy.repository}/${request.strategy.workflow}@${request.strategy.workflowRef}`],
|
|
114
|
+
[githubServerUrlName, "https://github.com"],
|
|
115
|
+
[githubShaName, request.strategy.sourceCommit],
|
|
116
|
+
[runnerEnvironmentName, "github-hosted"]
|
|
117
|
+
];
|
|
118
|
+
const values = {};
|
|
119
|
+
for (const [name, value] of expected) {
|
|
120
|
+
const observed = yield* Config.string(name).pipe(Effect.mapError(() => unsupported(request, `Trusted publishing host identity ${name} is unavailable.`)));
|
|
121
|
+
if (observed !== value) {
|
|
122
|
+
return yield* unsupported(request, `Trusted publishing host identity ${name} does not match prepared intent.`);
|
|
123
|
+
}
|
|
124
|
+
values[name] = observed;
|
|
125
|
+
}
|
|
126
|
+
const shaped = [
|
|
127
|
+
[githubEventName, certifiedEvent, "a canonical GitHub event name"],
|
|
128
|
+
[githubRepositoryIdName, certifiedDecimalId, "a canonical positive decimal repository ID"],
|
|
129
|
+
[githubRepositoryOwnerIdName, certifiedDecimalId, "a canonical positive decimal repository-owner ID"],
|
|
130
|
+
[githubRefName, certifiedSourceRef, "a canonical heads/tags source ref"],
|
|
131
|
+
[githubRunIdName, certifiedDecimalId, "a canonical positive decimal run ID"],
|
|
132
|
+
[githubRunAttemptName, certifiedDecimalId, "a canonical positive decimal run attempt"]
|
|
133
|
+
];
|
|
134
|
+
for (const [name, pattern, description] of shaped) {
|
|
135
|
+
const observed = yield* Config.string(name).pipe(Effect.mapError(() => unsupported(request, `Trusted publishing provenance fact ${name} is unavailable.`)));
|
|
136
|
+
if (!pattern.test(observed)) {
|
|
137
|
+
return yield* unsupported(request, `Trusted publishing provenance fact ${name} is not ${description}.`);
|
|
138
|
+
}
|
|
139
|
+
values[name] = observed;
|
|
140
|
+
}
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
GITHUB_ACTIONS: values[githubActionsName],
|
|
143
|
+
GITHUB_REPOSITORY: values[githubRepositoryName],
|
|
144
|
+
GITHUB_WORKFLOW_REF: values[githubWorkflowRefName],
|
|
145
|
+
GITHUB_SERVER_URL: values[githubServerUrlName],
|
|
146
|
+
GITHUB_EVENT_NAME: values[githubEventName],
|
|
147
|
+
GITHUB_REPOSITORY_ID: values[githubRepositoryIdName],
|
|
148
|
+
GITHUB_REPOSITORY_OWNER_ID: values[githubRepositoryOwnerIdName],
|
|
149
|
+
GITHUB_REF: values[githubRefName],
|
|
150
|
+
GITHUB_SHA: values[githubShaName],
|
|
151
|
+
RUNNER_ENVIRONMENT: values[runnerEnvironmentName],
|
|
152
|
+
GITHUB_RUN_ID: values[githubRunIdName],
|
|
153
|
+
GITHUB_RUN_ATTEMPT: values[githubRunAttemptName]
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
/**
|
|
157
|
+
* A raw environment token has no host-verifiable downscope metadata. Treat it
|
|
158
|
+
* conservatively as bundled provider authority instead of relabeling the same
|
|
159
|
+
* bearer as a purpose-specific secret merely because one method requested it.
|
|
160
|
+
*/
|
|
161
|
+
const environmentTokenPurposes = (purpose) => purpose === "correct"
|
|
162
|
+
? ["observe", "publish", "correct"]
|
|
163
|
+
: ["observe", "publish"];
|
|
164
|
+
const makeEnvironmentAcquirer = (vault, onTrustedWorkload) => ({
|
|
165
|
+
acquire: Effect.fn("EnvironmentCredentialProvider.acquire")(function* (request) {
|
|
166
|
+
switch (request.strategy.kind) {
|
|
167
|
+
case "anonymous":
|
|
168
|
+
return { _tag: "AnonymousAccess", purposes: ["observe"] };
|
|
169
|
+
case "token": {
|
|
170
|
+
if (request.provider === "npm" && request.purpose === "observe") {
|
|
171
|
+
return yield* unsupported(request, "Authenticated npm observation is unsupported without a distinct typed read credential.");
|
|
172
|
+
}
|
|
173
|
+
const name = request.strategy.credential.toString();
|
|
174
|
+
if (!portableEnvironmentName.test(name)) {
|
|
175
|
+
return yield* unsupported(request, "Token credential references must be portable environment names.");
|
|
176
|
+
}
|
|
177
|
+
const value = yield* readSecret(request, name);
|
|
178
|
+
vault.set(vaultKey(request.subject, name), value);
|
|
179
|
+
return {
|
|
180
|
+
_tag: "ScopedSecret",
|
|
181
|
+
purposes: environmentTokenPurposes(request.purpose),
|
|
182
|
+
ref: request.strategy.credential
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
case "trusted-publishing": {
|
|
186
|
+
if (onTrustedWorkload === undefined) {
|
|
187
|
+
return yield* unsupported(request, "Trusted workload acquisition requires a private host-material binding.");
|
|
188
|
+
}
|
|
189
|
+
const snapshot = yield* verifyTrustedHostIdentity(request);
|
|
190
|
+
const oidcValues = new Map();
|
|
191
|
+
for (const name of oidcNames) {
|
|
192
|
+
const value = yield* readSecret(request, name);
|
|
193
|
+
oidcValues.set(name, value);
|
|
194
|
+
}
|
|
195
|
+
onTrustedWorkload(Object.freeze({ snapshot, oidc: oidcValues }));
|
|
196
|
+
return {
|
|
197
|
+
_tag: "WorkloadIdentity",
|
|
198
|
+
purposes: [request.purpose],
|
|
199
|
+
names: oidcNames
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
const makeProvider = (vault, trustedWorkloads, observationGrants) => {
|
|
206
|
+
const provider = makeCredentialProvider(makeEnvironmentAcquirer(vault));
|
|
207
|
+
return {
|
|
208
|
+
acquireForObservation: Effect.fn("EnvironmentCredentialProvider.acquireForObservation")(function* (request) {
|
|
209
|
+
const grant = yield* provider.acquireForObservation(request);
|
|
210
|
+
if (grant._tag !== "WorkloadIdentity") {
|
|
211
|
+
rememberObservationGrant(observationGrants, grant);
|
|
212
|
+
}
|
|
213
|
+
return grant;
|
|
214
|
+
}),
|
|
215
|
+
acquireForMutation: Effect.fn("EnvironmentCredentialProvider.acquireForMutation")(function* (request, decision) {
|
|
216
|
+
let grant;
|
|
217
|
+
if (request.strategy.kind === "trusted-publishing") {
|
|
218
|
+
let material;
|
|
219
|
+
const trustedProvider = makeCredentialProvider(makeEnvironmentAcquirer(vault, (acquired) => { material = acquired; }));
|
|
220
|
+
grant = yield* trustedProvider.acquireForMutation(request, decision);
|
|
221
|
+
if (grant._tag !== "WorkloadIdentity" || material === undefined) {
|
|
222
|
+
return yield* unavailable(request, "Trusted publishing host evidence was not bound to its workload grant.");
|
|
223
|
+
}
|
|
224
|
+
trustedWorkloads.set(grant, material);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
grant = yield* provider.acquireForMutation(request, decision);
|
|
228
|
+
}
|
|
229
|
+
// npm publication tokens never become observation capabilities. GitHub
|
|
230
|
+
// retains its truthfully bundled installation-token read/write model.
|
|
231
|
+
if (grant._tag === "ScopedSecret" && request.provider !== "npm") {
|
|
232
|
+
rememberObservationGrant(observationGrants, grant);
|
|
233
|
+
}
|
|
234
|
+
return grant;
|
|
235
|
+
})
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
/** Environment-backed provider projection for hosts that need no sinks. */
|
|
239
|
+
export const makeEnvironmentCredentialProvider = () => makeProvider(new Map(), new WeakMap(), new WeakMap());
|
|
240
|
+
const hasAuthorizationHeader = (headers) => headers !== undefined && Object.keys(headers).some((name) => name.toLowerCase() === "authorization");
|
|
241
|
+
const audienceAllows = (audience, requested) => {
|
|
242
|
+
try {
|
|
243
|
+
const expected = new URL(audience);
|
|
244
|
+
const observed = new URL(requested);
|
|
245
|
+
if (expected.protocol !== "https:" || observed.protocol !== "https:")
|
|
246
|
+
return false;
|
|
247
|
+
if (expected.username !== "" || expected.password !== "" || expected.search !== "" || expected.hash !== "")
|
|
248
|
+
return false;
|
|
249
|
+
if (expected.origin !== observed.origin)
|
|
250
|
+
return false;
|
|
251
|
+
const base = expected.pathname.endsWith("/") ? expected.pathname : `${expected.pathname}/`;
|
|
252
|
+
return observed.pathname === expected.pathname || observed.pathname.startsWith(base);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const githubUploadAudienceAllows = (audience, requested) => {
|
|
259
|
+
try {
|
|
260
|
+
const expected = new URL(audience);
|
|
261
|
+
const observed = new URL(requested);
|
|
262
|
+
if (expected.protocol !== "https:" || expected.hostname !== "api.github.com" ||
|
|
263
|
+
expected.port !== "" || expected.username !== "" || expected.password !== "" ||
|
|
264
|
+
expected.search !== "" || expected.hash !== "")
|
|
265
|
+
return false;
|
|
266
|
+
if (observed.protocol !== "https:" || observed.hostname !== "uploads.github.com" ||
|
|
267
|
+
observed.port !== "" || observed.username !== "" || observed.password !== "" ||
|
|
268
|
+
observed.hash !== "")
|
|
269
|
+
return false;
|
|
270
|
+
const repository = /^\/repos\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/u.exec(expected.pathname)?.[0];
|
|
271
|
+
if (repository === undefined ||
|
|
272
|
+
!new RegExp(`^${repository.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\/releases\/[1-9][0-9]*\/assets$`, "u")
|
|
273
|
+
.test(observed.pathname))
|
|
274
|
+
return false;
|
|
275
|
+
const keys = [...observed.searchParams.keys()];
|
|
276
|
+
return /^\?name=[^&]+(?:&label=[^&]*)?$/u.test(observed.search) &&
|
|
277
|
+
new Set(keys).size === keys.length && observed.searchParams.get("name").length > 0;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const checkAudience = (subject, audience, requested, allowGithubUpload = false) => audienceAllows(audience, requested) ||
|
|
284
|
+
(allowGithubUpload && githubUploadAudienceAllows(audience, requested))
|
|
285
|
+
? Effect.void
|
|
286
|
+
: Effect.fail(new CredentialAudienceMismatch({
|
|
287
|
+
subject,
|
|
288
|
+
expected: audience,
|
|
289
|
+
observed: requested
|
|
290
|
+
}));
|
|
291
|
+
const lookupToken = (vault, subject, ref) => {
|
|
292
|
+
const value = vault.get(vaultKey(subject, ref));
|
|
293
|
+
return value === undefined
|
|
294
|
+
? Effect.fail(new CredentialUnavailable({
|
|
295
|
+
subject,
|
|
296
|
+
provider: "unknown",
|
|
297
|
+
purpose: "unknown",
|
|
298
|
+
reason: "The credential grant has no host-owned secret in this platform boundary."
|
|
299
|
+
}))
|
|
300
|
+
: Effect.succeed(value);
|
|
301
|
+
};
|
|
302
|
+
const validateObservationGrant = Effect.fn("HttpAuthorizer.validateGrant")(function* (input, grant, observationGrants) {
|
|
303
|
+
const issued = observationGrants.get(grant);
|
|
304
|
+
if (issued === undefined) {
|
|
305
|
+
return yield* new CredentialUnavailable({
|
|
306
|
+
subject: input.subject,
|
|
307
|
+
provider: "unknown",
|
|
308
|
+
purpose: "observe",
|
|
309
|
+
reason: "The supplied observation grant was not issued by this platform boundary."
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (issued.subject !== input.subject) {
|
|
313
|
+
return yield* new CredentialSubjectMismatch({
|
|
314
|
+
expected: input.subject,
|
|
315
|
+
observed: issued.subject
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (!issued.purposes.has("observe")) {
|
|
319
|
+
return yield* new CredentialPurposeMismatch({
|
|
320
|
+
subject: input.subject,
|
|
321
|
+
required: "observe",
|
|
322
|
+
granted: [...issued.purposes]
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
yield* checkAudience(input.subject, issued.audience, input.url);
|
|
326
|
+
return issued;
|
|
327
|
+
});
|
|
328
|
+
const sendAnonymous = (http, request) => {
|
|
329
|
+
if (hasAuthorizationHeader(request.headers)) {
|
|
330
|
+
return Effect.fail(platformError("observe", "before-dispatch", "Callers may not supply an Authorization header."));
|
|
331
|
+
}
|
|
332
|
+
return http.request({
|
|
333
|
+
method: request.method,
|
|
334
|
+
url: request.url,
|
|
335
|
+
...(request.headers === undefined ? {} : { headers: request.headers }),
|
|
336
|
+
...(request.body === undefined ? {} : { body: request.body })
|
|
337
|
+
}).pipe(Effect.mapError(() => platformError("observe", "unknown", "Observation HTTP transport failed after dispatch.")));
|
|
338
|
+
};
|
|
339
|
+
const sendAuthorized = (http, request, token, phase, credentialScheme = "bearer") => {
|
|
340
|
+
if (hasAuthorizationHeader(request.headers)) {
|
|
341
|
+
return Effect.fail(platformError(phase, "before-dispatch", "Callers may not supply an Authorization header."));
|
|
342
|
+
}
|
|
343
|
+
const headers = {
|
|
344
|
+
...(request.headers ?? {}),
|
|
345
|
+
authorization: credentialScheme === "pypi-token-basic"
|
|
346
|
+
? `Basic ${Buffer.from(`__token__:${Redacted.value(token)}`, "utf8").toString("base64")}`
|
|
347
|
+
: `Bearer ${Redacted.value(token)}`
|
|
348
|
+
};
|
|
349
|
+
return http.request({
|
|
350
|
+
method: request.method,
|
|
351
|
+
url: request.url,
|
|
352
|
+
headers,
|
|
353
|
+
...(request.body === undefined ? {} : { body: request.body })
|
|
354
|
+
}).pipe(Effect.mapError(() => platformError(phase, "unknown", "Authorized HTTP transport failed after dispatch.")));
|
|
355
|
+
};
|
|
356
|
+
const makeHttpAuthorizer = (http, vault, observationGrants) => ({
|
|
357
|
+
execute: Effect.fn("HttpAuthorizer.execute")(function* (input, grant) {
|
|
358
|
+
const issued = yield* validateObservationGrant(input, grant, observationGrants);
|
|
359
|
+
if (issued._tag === "AnonymousAccess") {
|
|
360
|
+
return yield* sendAnonymous(http, input);
|
|
361
|
+
}
|
|
362
|
+
const token = yield* lookupToken(vault, issued.subject, issued.ref);
|
|
363
|
+
return yield* sendAuthorized(http, input, token, "observe");
|
|
364
|
+
})
|
|
365
|
+
});
|
|
366
|
+
const makeAuthorizedMutationHttp = (http, vault) => ({
|
|
367
|
+
execute: Effect.fn("AuthorizedMutationHttp.execute")(function* (operation, request, grant) {
|
|
368
|
+
yield* validateGrantForOperation(operation, grant);
|
|
369
|
+
if ((operation.provider === "pypi") !== (request.credentialScheme === "pypi-token-basic")) {
|
|
370
|
+
return yield* platformError("mutate", "before-dispatch", "The PyPI Basic credential projection is accepted only for typed PyPI mutations and is mandatory for them.");
|
|
371
|
+
}
|
|
372
|
+
yield* checkAudience(operation.subject, operation.audience, request.url, operation.provider === "github");
|
|
373
|
+
const token = yield* lookupToken(vault, grant.subject, grant.ref);
|
|
374
|
+
return yield* sendAuthorized(http, request, token, "mutate", request.credentialScheme);
|
|
375
|
+
})
|
|
376
|
+
});
|
|
377
|
+
const npmUserConfigs = new WeakMap();
|
|
378
|
+
const npmAuthLine = (registryUrl, token) => {
|
|
379
|
+
const registry = new URL(registryUrl);
|
|
380
|
+
const pathname = registry.pathname.endsWith("/") ? registry.pathname : `${registry.pathname}/`;
|
|
381
|
+
return `//${registry.host}${pathname}:_authToken=${Redacted.value(token)}`;
|
|
382
|
+
};
|
|
383
|
+
const makeNpmUserConfigResource = (vault, temporaryRoot) => ({
|
|
384
|
+
acquire: Effect.fn("NpmUserConfigResource.acquire")(function* (input, grant) {
|
|
385
|
+
yield* validateGrantForOperation(input.operation, grant);
|
|
386
|
+
if (input.operation.provider !== "npm") {
|
|
387
|
+
return yield* platformError("resource", "before-dispatch", "npm user config accepts only the npm provider.");
|
|
388
|
+
}
|
|
389
|
+
yield* checkAudience(input.operation.subject, input.operation.audience, input.registryUrl);
|
|
390
|
+
const token = yield* lookupToken(vault, grant.subject, grant.ref);
|
|
391
|
+
const root = temporaryRoot ?? tmpdir();
|
|
392
|
+
const handle = makeNpmUserConfigHandle();
|
|
393
|
+
const acquired = Effect.tryPromise({
|
|
394
|
+
try: async () => {
|
|
395
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
396
|
+
const directory = await mkdtemp(join(root, "ts-release-npm-"));
|
|
397
|
+
try {
|
|
398
|
+
const path = join(directory, "userconfig");
|
|
399
|
+
const globalConfigPath = join(directory, "globalconfig");
|
|
400
|
+
const contents = `${npmAuthLine(input.registryUrl, token)}\nignore-scripts=true\n`;
|
|
401
|
+
await writeFile(path, contents, { mode: 0o600, flag: "wx" });
|
|
402
|
+
await writeFile(globalConfigPath, "", { mode: 0o600, flag: "wx" });
|
|
403
|
+
npmUserConfigs.set(handle, {
|
|
404
|
+
directory,
|
|
405
|
+
path,
|
|
406
|
+
globalConfigPath,
|
|
407
|
+
subject: input.operation.subject,
|
|
408
|
+
grant,
|
|
409
|
+
token
|
|
410
|
+
});
|
|
411
|
+
return handle;
|
|
412
|
+
}
|
|
413
|
+
catch (cause) {
|
|
414
|
+
await rm(directory, { recursive: true, force: true });
|
|
415
|
+
throw cause;
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
catch: () => platformError("resource", "before-dispatch", "Unable to create the scoped npm user config.")
|
|
419
|
+
});
|
|
420
|
+
return yield* Effect.acquireRelease(acquired, (resource) => {
|
|
421
|
+
const metadata = npmUserConfigs.get(resource);
|
|
422
|
+
npmUserConfigs.delete(resource);
|
|
423
|
+
return metadata === undefined
|
|
424
|
+
? Effect.void
|
|
425
|
+
: Effect.tryPromise({
|
|
426
|
+
try: () => rm(metadata.directory, { recursive: true, force: true }),
|
|
427
|
+
catch: () => undefined
|
|
428
|
+
}).pipe(Effect.catch(() => Effect.void));
|
|
429
|
+
});
|
|
430
|
+
})
|
|
431
|
+
});
|
|
432
|
+
const collect = (stream) => Stream.mkString(Stream.decodeText(stream));
|
|
433
|
+
const minimumTrustedNode = [22, 14, 0];
|
|
434
|
+
const minimumTrustedNpm = [11, 5, 1];
|
|
435
|
+
const parseVersion = (value) => {
|
|
436
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?\s*$/u.exec(value);
|
|
437
|
+
if (match === null)
|
|
438
|
+
return undefined;
|
|
439
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
440
|
+
};
|
|
441
|
+
const versionAtLeast = (value, minimum) => value[0] > minimum[0] ||
|
|
442
|
+
(value[0] === minimum[0] && (value[1] > minimum[1] ||
|
|
443
|
+
(value[1] === minimum[1] && value[2] >= minimum[2])));
|
|
444
|
+
const optionalPath = Config.option(Config.string("PATH")).pipe(Effect.map(Option.getOrUndefined), Effect.orElseSucceed(() => undefined));
|
|
445
|
+
const closedBaseEnvironment = (path) => path === undefined ? {} : { PATH: path };
|
|
446
|
+
const optionalReleaseHome = Config.option(Config.string("TS_RELEASE_HOME")).pipe(Effect.map(Option.getOrUndefined), Effect.orElseSucceed(() => undefined));
|
|
447
|
+
const privateTrustedNpmEnvironment = Effect.fn("CertifiedPublisherSpawn.privateTrustedNpmEnvironment")(function* (grant) {
|
|
448
|
+
const configured = yield* optionalReleaseHome;
|
|
449
|
+
if (configured === undefined || !isAbsolute(configured) || resolve(configured) !== configured) {
|
|
450
|
+
return yield* new CredentialUnavailable({
|
|
451
|
+
subject: grant.subject,
|
|
452
|
+
provider: grant.provider,
|
|
453
|
+
purpose: "publish",
|
|
454
|
+
reason: "Trusted npm publishing requires one canonical absolute TS_RELEASE_HOME."
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return yield* Effect.tryPromise({
|
|
458
|
+
try: async () => {
|
|
459
|
+
const canonicalHome = await realpath(configured);
|
|
460
|
+
if (canonicalHome !== configured)
|
|
461
|
+
throw new Error("TS_RELEASE_HOME is not canonical.");
|
|
462
|
+
const homeMetadata = await stat(canonicalHome);
|
|
463
|
+
const owner = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
464
|
+
if (!homeMetadata.isDirectory() || (homeMetadata.mode & 0o077) !== 0 ||
|
|
465
|
+
(owner !== undefined && homeMetadata.uid !== owner)) {
|
|
466
|
+
throw new Error("TS_RELEASE_HOME is not one private caller-owned directory.");
|
|
467
|
+
}
|
|
468
|
+
const userConfig = join(canonicalHome, "npm-userconfig");
|
|
469
|
+
const globalConfig = join(canonicalHome, "npm-globalconfig");
|
|
470
|
+
for (const path of [userConfig, globalConfig]) {
|
|
471
|
+
if (await realpath(path) !== path)
|
|
472
|
+
throw new Error("A trusted npm config path is not canonical.");
|
|
473
|
+
const metadata = await stat(path);
|
|
474
|
+
if (!metadata.isFile() || (metadata.mode & 0o777) !== 0o600 ||
|
|
475
|
+
(owner !== undefined && metadata.uid !== owner) || (await readFile(path)).length !== 0) {
|
|
476
|
+
throw new Error("A trusted npm config is not an empty private caller-owned file.");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
HOME: canonicalHome,
|
|
481
|
+
NPM_CONFIG_USERCONFIG: userConfig,
|
|
482
|
+
NPM_CONFIG_GLOBALCONFIG: globalConfig
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
catch: () => new CredentialUnavailable({
|
|
486
|
+
subject: grant.subject,
|
|
487
|
+
provider: grant.provider,
|
|
488
|
+
purpose: "publish",
|
|
489
|
+
reason: "Trusted npm publishing requires validated empty private npm configuration files."
|
|
490
|
+
})
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
const workloadEnvironment = Effect.fn("CertifiedPublisherSpawn.workloadEnvironment")(function* (trustedWorkloads, grant) {
|
|
494
|
+
if (grant.names.size !== oidcNames.length || oidcNames.some((name) => !grant.names.has(name))) {
|
|
495
|
+
return yield* new CredentialStrategyUnsupported({
|
|
496
|
+
subject: grant.subject,
|
|
497
|
+
provider: grant.provider,
|
|
498
|
+
strategy: "trusted-publishing",
|
|
499
|
+
reason: "Workload identity lacks the exact certified GitHub Actions OIDC names."
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const material = trustedWorkloads.get(grant);
|
|
503
|
+
if (material === undefined) {
|
|
504
|
+
return yield* new CredentialUnavailable({
|
|
505
|
+
subject: grant.subject,
|
|
506
|
+
provider: grant.provider,
|
|
507
|
+
purpose: "publish",
|
|
508
|
+
reason: "Certified GitHub Actions provenance facts are unavailable in this platform boundary."
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const path = yield* optionalPath;
|
|
512
|
+
const privateEnvironment = yield* privateTrustedNpmEnvironment(grant);
|
|
513
|
+
const env = {
|
|
514
|
+
...closedBaseEnvironment(path),
|
|
515
|
+
...privateEnvironment
|
|
516
|
+
};
|
|
517
|
+
for (const name of oidcNames) {
|
|
518
|
+
const value = material.oidc.get(name);
|
|
519
|
+
if (value === undefined) {
|
|
520
|
+
return yield* new CredentialUnavailable({
|
|
521
|
+
subject: grant.subject,
|
|
522
|
+
provider: grant.provider,
|
|
523
|
+
purpose: "publish",
|
|
524
|
+
reason: "Certified workload identity material is unavailable in this platform boundary."
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
env[name] = Redacted.value(value);
|
|
528
|
+
}
|
|
529
|
+
// npm's OIDC exchange consumes the two host-held request values. Its
|
|
530
|
+
// libnpmpublish provenance builder separately consumes this exact validated
|
|
531
|
+
// non-secret GitHub run snapshot. No ambient environment is inherited.
|
|
532
|
+
Object.assign(env, material.snapshot);
|
|
533
|
+
env.NPM_CONFIG_IGNORE_SCRIPTS = "true";
|
|
534
|
+
return env;
|
|
535
|
+
});
|
|
536
|
+
const validateTrustedNpmOperation = Effect.fn("CertifiedPublisherSpawn.validateTrustedNpmOperation")(function* (operation, grant) {
|
|
537
|
+
if (operation.provider !== "npm" || operation.purpose !== "publish" ||
|
|
538
|
+
operation.audience !== "https://registry.npmjs.org/" ||
|
|
539
|
+
grant.strategy.identityProvider !== "github-actions" ||
|
|
540
|
+
grant.strategy.runnerClass !== "github-hosted" ||
|
|
541
|
+
grant.strategy.allowedAction !== "npm-publish-direct" ||
|
|
542
|
+
grant.strategy.publisherSink !== "certified-npm-cli") {
|
|
543
|
+
return yield* new CredentialStrategyUnsupported({
|
|
544
|
+
subject: operation.subject,
|
|
545
|
+
provider: operation.provider,
|
|
546
|
+
strategy: "trusted-publishing",
|
|
547
|
+
reason: "The workload grant is not bound to the certified direct npm publisher sink."
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
const publisherSpecKeys = Object.freeze({
|
|
552
|
+
NpmPublisherSpec: [
|
|
553
|
+
"_tag", "access", "cwd", "distTag", "operation", "packageName", "provenance",
|
|
554
|
+
"registryUrl", "tarballPath", "userConfig", "version"
|
|
555
|
+
],
|
|
556
|
+
WorkloadPublisherSpec: [
|
|
557
|
+
"_tag", "access", "cwd", "distTag", "operation", "packageName", "provenance",
|
|
558
|
+
"registryUrl", "tarballPath", "version"
|
|
559
|
+
]
|
|
560
|
+
});
|
|
561
|
+
const sameKeys = (value, expected) => {
|
|
562
|
+
const observed = Object.keys(value).sort();
|
|
563
|
+
return observed.length === expected.length && observed.every((key, index) => key === expected[index]);
|
|
564
|
+
};
|
|
565
|
+
const certifiedNpmSpecIssue = (spec) => {
|
|
566
|
+
const tagDescriptor = Object.getOwnPropertyDescriptor(spec, "_tag");
|
|
567
|
+
const tag = tagDescriptor !== undefined && "value" in tagDescriptor
|
|
568
|
+
? tagDescriptor.value
|
|
569
|
+
: undefined;
|
|
570
|
+
if (tag !== "NpmPublisherSpec" && tag !== "WorkloadPublisherSpec") {
|
|
571
|
+
return "The certified npm publisher accepts only its two closed typed spec variants.";
|
|
572
|
+
}
|
|
573
|
+
const expectedKeys = publisherSpecKeys[tag];
|
|
574
|
+
const descriptors = Object.getOwnPropertyDescriptors(spec);
|
|
575
|
+
if (!sameKeys(spec, expectedKeys) || expectedKeys.some((key) => {
|
|
576
|
+
const descriptor = descriptors[key];
|
|
577
|
+
return descriptor === undefined || !("value" in descriptor);
|
|
578
|
+
})) {
|
|
579
|
+
return "The certified npm publisher accepts only its exact typed fields; generic argv and override fields are forbidden.";
|
|
580
|
+
}
|
|
581
|
+
const operation = spec.operation;
|
|
582
|
+
if (operation._tag !== "PublishOperation" || operation.provider !== "npm" || operation.purpose !== "publish") {
|
|
583
|
+
return "The certified npm publisher accepts only an npm publish operation.";
|
|
584
|
+
}
|
|
585
|
+
if (typeof spec.packageName !== "string" || spec.packageName.length === 0 ||
|
|
586
|
+
typeof spec.version !== "string" || Semver.valid(spec.version) !== spec.version) {
|
|
587
|
+
return "The certified npm publisher requires one nonempty package name and canonical semantic version.";
|
|
588
|
+
}
|
|
589
|
+
if (operation.subject !== `npm:${spec.packageName}@${spec.version}`) {
|
|
590
|
+
return "The certified npm publisher package and version do not match the authorized operation subject.";
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
if (canonicalizeRegistryUrl(spec.registryUrl) !== spec.registryUrl ||
|
|
594
|
+
operation.audience.toString() !== spec.registryUrl.toString()) {
|
|
595
|
+
return "The certified npm publisher registry does not match the authorized canonical audience.";
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return "The certified npm publisher requires a canonical registry and npm distribution tag.";
|
|
600
|
+
}
|
|
601
|
+
if (Semver.validRange(spec.distTag) !== null || spec.distTag.trim() !== spec.distTag ||
|
|
602
|
+
encodeURIComponent(spec.distTag) !== spec.distTag) {
|
|
603
|
+
return "The certified npm publisher requires a canonical registry and npm distribution tag.";
|
|
604
|
+
}
|
|
605
|
+
if ((spec.access !== "public" && spec.access !== "restricted") ||
|
|
606
|
+
(spec.provenance !== "required" && spec.provenance !== "automatic" && spec.provenance !== "disabled") ||
|
|
607
|
+
(spec._tag === "NpmPublisherSpec" && spec.provenance === "automatic") ||
|
|
608
|
+
(spec.access === "restricted" && !spec.packageName.startsWith("@")) ||
|
|
609
|
+
(Semver.prerelease(spec.version) !== null && spec.distTag === "latest")) {
|
|
610
|
+
return "The certified npm publisher access, provenance, or dist-tag policy is not an allowed canonical value.";
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
const fileName = basename(spec.tarballPath);
|
|
614
|
+
if (!isAbsolute(spec.cwd) || resolve(spec.cwd) !== spec.cwd ||
|
|
615
|
+
!/^[a-f0-9]{64}$/u.test(fileName) ||
|
|
616
|
+
resolve(spec.cwd, "blobs", fileName) !== spec.tarballPath) {
|
|
617
|
+
return "The certified npm publisher tarball must be the canonical prepared-store blob path.";
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
return "The certified npm publisher tarball must be the canonical prepared-store blob path.";
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
};
|
|
625
|
+
const validateCertifiedNpmSpec = Effect.fn("CertifiedPublisherSpawn.validateCertifiedNpmSpec")(function* (spec) {
|
|
626
|
+
const issue = certifiedNpmSpecIssue(spec);
|
|
627
|
+
if (issue !== undefined) {
|
|
628
|
+
return yield* new CredentialStrategyUnsupported({
|
|
629
|
+
subject: spec.operation.subject,
|
|
630
|
+
provider: spec.operation.provider,
|
|
631
|
+
strategy: spec._tag === "WorkloadPublisherSpec" ? "trusted-publishing" : "token",
|
|
632
|
+
reason: issue
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
/** Snapshot caller data once so accessors or concurrent mutation cannot change the admitted command after validation. */
|
|
637
|
+
const snapshotCertifiedNpmSpec = (spec) => {
|
|
638
|
+
const operation = Object.freeze({
|
|
639
|
+
...spec.operation,
|
|
640
|
+
decision: Object.freeze({ ...spec.operation.decision })
|
|
641
|
+
});
|
|
642
|
+
return Object.freeze({ ...spec, operation });
|
|
643
|
+
};
|
|
644
|
+
const publisherEnvironment = Effect.fn("CertifiedPublisherSpawn.environment")(function* (vault, trustedWorkloads, spec, grant) {
|
|
645
|
+
yield* validateCertifiedNpmSpec(spec);
|
|
646
|
+
if (spec._tag === "NpmPublisherSpec") {
|
|
647
|
+
if (grant._tag !== "ScopedSecret") {
|
|
648
|
+
return yield* new CredentialStrategyUnsupported({
|
|
649
|
+
subject: spec.operation.subject,
|
|
650
|
+
provider: spec.operation.provider,
|
|
651
|
+
strategy: "trusted-publishing",
|
|
652
|
+
reason: "A scoped npm user config requires a scoped-secret grant."
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
const metadata = npmUserConfigs.get(spec.userConfig);
|
|
656
|
+
if (metadata === undefined || metadata.subject !== spec.operation.subject || metadata.grant !== grant) {
|
|
657
|
+
return yield* new CredentialUnavailable({
|
|
658
|
+
subject: spec.operation.subject,
|
|
659
|
+
provider: spec.operation.provider,
|
|
660
|
+
purpose: spec.operation.purpose,
|
|
661
|
+
reason: "The npm user config is not active for this exact operation and grant."
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
const path = yield* optionalPath;
|
|
665
|
+
return {
|
|
666
|
+
...closedBaseEnvironment(path),
|
|
667
|
+
HOME: metadata.directory,
|
|
668
|
+
NPM_CONFIG_USERCONFIG: metadata.path,
|
|
669
|
+
NPM_CONFIG_GLOBALCONFIG: metadata.globalConfigPath,
|
|
670
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
if (grant._tag !== "WorkloadIdentity") {
|
|
674
|
+
return yield* new CredentialStrategyUnsupported({
|
|
675
|
+
subject: spec.operation.subject,
|
|
676
|
+
provider: spec.operation.provider,
|
|
677
|
+
strategy: "token",
|
|
678
|
+
reason: "The workload publisher requires a workload-identity grant."
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
yield* validateTrustedNpmOperation(spec.operation, grant);
|
|
682
|
+
return yield* workloadEnvironment(trustedWorkloads, grant);
|
|
683
|
+
});
|
|
684
|
+
const redactPublisherOutput = (value, env, spec) => {
|
|
685
|
+
const metadata = spec._tag === "NpmPublisherSpec" ? npmUserConfigs.get(spec.userConfig) : undefined;
|
|
686
|
+
const known = metadata === undefined
|
|
687
|
+
? env
|
|
688
|
+
: { ...env, PUBLISH_CREDENTIAL: Redacted.value(metadata.token) };
|
|
689
|
+
return redactOutput(value, known);
|
|
690
|
+
};
|
|
691
|
+
/**
|
|
692
|
+
* npm classifies a suffixless local path as a package directory. Prepared
|
|
693
|
+
* artifacts deliberately use digest-only blob names, so expose the already
|
|
694
|
+
* admitted bytes through a private `.tgz` transport name only while npm runs.
|
|
695
|
+
*/
|
|
696
|
+
const makeNpmPublishTransport = (sourcePath, temporaryRoot) => {
|
|
697
|
+
const root = temporaryRoot ?? tmpdir();
|
|
698
|
+
const acquired = Effect.tryPromise({
|
|
699
|
+
try: async () => {
|
|
700
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
701
|
+
const directory = await mkdtemp(join(root, "ts-release-npm-tarball-"));
|
|
702
|
+
try {
|
|
703
|
+
const path = join(directory, "package.tgz");
|
|
704
|
+
await copyFile(sourcePath, path);
|
|
705
|
+
await chmod(path, 0o600);
|
|
706
|
+
return { directory, path };
|
|
707
|
+
}
|
|
708
|
+
catch (cause) {
|
|
709
|
+
await rm(directory, { recursive: true, force: true });
|
|
710
|
+
throw cause;
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
catch: () => platformError("resource", "before-dispatch", "Unable to materialize the verified npm tarball transport path.")
|
|
714
|
+
});
|
|
715
|
+
return Effect.acquireRelease(acquired, ({ directory }) => Effect.tryPromise({
|
|
716
|
+
try: () => rm(directory, { recursive: true, force: true }),
|
|
717
|
+
catch: () => undefined
|
|
718
|
+
}).pipe(Effect.catch(() => Effect.void)));
|
|
719
|
+
};
|
|
720
|
+
const makeCertifiedPublisherSpawn = (spawner, vault, trustedWorkloads, temporaryRoot) => {
|
|
721
|
+
const runClosed = Effect.fn("CertifiedPublisherSpawn.runClosed")(function* (argv) {
|
|
722
|
+
const path = yield* optionalPath;
|
|
723
|
+
const command = ChildProcess.make(argv[0], [...argv.slice(1)], {
|
|
724
|
+
env: closedBaseEnvironment(path),
|
|
725
|
+
extendEnv: false,
|
|
726
|
+
stdin: "ignore",
|
|
727
|
+
stdout: "pipe",
|
|
728
|
+
stderr: "pipe"
|
|
729
|
+
});
|
|
730
|
+
return yield* Effect.scoped(Effect.gen(function* () {
|
|
731
|
+
const spawned = yield* Effect.exit(spawner.spawn(command));
|
|
732
|
+
if (Exit.isFailure(spawned)) {
|
|
733
|
+
return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight could not start its version probe.");
|
|
734
|
+
}
|
|
735
|
+
const output = yield* Effect.exit(Effect.all({
|
|
736
|
+
stdout: collect(spawned.value.stdout),
|
|
737
|
+
stderr: collect(spawned.value.stderr),
|
|
738
|
+
exitCode: spawned.value.exitCode
|
|
739
|
+
}, { concurrency: "unbounded" }));
|
|
740
|
+
if (Exit.isFailure(output)) {
|
|
741
|
+
return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight could not observe its version probe.");
|
|
742
|
+
}
|
|
743
|
+
if (Number(output.value.exitCode) !== 0) {
|
|
744
|
+
return yield* platformError("spawn", "before-dispatch", "Trusted npm preflight version probe was rejected.");
|
|
745
|
+
}
|
|
746
|
+
return output.value.stdout;
|
|
747
|
+
}));
|
|
748
|
+
});
|
|
749
|
+
return {
|
|
750
|
+
preflightTrustedNpm: Effect.fn("CertifiedPublisherSpawn.preflightTrustedNpm")(function* (operation, grant) {
|
|
751
|
+
yield* validateGrantForOperation(operation, grant);
|
|
752
|
+
if (grant._tag !== "WorkloadIdentity") {
|
|
753
|
+
return yield* new CredentialStrategyUnsupported({
|
|
754
|
+
subject: operation.subject,
|
|
755
|
+
provider: operation.provider,
|
|
756
|
+
strategy: "token",
|
|
757
|
+
reason: "Trusted npm preflight requires a workload-identity grant."
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
yield* validateTrustedNpmOperation(operation, grant);
|
|
761
|
+
// Validate the exact host-held OIDC names before any provider authority is dispatched.
|
|
762
|
+
yield* workloadEnvironment(trustedWorkloads, grant);
|
|
763
|
+
const node = parseVersion(yield* runClosed(["node", "--version"]));
|
|
764
|
+
const npm = parseVersion(yield* runClosed(["npm", "--version"]));
|
|
765
|
+
if (node === undefined || !versionAtLeast(node, minimumTrustedNode) ||
|
|
766
|
+
npm === undefined || !versionAtLeast(npm, minimumTrustedNpm)) {
|
|
767
|
+
return yield* platformError("spawn", "before-dispatch", "Trusted npm publishing requires Node >=22.14.0 and npm >=11.5.1.");
|
|
768
|
+
}
|
|
769
|
+
}),
|
|
770
|
+
spawn: Effect.fn("CertifiedPublisherSpawn.spawn")(function* (inputSpec, grant) {
|
|
771
|
+
yield* validateGrantForOperation(inputSpec.operation, grant);
|
|
772
|
+
yield* validateCertifiedNpmSpec(inputSpec);
|
|
773
|
+
const spec = snapshotCertifiedNpmSpec(inputSpec);
|
|
774
|
+
const env = yield* publisherEnvironment(vault, trustedWorkloads, spec, grant);
|
|
775
|
+
return yield* Effect.scoped(Effect.gen(function* () {
|
|
776
|
+
const transport = yield* makeNpmPublishTransport(spec.tarballPath, temporaryRoot);
|
|
777
|
+
const argv = npmPublishArgv({ ...spec, tarballPath: transport.path });
|
|
778
|
+
const command = ChildProcess.make(argv[0], [...argv.slice(1)], {
|
|
779
|
+
cwd: spec.cwd,
|
|
780
|
+
env,
|
|
781
|
+
extendEnv: false,
|
|
782
|
+
stdin: "ignore",
|
|
783
|
+
stdout: "pipe",
|
|
784
|
+
stderr: "pipe"
|
|
785
|
+
});
|
|
786
|
+
const spawned = yield* Effect.exit(spawner.spawn(command));
|
|
787
|
+
if (Exit.isFailure(spawned)) {
|
|
788
|
+
return {
|
|
789
|
+
_tag: "RejectedBeforeStart",
|
|
790
|
+
commitment: "before-dispatch",
|
|
791
|
+
reason: "The certified publisher process did not return a started handle."
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const output = yield* Effect.exit(Effect.all({
|
|
795
|
+
stdout: collect(spawned.value.stdout),
|
|
796
|
+
stderr: collect(spawned.value.stderr),
|
|
797
|
+
exitCode: spawned.value.exitCode
|
|
798
|
+
}, { concurrency: "unbounded" }));
|
|
799
|
+
if (Exit.isFailure(output)) {
|
|
800
|
+
return {
|
|
801
|
+
_tag: "PublisherOutcomeUnknown",
|
|
802
|
+
commitment: "unknown",
|
|
803
|
+
reason: "The certified publisher started, but its final outcome could not be observed."
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
_tag: "PublisherExited",
|
|
808
|
+
commitment: "started",
|
|
809
|
+
exitCode: Number(output.value.exitCode),
|
|
810
|
+
stdout: redactPublisherOutput(output.value.stdout, env, spec),
|
|
811
|
+
stderr: redactPublisherOutput(output.value.stderr, env, spec)
|
|
812
|
+
};
|
|
813
|
+
}));
|
|
814
|
+
})
|
|
815
|
+
};
|
|
816
|
+
};
|
|
817
|
+
/** All host sinks share one private vault; no secret is returned from it. */
|
|
818
|
+
export const makeEnvironmentCredentialPlatform = (http, spawner, options = {}) => {
|
|
819
|
+
const vault = new Map();
|
|
820
|
+
const trustedWorkloads = new WeakMap();
|
|
821
|
+
const observationGrants = new WeakMap();
|
|
822
|
+
return {
|
|
823
|
+
credentialProvider: makeProvider(vault, trustedWorkloads, observationGrants),
|
|
824
|
+
httpAuthorizer: makeHttpAuthorizer(http, vault, observationGrants),
|
|
825
|
+
authorizedMutationHttp: makeAuthorizedMutationHttp(http, vault),
|
|
826
|
+
npmUserConfigResource: makeNpmUserConfigResource(vault, options.temporaryRoot),
|
|
827
|
+
certifiedPublisherSpawn: makeCertifiedPublisherSpawn(spawner, vault, trustedWorkloads, options.temporaryRoot)
|
|
828
|
+
};
|
|
829
|
+
};
|
|
830
|
+
export const makeEnvironmentCredentialPlatformLayer = (http, spawner, options = {}) => {
|
|
831
|
+
const platform = makeEnvironmentCredentialPlatform(http, spawner, options);
|
|
832
|
+
return Layer.mergeAll(Layer.succeed(CredentialProvider)(platform.credentialProvider), Layer.succeed(HttpAuthorizer)(platform.httpAuthorizer), Layer.succeed(AuthorizedMutationHttp)(platform.authorizedMutationHttp), Layer.succeed(NpmUserConfigResource)(platform.npmUserConfigResource), Layer.succeed(CertifiedPublisherSpawn)(platform.certifiedPublisherSpawn));
|
|
833
|
+
};
|
|
834
|
+
//# sourceMappingURL=credentials.js.map
|