From 0883fb9bcca4c3b4579b5d4168b26bcfbe0dc606 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 23:48:53 +0000 Subject: [PATCH 1/6] ci: Add preview publish workflow for PRs Automatically publishes a preview package to npm for each open PR, versioned as 0.0.0-pr.. with a pr- dist-tag. Cleans up the dist-tag when the PR is closed. https://claude.ai/code/session_01KU3xVUdpt6CP6sjwRFFydY --- .github/workflows/preview-publish.yml | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/preview-publish.yml diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml new file mode 100644 index 0000000..267cf56 --- /dev/null +++ b/.github/workflows/preview-publish.yml @@ -0,0 +1,115 @@ +name: Preview Publish + +on: + pull_request: + types: [opened, synchronize, reopened, closed] + branches: [main] + +concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + publish-preview: + name: Publish Preview + if: >- + github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.17.0 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "pnpm" + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Set preview version + id: version + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + SHORT_SHA=$(git rev-parse --short HEAD) + PREVIEW_VERSION="0.0.0-pr.${PR_NUMBER}.${SHORT_SHA}" + npm version "$PREVIEW_VERSION" --no-git-tag-version + echo "version=$PREVIEW_VERSION" >> "$GITHUB_OUTPUT" + echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT" + + - name: Publish preview package + run: npm publish --tag ${{ steps.version.outputs.tag }} --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const version = '${{ steps.version.outputs.version }}'; + const tag = '${{ steps.version.outputs.tag }}'; + const marker = ''; + const body = [ + marker, + '## Preview Package Published', + '', + '```', + `pnpm add effect-jmap@${version}`, + '# or', + `pnpm add effect-jmap@${tag}`, + '```', + '', + `_Published from commit ${context.sha.substring(0, 7)}_`, + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + const existing = comments.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.payload.pull_request.number, + body, + }); + } + + cleanup-preview: + name: Cleanup Preview + if: >- + github.event.action == 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Remove dist-tag + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + TAG="pr-${{ github.event.pull_request.number }}" + npm dist-tag rm effect-jmap "$TAG" 2>/dev/null || true From ac3edac22494e2d4a2a0bf63ee140149b7630a1f Mon Sep 17 00:00:00 2001 From: Ilnur Khalilov Date: Wed, 11 Feb 2026 01:02:54 +0100 Subject: [PATCH 2/6] fix: Add npm provenance to preview publish workflow Adds id-token: write permission and NPM_CONFIG_PROVENANCE to bypass 2FA/OTP requirement, matching the release workflow approach. --- .github/workflows/preview-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml index 267cf56..2287fbb 100644 --- a/.github/workflows/preview-publish.yml +++ b/.github/workflows/preview-publish.yml @@ -18,6 +18,7 @@ jobs: runs-on: ubuntu-latest permissions: pull-requests: write + id-token: write steps: - uses: actions/checkout@v4 @@ -51,6 +52,7 @@ jobs: run: npm publish --tag ${{ steps.version.outputs.tag }} --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true - name: Comment on PR uses: actions/github-script@v7 From dbfb8c86c4c53f51559218cbfed46a5e2e25840f Mon Sep 17 00:00:00 2001 From: Ilnur Khalilov Date: Wed, 11 Feb 2026 01:09:52 +0100 Subject: [PATCH 3/6] fix: Use OIDC trusted publishing instead of NPM_TOKEN Remove static token auth and use npm OIDC token exchange to bypass 2FA requirement, matching how semantic-release publishes. --- .github/workflows/preview-publish.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml index 2287fbb..c1c56a1 100644 --- a/.github/workflows/preview-publish.yml +++ b/.github/workflows/preview-publish.yml @@ -49,10 +49,7 @@ jobs: echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT" - name: Publish preview package - run: npm publish --tag ${{ steps.version.outputs.tag }} --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_CONFIG_PROVENANCE: true + run: npm publish --tag ${{ steps.version.outputs.tag }} --access public --provenance - name: Comment on PR uses: actions/github-script@v7 From f1b6da89216aa137a52a2d2cabf162882647deed Mon Sep 17 00:00:00 2001 From: Ilnur Khalilov Date: Wed, 11 Feb 2026 01:11:55 +0100 Subject: [PATCH 4/6] fix: Restore NPM_TOKEN for preview publish OIDC trusted publishing only supports one workflow per package. Use granular token with bypass 2FA instead. --- .github/workflows/preview-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml index c1c56a1..e2909c8 100644 --- a/.github/workflows/preview-publish.yml +++ b/.github/workflows/preview-publish.yml @@ -50,6 +50,8 @@ jobs: - name: Publish preview package run: npm publish --tag ${{ steps.version.outputs.tag }} --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Comment on PR uses: actions/github-script@v7 From f372b2f6cb034cad5044e17f8fe7c289c1870902 Mon Sep 17 00:00:00 2001 From: Ilnur Khalilov Date: Wed, 11 Feb 2026 01:13:07 +0100 Subject: [PATCH 5/6] fix: Merge preview publish into release workflow for OIDC support Move preview publish and cleanup jobs into publish.yml so they share the same trusted publisher configuration. This lets preview publishes use OIDC token exchange instead of a static NPM_TOKEN, bypassing 2FA. --- .github/workflows/preview-publish.yml | 116 -------------------------- .github/workflows/publish.yml | 113 +++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 116 deletions(-) delete mode 100644 .github/workflows/preview-publish.yml diff --git a/.github/workflows/preview-publish.yml b/.github/workflows/preview-publish.yml deleted file mode 100644 index e2909c8..0000000 --- a/.github/workflows/preview-publish.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Preview Publish - -on: - pull_request: - types: [opened, synchronize, reopened, closed] - branches: [main] - -concurrency: - group: preview-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - publish-preview: - name: Publish Preview - if: >- - github.event.action != 'closed' && - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - pull-requests: write - id-token: write - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 10.17.0 - - - uses: actions/setup-node@v4 - with: - node-version: "24" - cache: "pnpm" - registry-url: "https://registry.npmjs.org" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Set preview version - id: version - run: | - PR_NUMBER=${{ github.event.pull_request.number }} - SHORT_SHA=$(git rev-parse --short HEAD) - PREVIEW_VERSION="0.0.0-pr.${PR_NUMBER}.${SHORT_SHA}" - npm version "$PREVIEW_VERSION" --no-git-tag-version - echo "version=$PREVIEW_VERSION" >> "$GITHUB_OUTPUT" - echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT" - - - name: Publish preview package - run: npm publish --tag ${{ steps.version.outputs.tag }} --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Comment on PR - uses: actions/github-script@v7 - with: - script: | - const version = '${{ steps.version.outputs.version }}'; - const tag = '${{ steps.version.outputs.tag }}'; - const marker = ''; - const body = [ - marker, - '## Preview Package Published', - '', - '```', - `pnpm add effect-jmap@${version}`, - '# or', - `pnpm add effect-jmap@${tag}`, - '```', - '', - `_Published from commit ${context.sha.substring(0, 7)}_`, - ].join('\n'); - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - }); - const existing = comments.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.payload.pull_request.number, - body, - }); - } - - cleanup-preview: - name: Cleanup Preview - if: >- - github.event.action == 'closed' && - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - steps: - - uses: actions/setup-node@v4 - with: - node-version: "24" - registry-url: "https://registry.npmjs.org" - - - name: Remove dist-tag - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: | - TAG="pr-${{ github.event.pull_request.number }}" - npm dist-tag rm effect-jmap "$TAG" 2>/dev/null || true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8445896..f08ff14 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,11 +3,19 @@ name: Release on: push: branches: [main] + pull_request: + types: [opened, synchronize, reopened, closed] + branches: [main] workflow_dispatch: +concurrency: + group: publish-${{ github.event.pull_request.number || 'release' }} + cancel-in-progress: true + jobs: release: name: Release + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest permissions: contents: write @@ -49,3 +57,108 @@ jobs: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} NPM_CONFIG_PROVENANCE: true run: pnpm exec semantic-release + + publish-preview: + name: Publish Preview + if: >- + github.event_name == 'pull_request' && + github.event.action != 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.17.0 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "pnpm" + registry-url: "https://registry.npmjs.org" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + - name: Set preview version + id: version + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + SHORT_SHA=$(git rev-parse --short HEAD) + PREVIEW_VERSION="0.0.0-pr.${PR_NUMBER}.${SHORT_SHA}" + npm version "$PREVIEW_VERSION" --no-git-tag-version + echo "version=$PREVIEW_VERSION" >> "$GITHUB_OUTPUT" + echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT" + + - name: Publish preview package + run: npm publish --tag ${{ steps.version.outputs.tag }} --access public --provenance + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const version = '${{ steps.version.outputs.version }}'; + const tag = '${{ steps.version.outputs.tag }}'; + const marker = ''; + const body = [ + marker, + '## Preview Package Published', + '', + '```', + `pnpm add effect-jmap@${version}`, + '# or', + `pnpm add effect-jmap@${tag}`, + '```', + '', + `_Published from commit ${context.sha.substring(0, 7)}_`, + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + }); + const existing = comments.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.payload.pull_request.number, + body, + }); + } + + cleanup-preview: + name: Cleanup Preview + if: >- + github.event_name == 'pull_request' && + github.event.action == 'closed' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Remove dist-tag + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + TAG="pr-${{ github.event.pull_request.number }}" + npm dist-tag rm effect-jmap "$TAG" 2>/dev/null || true From b46af2c5e055ed30ac1ec131a6185c4faffa1fc2 Mon Sep 17 00:00:00 2001 From: Ilnur Khalilov Date: Wed, 11 Feb 2026 01:15:11 +0100 Subject: [PATCH 6/6] docs: Document publishing setup in AGENTS.md --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 9264b94..0bd4774 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,13 @@ A TypeScript library implementing RFC 8621 JMAP for Mail using Effect-TS. - **Sentence-case** subjects: `fix: Correct the return type` (not `fix: correct...`) - No "Generated with" banners or co-author attributions +## Publishing + +- Both release and preview publishing live in `publish.yml` to share a single npm OIDC trusted publisher +- Release (push to main): uses `semantic-release` with OIDC token exchange +- Preview (pull requests): publishes `0.0.0-pr..` versions via `npm publish --provenance` with OIDC +- Do not create separate publishing workflow files — npm only allows one trusted publisher per package + ## Implementing JMAP Methods See [docs/implementing-spec-features.md](docs/implementing-spec-features.md) for the workflow.