From 9d8821fdcbc45bd2b3054782ee1e662e32f07f81 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 22:06:35 +0000 Subject: [PATCH 1/3] ci: use npm Trusted Publishing and add changeset check - Remove NPM_TOKEN dependency, use OIDC-based Trusted Publishing - Add NPM_CONFIG_PROVENANCE for provenance attestation - Add changeset-check.yml to enforce changesets in PRs --- .github/workflows/changeset-check.yml | 52 +++++++++++++++++++++++++++ .github/workflows/publish.yml | 7 ++-- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/changeset-check.yml diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml new file mode 100644 index 0000000..1d439c1 --- /dev/null +++ b/.github/workflows/changeset-check.yml @@ -0,0 +1,52 @@ +name: Changeset Check + +on: + pull_request: + branches: + - main + +jobs: + check: + name: Check for changeset + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check for changeset + run: | + # Skip check for release PRs and bot PRs + if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then + echo "Skipping changeset check for release PR" + exit 0 + fi + + # Check if any changeset files exist (excluding config.json) + CHANGESETS=$(find .changeset -name "*.md" -type f 2>/dev/null | wc -l) + + if [ "$CHANGESETS" -eq 0 ]; then + echo "::error::No changeset found! Please run 'pnpm changeset' to create one." + echo "" + echo "A changeset describes what changes you made and how they affect the packages." + echo "" + echo "Run: pnpm changeset" + echo "Then select the packages you changed and the type of change (patch/minor/major)." + exit 1 + fi + + echo "Found $CHANGESETS changeset(s)" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b64c7b9..4723e18 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,11 +46,10 @@ jobs: id: changesets uses: changesets/action@v1 with: - publish: pnpm release - version: pnpm version + publish: pnpm run release + version: pnpm run version title: 'chore: release packages' commit: 'chore: release packages' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true From fc1fd706f835746d3611b19e0bc29333cc966c3b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 22:15:30 +0000 Subject: [PATCH 2/3] ci: add changeset bot for PR comments - Add /changeset command to create changesets via PR comments - Update changeset-check to post helpful instructions - Supports: /changeset patch|minor|major [packages] --- .github/workflows/changeset-bot.yml | 162 ++++++++++++++++++++++++++ .github/workflows/changeset-check.yml | 82 +++++++++---- 2 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/changeset-bot.yml diff --git a/.github/workflows/changeset-bot.yml b/.github/workflows/changeset-bot.yml new file mode 100644 index 0000000..aa4e949 --- /dev/null +++ b/.github/workflows/changeset-bot.yml @@ -0,0 +1,162 @@ +name: Changeset Bot + +on: + issue_comment: + types: [created] + +jobs: + changeset: + name: Create Changeset + if: | + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/changeset') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Get PR branch + id: pr + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + core.setOutput('branch', pr.data.head.ref); + core.setOutput('title', pr.data.title); + + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr.outputs.branch }} + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Parse command + id: parse + uses: actions/github-script@v7 + with: + script: | + const comment = context.payload.comment.body.trim(); + const lines = comment.split('\n'); + const firstLine = lines[0].trim(); + + // Parse: /changeset [patch|minor|major] [package1,package2] + const match = firstLine.match(/^\/changeset\s+(patch|minor|major)(?:\s+(.+))?$/i); + + if (!match) { + core.setFailed('Invalid format. Use: /changeset [package1,package2]'); + return; + } + + const bump = match[1].toLowerCase(); + const packagesArg = match[2]; + + // Get summary from rest of comment or PR title + let summary = lines.slice(1).join('\n').trim(); + if (!summary) { + summary = '${{ steps.pr.outputs.title }}'; + } + + core.setOutput('bump', bump); + core.setOutput('packages', packagesArg || ''); + core.setOutput('summary', summary); + + - name: Get changed packages + id: packages + run: | + if [ -n "${{ steps.parse.outputs.packages }}" ]; then + echo "packages=${{ steps.parse.outputs.packages }}" >> $GITHUB_OUTPUT + else + # Auto-detect from changed files + CHANGED=$(git diff --name-only origin/main...HEAD | grep -E "^(ts-cache|storages)/" | cut -d'/' -f1-2 | sort -u | tr '\n' ',' | sed 's/,$//') + if [ -z "$CHANGED" ]; then + CHANGED="ts-cache" + fi + echo "packages=$CHANGED" >> $GITHUB_OUTPUT + fi + + - name: Create changeset + run: | + BUMP="${{ steps.parse.outputs.bump }}" + PACKAGES="${{ steps.packages.outputs.packages }}" + SUMMARY="${{ steps.parse.outputs.summary }}" + + # Generate random filename + FILENAME=".changeset/$(echo $RANDOM | md5sum | head -c 12).md" + + # Map folder names to package names + get_package_name() { + case "$1" in + ts-cache) echo "@node-ts-cache/core" ;; + storages/lru) echo "@node-ts-cache/lru-storage" ;; + storages/redis) echo "@node-ts-cache/redis-storage" ;; + storages/redisio) echo "@node-ts-cache/ioredis-storage" ;; + storages/node-cache) echo "@node-ts-cache/node-cache-storage" ;; + storages/lru-redis) echo "@node-ts-cache/lru-redis-storage" ;; + *) echo "$1" ;; + esac + } + + # Create changeset content + echo "---" > "$FILENAME" + + IFS=',' read -ra PKGS <<< "$PACKAGES" + for pkg in "${PKGS[@]}"; do + pkg=$(echo "$pkg" | xargs) # trim whitespace + pkg_name=$(get_package_name "$pkg") + echo "\"$pkg_name\": $BUMP" >> "$FILENAME" + done + + echo "---" >> "$FILENAME" + echo "" >> "$FILENAME" + echo "$SUMMARY" >> "$FILENAME" + + echo "Created changeset:" + cat "$FILENAME" + + - name: Commit and push + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add .changeset/ + git commit -m "chore: add changeset" + git push + + - name: Comment success + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `✅ Changeset created!\n\n**Type:** ${{ steps.parse.outputs.bump }}\n**Packages:** ${{ steps.packages.outputs.packages }}\n**Summary:** ${{ steps.parse.outputs.summary }}` + }); + + - name: Comment failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `❌ Failed to create changeset. Please check the [workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.\n\n**Usage:**\n\`\`\`\n/changeset patch\n/changeset minor\n/changeset major @node-ts-cache/core,@node-ts-cache/lru-storage\n\`\`\`\n\nAdd a summary on the next line:\n\`\`\`\n/changeset patch\nFixed a bug in cache expiration\n\`\`\`` + }); diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml index 1d439c1..b34eac0 100644 --- a/.github/workflows/changeset-check.yml +++ b/.github/workflows/changeset-check.yml @@ -9,6 +9,8 @@ jobs: check: name: Check for changeset runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Checkout repository @@ -16,37 +18,69 @@ jobs: with: fetch-depth: 0 - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - name: Check for changeset + id: check run: | - # Skip check for release PRs and bot PRs + # Skip check for release PRs if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then - echo "Skipping changeset check for release PR" + echo "skip=true" >> $GITHUB_OUTPUT exit 0 fi - # Check if any changeset files exist (excluding config.json) - CHANGESETS=$(find .changeset -name "*.md" -type f 2>/dev/null | wc -l) + # Check if any changeset files exist (excluding config.json and README) + CHANGESETS=$(find .changeset -name "*.md" -type f ! -name "README.md" 2>/dev/null | wc -l) if [ "$CHANGESETS" -eq 0 ]; then - echo "::error::No changeset found! Please run 'pnpm changeset' to create one." - echo "" - echo "A changeset describes what changes you made and how they affect the packages." - echo "" - echo "Run: pnpm changeset" - echo "Then select the packages you changed and the type of change (patch/minor/major)." - exit 1 + echo "found=false" >> $GITHUB_OUTPUT + exit 0 fi - echo "Found $CHANGESETS changeset(s)" + echo "found=true" >> $GITHUB_OUTPUT + echo "count=$CHANGESETS" >> $GITHUB_OUTPUT + + - name: Post or update comment + if: steps.check.outputs.skip != 'true' + uses: actions/github-script@v7 + with: + script: | + const found = '${{ steps.check.outputs.found }}' === 'true'; + const count = '${{ steps.check.outputs.count }}'; + + const marker = ''; + + let body; + if (found) { + body = `${marker}\n✅ **Changeset found** (${count} changeset file(s))\n\nThis PR will be included in the next release.`; + } else { + body = `${marker}\n⚠️ **No changeset found**\n\nThis PR doesn't have a changeset. If this PR should trigger a release, please add one:\n\n### Option 1: Comment command\nComment on this PR:\n\`\`\`\n/changeset patch\nYour change description here\n\`\`\`\n\nOr specify packages:\n\`\`\`\n/changeset minor @node-ts-cache/core\nAdded new caching feature\n\`\`\`\n\n### Option 2: Manual\n\`\`\`bash\npnpm changeset\ngit add .changeset && git commit -m "chore: add changeset" && git push\n\`\`\`\n\n---\n*If this PR doesn't need a release (e.g., docs, CI changes), you can ignore this message.*`; + } + + // Find existing comment + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const existing = comments.data.find(c => c.body.includes(marker)); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } + + // Fail the check if no changeset found + if (!found) { + core.setFailed('No changeset found. See comment for instructions.'); + } From 773a00e51212aa17379f421446dbd231c37fb07a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 22:51:32 +0000 Subject: [PATCH 3/3] ci: add emoji reactions to changeset bot --- .github/workflows/changeset-bot.yml | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/changeset-bot.yml b/.github/workflows/changeset-bot.yml index aa4e949..cdecba2 100644 --- a/.github/workflows/changeset-bot.yml +++ b/.github/workflows/changeset-bot.yml @@ -16,6 +16,17 @@ jobs: pull-requests: write steps: + - name: React to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + - name: Get PR branch id: pr uses: actions/github-script@v7 @@ -138,6 +149,17 @@ jobs: git commit -m "chore: add changeset" git push + - name: React success + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket' + }); + - name: Comment success uses: actions/github-script@v7 with: @@ -149,6 +171,18 @@ jobs: body: `✅ Changeset created!\n\n**Type:** ${{ steps.parse.outputs.bump }}\n**Packages:** ${{ steps.packages.outputs.packages }}\n**Summary:** ${{ steps.parse.outputs.summary }}` }); + - name: React failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused' + }); + - name: Comment failure if: failure() uses: actions/github-script@v7