Skip to content

chore(release): v1.4.1 — atomic six-package republish with looser @pu… #10

chore(release): v1.4.1 — atomic six-package republish with looser @pu…

chore(release): v1.4.1 — atomic six-package republish with looser @pu… #10

Workflow file for this run

# Release — atomic six-package npm publish with isolated SLSA Build L3
# provenance + SBOM generation + signed GitHub release.
#
# Trigger: tag v*.*.* on main. The release is atomic — any step
# failure aborts before any `npm publish`. There is NO long-lived
# NPM_TOKEN; npm trusted publishing via OIDC is the only auth path.
#
# Build, pack, and SBOM generation happen without `id-token: write`.
# `.github/workflows/sign-and-publish.yml` is the reusable signing lane
# that receives OIDC + attestation permissions.
#
# The six-package set is asserted by
# packages/k8s-baseline/tests/release-readiness.test.ts:
# - @hulumi/baseline
# - @hulumi/policies
# - @hulumi/drift
# - @hulumi/k8s-baseline (added in runbook hulumi-operations-k8s-security M1)
# - @hulumi/cloudflare-baseline
# - @hulumi/platform-patterns
name: release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: read
jobs:
preflight:
name: pre-flight (build + test + lint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6
with:
version: 9.12.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --frozen-lockfile
- run: pnpm run lint:exact-pin-guard
- run: pnpm -r build
- run: pnpm -r typecheck
- run: pnpm -r test
- run: pnpm -r lint
- run: pnpm run lint:license-boundary
- run: pnpm run format:check
build-release-artifacts:
name: build release artifacts (no OIDC signing)
needs: preflight
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6
with:
version: 9.12.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22.14.0
cache: pnpm
registry-url: https://registry.npmjs.org
- name: Use npm CLI pinned for release tooling
run: |
npm install -g npm@11.5.1
node --version
npm --version
- run: pnpm install --frozen-lockfile
- run: pnpm -r build
- name: Generate tarballs
run: |
mkdir -p .release-artifacts
ABS_DEST="$(pwd)/.release-artifacts"
for pkg in baseline policies drift k8s-baseline cloudflare-baseline platform-patterns; do
(cd "packages/$pkg" && pnpm pack --pack-destination "$ABS_DEST")
done
ls -la .release-artifacts/
- name: Generate tarball digest manifest
run: |
set -euo pipefail
sha256sum .release-artifacts/*.tgz > .release-artifacts/tarballs-sha256.txt
cat .release-artifacts/tarballs-sha256.txt
- name: Generate SBOMs (CycloneDX, via cdxgen)
# cdxgen is the multi-package-manager CycloneDX generator. It reads
# `pnpm-lock.yaml` natively, so no transient `package-lock.json`
# round-trip is needed. The previous fix (`npm install
# --package-lock-only`) was intercepted by Corepack — when the
# workspace's `package.json#packageManager` is `pnpm@9.12.0`,
# Corepack rejects or rewrites direct npm calls, so the lockfile
# never got created and `@cyclonedx/cyclonedx-npm` errored with
# "No evidence: no package lock file nor `node_modules` dir".
# Switching to cdxgen sidesteps the Corepack interaction
# entirely.
env:
# cdxgen reads `pnpm-lock.yaml` from the workspace root and
# the package.json from the per-package directory. Run from
# the repo root so pnpm-lock.yaml is discoverable.
FETCH_LICENSE: "true"
run: |
set -euo pipefail
for pkg in baseline policies drift k8s-baseline cloudflare-baseline platform-patterns; do
npx --yes @cyclonedx/cdxgen@11.10.0 \
--type js \
--no-recurse \
--spec-version 1.5 \
--output ".release-artifacts/sbom-${pkg}.cdx.json" \
"packages/$pkg"
done
- name: Verify release-artifact directory before upload (diagnostic)
# The v1.4.0 release run regressed at this point — upload-artifact
# said "No files were found" even though the previous `Generate
# tarballs` step had ls-ed six .tgz files in this directory. This
# diagnostic step captures the directory contents immediately
# before the upload, fails the job loudly if the dir is empty, and
# records the file count + total size in the run log. See PR #186
# post-mortem.
run: |
set -euo pipefail
test -d .release-artifacts || { echo "::error::.release-artifacts/ does not exist"; exit 1; }
ls -la .release-artifacts/
count=$(find .release-artifacts -mindepth 1 -maxdepth 1 -type f | wc -l)
echo "file count: $count"
if [ "$count" -eq 0 ]; then
echo "::error::.release-artifacts/ is empty immediately before upload"
exit 1
fi
- name: Upload release artifacts for isolated signing
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: hulumi-release-artifacts
# v5+ default `include-hidden-files: false` skips any directory
# or file whose name starts with `.`. Because `.release-artifacts/`
# is itself dot-prefixed, every path under it counts as hidden
# and the action emits "No files were found" even when the
# directory contains the .tgz + SBOM files (see PR #187
# post-mortem and the diagnostic step's `ls -la` output).
# Setting include-hidden-files: true uploads the contents of the
# dot-prefixed staging directory without changing repo-level
# hidden-file handling (this glob is scoped to .release-artifacts).
include-hidden-files: true
path: .release-artifacts/**
if-no-files-found: error
retention-days: 7
sign-and-publish:
name: SLSA attest + npm publish (reusable)
needs: build-release-artifacts
uses: ./.github/workflows/sign-and-publish.yml
permissions:
actions: read
contents: write
id-token: write
attestations: write
with:
artifact-name: hulumi-release-artifacts
package-list: baseline policies drift k8s-baseline cloudflare-baseline platform-patterns