From 317155d0c8bdca2e44395e9ec68f332a94008f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B1=B1=E8=B2=B4=E8=87=B3?= Date: Wed, 27 May 2026 15:09:06 +0900 Subject: [PATCH 1/4] build: pin Node 24 in .nvmrc for OIDC trusted publishing npm CLI v10 (bundled with Node 20) does not implement OIDC trusted publishing for `npm publish --provenance`. v11.5+ (bundled with Node 22+) is required, and Node 24 is recommended. Add `.nvmrc` so the release workflow can pick Node 24 via `actions/setup-node` with `node-version-file: '.nvmrc'`. The runtime requirement is scoped to the CI runner only. The library consumer minimum (root `engines.node`, `>=20.10.0` per AGENTS.md Tier 1) is intentionally not raised - OIDC trusted publishing affects publish-time tooling, not the published artifacts. Co-Authored-By: Claude Opus 4.7 (1M context) --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 From fd313683ed35ed7a8d97911772764a1c3ba2e51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B1=B1=E8=B2=B4=E8=87=B3?= Date: Wed, 27 May 2026 15:09:06 +0900 Subject: [PATCH 2/4] build: prepare each package.json for OIDC trusted publishing Add the metadata that npm's provenance attestation pipeline validates strictly, and the publish-time flags that the OIDC trusted publisher flow expects: - `publishConfig.provenance: true` so each package opts in to SLSA provenance v1 attestation by default - `homepage` + `bugs.url` for consistent npm registry display - `prepublishOnly: "pnpm -w run build"` so the workspace builds before any local `pnpm pack` / `npm publish` invocation `repository.url` + `repository.directory` were already correct on every package, so the provenance validator's URL match is covered. The consumer-facing `engines.node` minimum stays at `>=20.10.0` per AGENTS.md Tier 1. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/basou/package.json | 12 +++++++++++- packages/cli/package.json | 12 ++++++++++-- packages/core/package.json | 10 ++++++++-- packages/sdk/package.json | 12 ++++++++++-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/basou/package.json b/packages/basou/package.json index e9a4d853..d8e0952d 100644 --- a/packages/basou/package.json +++ b/packages/basou/package.json @@ -21,7 +21,14 @@ "url": "git+https://github.com/basou-dev/basou.git", "directory": "packages/basou" }, - "publishConfig": { "access": "public" }, + "homepage": "https://basou.dev", + "bugs": { + "url": "https://github.com/basou-dev/basou/issues" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, "bin": { "basou": "./bin.mjs" }, @@ -29,6 +36,9 @@ "engines": { "node": ">=20.10.0" }, + "scripts": { + "prepublishOnly": "pnpm -w run build" + }, "dependencies": { "@basou/cli": "workspace:*" } diff --git a/packages/cli/package.json b/packages/cli/package.json index 1cab2029..6ca871cd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,7 +11,14 @@ "url": "git+https://github.com/basou-dev/basou.git", "directory": "packages/cli" }, - "publishConfig": { "access": "public" }, + "homepage": "https://basou.dev", + "bugs": { + "url": "https://github.com/basou-dev/basou/issues" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", @@ -34,7 +41,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", - "clean": "rm -rf dist .tsup *.tsbuildinfo" + "clean": "rm -rf dist .tsup *.tsbuildinfo", + "prepublishOnly": "pnpm -w run build" }, "dependencies": { "@basou/core": "workspace:*", diff --git a/packages/core/package.json b/packages/core/package.json index 1c0eec08..cf8bc753 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,8 +11,13 @@ "url": "git+https://github.com/basou-dev/basou.git", "directory": "packages/core" }, + "homepage": "https://basou.dev", + "bugs": { + "url": "https://github.com/basou-dev/basou/issues" + }, "publishConfig": { - "access": "public" + "access": "public", + "provenance": true }, "sideEffects": false, "main": "./dist/index.js", @@ -34,7 +39,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", - "clean": "rm -rf dist .tsup *.tsbuildinfo" + "clean": "rm -rf dist .tsup *.tsbuildinfo", + "prepublishOnly": "pnpm -w run build" }, "dependencies": { "simple-git": "^3.36.0", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index bfec9c6b..210a43ad 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -11,7 +11,14 @@ "url": "git+https://github.com/basou-dev/basou.git", "directory": "packages/sdk" }, - "publishConfig": { "access": "public" }, + "homepage": "https://basou.dev", + "bugs": { + "url": "https://github.com/basou-dev/basou/issues" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, "sideEffects": false, "main": "./dist/index.js", "module": "./dist/index.js", @@ -32,6 +39,7 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", - "clean": "rm -rf dist .tsup *.tsbuildinfo" + "clean": "rm -rf dist .tsup *.tsbuildinfo", + "prepublishOnly": "pnpm -w run build" } } From 7b3fb95fd1300be710ef5e3acea5cb29da8b3263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B1=B1=E8=B2=B4=E8=87=B3?= Date: Wed, 27 May 2026 15:09:06 +0900 Subject: [PATCH 3/4] ci: enable Dependabot for github-actions and npm ecosystems Keep SHA-pinned actions fresh and surface upstream npm dependency updates on a weekly cadence. Initial SHA pinning is still manual (the github-actions ecosystem does not perform tag-to-SHA conversion on its own); Dependabot only updates pins that already exist. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/dependabot.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..afa0f4d8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,38 @@ +version: 2 + +# All third-party GitHub Actions are SHA-pinned (Codex W2 supply-chain +# hardening). Dependabot does NOT convert tag pins to SHA pins on its +# own, so pinning must be done by hand at commit time; Dependabot then +# keeps the existing SHA pins fresh. + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: '09:00' + timezone: 'Asia/Tokyo' + open-pull-requests-limit: 5 + labels: + - dependencies + - github-actions + commit-message: + prefix: 'Update' + include: scope + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: '09:00' + timezone: 'Asia/Tokyo' + open-pull-requests-limit: 5 + labels: + - dependencies + - npm + commit-message: + prefix: 'Update' + include: scope + versioning-strategy: increase From 2f416698741a2eed9000ad17c8798cb6258a6f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B1=B1=E8=B2=B4=E8=87=B3?= Date: Wed, 27 May 2026 15:09:06 +0900 Subject: [PATCH 4/4] ci: add OIDC trusted-publish release workflow A tag push of `vX.Y.Z` runs three jobs: 1. verify - pnpm install + build + typecheck + lint + test, the same gate as quality.yml but anchored to the tagged tree. 2. publish-npm - fan out across `@basou/{core,cli,sdk}` and publish each through npm's OIDC trusted-publisher path, with SLSA provenance v1 attestation. No long-lived `NPM_TOKEN` secret is used; the runner obtains an OIDC token at job time. 3. github-release - pack each package with `pnpm pack`, sign every tarball with cosign sign-blob (keyless, Sigstore Fulcio + Rekor), and attach both the tarball and the cosign bundle to a GitHub Release with auto-generated notes. `workflow_dispatch` is wired up for dry-runs only: the publish path is gated on `$GITHUB_EVENT_NAME = "push"`, and github-release is additionally gated on `inputs.dry_run != true`, so a manual dispatch can never produce a real publish or release. All third-party actions are SHA-pinned. Inline comments in the workflow call out the precise reason for each defensive step (notably: omitting `registry-url` on `actions/setup-node` and rewriting ~/.npmrc to a single registry line so the OIDC handshake takes over instead of falling back to an empty bearer token). The `basou` unscoped package is not yet listed in the matrix; the npm support ticket for that name is still pending. Once the name resolves the matrix can grow a fourth entry without any other changes to this workflow. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 171 ++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..2b36357d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,171 @@ +name: Release + +# vX.Y.Z tag push triggers the full release. workflow_dispatch only +# permits dry-runs so that the OIDC handshake can be smoke-tested +# without publishing. + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + dry_run: + description: 'Skip the actual publish / release-create. Use to smoke-test the workflow on a non-tag run.' + type: boolean + default: true + +# Minimum-required default; each job tightens or expands as needed +# (Codex W2 / supply-chain hardening). +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + NODE_OPTIONS: '--max-old-space-size=4096' + +jobs: + # ========================================================== + # 1. verify — confirm the tagged tree is typecheck / lint / + # test green before any publish step runs. + # ========================================================== + verify: + name: Verify (typecheck / lint / test) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install pnpm + uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + # `.nvmrc` is pinned to Node 24, which ships npm v11.5+ — + # required for OIDC trusted publishing. + node-version-file: '.nvmrc' + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build all workspace packages + run: pnpm -r run build + - name: Typecheck + run: pnpm -r typecheck + - name: Lint + run: pnpm run lint + - name: Test + run: pnpm -r --if-present test + + # ========================================================== + # 2. publish-npm — publish each package through npm's OIDC + # trusted publisher path, with SLSA provenance v1 attestation. + # ========================================================== + publish-npm: + name: Publish ${{ matrix.pkg }} to npm + needs: verify + runs-on: ubuntu-latest + permissions: + contents: read + # Required so the runner can fetch an OIDC token to exchange + # with the npm registry and Sigstore Fulcio. + id-token: write + strategy: + fail-fast: false + matrix: + include: + - pkg: '@basou/core' + dir: core + - pkg: '@basou/cli' + dir: cli + - pkg: '@basou/sdk' + dir: sdk + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Install pnpm + uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6 + + # `registry-url` is intentionally omitted: when it is set, + # `actions/setup-node` writes a `_authToken=${NODE_AUTH_TOKEN}` + # line into ~/.npmrc that the npm CLI then sends as an empty + # bearer token, bypassing the OIDC trusted-publisher path and + # producing a 404. The separate step below rewrites ~/.npmrc to + # contain only the registry line. + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Configure npm registry without a token line (so OIDC takes over) + run: | + echo "registry=https://registry.npmjs.org/" > ~/.npmrc + cat ~/.npmrc + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build all workspace packages so workspace:* refs resolve + run: pnpm -r run build + + - name: pnpm pack + npm publish (provenance via OIDC) + working-directory: packages/${{ matrix.dir }} + run: | + # Real publish runs only on tag push. workflow_dispatch + # always exercises the dry-run branch — the dry_run input + # is informational only. + if [ "$GITHUB_EVENT_NAME" = "push" ]; then + echo "Publishing ${{ matrix.pkg }} via npm CLI (pnpm pack handles the workspace:* rewrite)" + pnpm pack + TGZ=$(ls *.tgz | head -1) + echo "Packed tarball: $TGZ" + npm publish "$TGZ" --access public --provenance + else + echo "Dry-run mode (event=$GITHUB_EVENT_NAME, dry_run=${{ inputs.dry_run }}): pnpm publish --dry-run for ${{ matrix.pkg }}" + pnpm publish --access public --provenance --no-git-checks --dry-run + fi + + # ========================================================== + # 3. github-release — attach pnpm-packed tarballs plus cosign + # sign-blob bundles as release assets, with auto-generated + # release notes. + # ========================================================== + github-release: + name: Create GitHub Release with signed npm tarballs + needs: [publish-npm] + if: ${{ inputs.dry_run != true && github.event_name == 'push' }} + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install pnpm + uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build all workspace packages + run: pnpm -r run build + - name: Pack tarballs (using pnpm to rewrite workspace:*, matching publish-npm) + run: | + mkdir -p artifacts + for d in core cli sdk; do + (cd "packages/$d" && pnpm pack --pack-destination ../../artifacts) + done + ls -la artifacts/ + - name: Install cosign + uses: sigstore/cosign-installer@f713795cb21599bc4e5c4b58cbad1da852d7eeb9 # v3 + - name: Sign each tarball with cosign sign-blob + run: | + for f in artifacts/*.tgz; do + cosign sign-blob --yes --bundle "$f.bundle" "$f" + done + - name: Create GitHub Release + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + with: + tag_name: ${{ github.ref_name }} + name: Basou ${{ github.ref_name }} + generate_release_notes: true + files: | + artifacts/*.tgz + artifacts/*.bundle