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 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 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 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" } }