From f1d82e96f6cb962ded9d5b8f93af039f7c84a0e9 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 20 Apr 2026 16:36:43 -0700 Subject: [PATCH 1/2] Add --pat-pr and --pat-comments flags for CI commands Allow users to opt in to using BUMPY_GH_TOKEN for PR creation and comment posting, not just git push. Useful when the token belongs to a dedicated bot account. When not set, PRs/comments still come from github-actions[bot] so a developer's PAT doesn't block self-review. Also fixes CI not triggering on new version PRs by adding a second push after PR creation when --pat-pr is not used. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yaml | 11 +++- .github/workflows/release.yaml | 16 ++++-- docs/cli.md | 6 ++- docs/github-actions.md | 25 +++++++--- llms.md | 10 ++-- packages/bumpy/src/cli.ts | 2 + packages/bumpy/src/commands/ci.ts | 83 +++++++++++++++++++++++++------ 7 files changed, 119 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd6b172..f2a8b4e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,10 +22,17 @@ jobs: - uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 - run: bun install - # Build first since we use the local workspace package instead of the published one + + # --- You wont need this part --- + # Build first since we use the local built version of bumpy instead of the published one - run: bun run --filter @varlock/bumpy build # run bun install again to make the now built CLI available - run: bun install - - run: bunx @varlock/bumpy ci check --fail-on-missing + # ------------------------------- + + # 🐸 This is the important part - checks for missing bump files and posts/updates a PR comment with the release plan + # (use --pat-comments if PAT belongs to dedicated bot, rather than person) + - run: bunx @varlock/bumpy ci check --fail-on-missing --pat-comments env: GH_TOKEN: ${{ github.token }} + BUMPY_GH_TOKEN: ${{ secrets.BUMPY_GH_TOKEN }} # <- PAT needed so that release PR will trigger CI diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8da6e1a..cb54722 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -15,16 +15,22 @@ jobs: with: fetch-depth: 0 - uses: oven-sh/setup-bun@v2 - # Node.js is needed for npm publish (trusted publishing via OIDC) + # Node.js (npm) is needed for npm publish - uses: actions/setup-node@v6 with: - node-version: lts/* + node-version: lts/* # newer node needed for OIDC - run: bun install - # Build first since we use the local workspace package instead of the published one + + # --- You wont need this part --- + # Build first since we use the local built version of bumpy instead of the published one - run: bun run --filter @varlock/bumpy build # run bun install again to make the now built CLI available - run: bun install - - run: bunx @varlock/bumpy ci release + # ------------------------------- + + # 🐸 This is the important part - creates/updates release PR when PRs merge to main, publishes packages when release PR is merged + # (use --pat-pr if PAT belongs to dedicated bot, rather than person) + - run: bunx @varlock/bumpy ci release --pat-pr env: GH_TOKEN: ${{ github.token }} - BUMPY_GH_TOKEN: ${{ secrets.BUMPY_GH_TOKEN }} + BUMPY_GH_TOKEN: ${{ secrets.BUMPY_GH_TOKEN }} # <- PAT needed so that release PR will trigger CI diff --git a/docs/cli.md b/docs/cli.md index f5241bc..ce71fc2 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -135,8 +135,9 @@ bumpy ci check --fail-on-missing | ------------------- | ---------------------------------------------------------------- | | `--comment` | Force PR comment on or off (default: auto-detect CI environment) | | `--fail-on-missing` | Exit 1 if changed packages have no bump files | +| `--pat-comments` | Post PR comments using `BUMPY_GH_TOKEN` instead of `GH_TOKEN` | -Requires `GH_TOKEN` environment variable. +Requires `GH_TOKEN` environment variable. The `--pat-comments` flag requires `BUMPY_GH_TOKEN` — use it when the token belongs to a dedicated automation account (bot user). If you're using a developer's personal PAT, leave this off so comments appear from `github-actions[bot]`. ## `bumpy ci release` @@ -157,8 +158,9 @@ bumpy ci release --auto-publish --tag beta | `--auto-publish` | Version + publish directly instead of creating a PR | | `--tag ` | npm dist-tag (for `--auto-publish`) | | `--branch ` | Version PR branch name (default: `bumpy/version-packages`) | +| `--pat-pr` | Create/edit the version PR using `BUMPY_GH_TOKEN` | -Requires `GH_TOKEN`. Optionally uses `BUMPY_GH_TOKEN` to create PRs that trigger other workflows (see [GitHub Actions setup](github-actions.md#token-setup)). +Requires `GH_TOKEN`. Optionally uses `BUMPY_GH_TOKEN` to push the version branch so PR workflows trigger (see [GitHub Actions setup](github-actions.md#token-setup)). The `--pat-pr` flag additionally uses `BUMPY_GH_TOKEN` to create/edit the PR itself — use it when the token belongs to a dedicated automation account (bot user). If you're using a developer's personal PAT, leave this off so the PR is authored by `github-actions[bot]` and the developer can still approve it. ## `bumpy ci setup` diff --git a/docs/github-actions.md b/docs/github-actions.md index 348c543..c980294 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -121,7 +121,20 @@ The default `${{ github.token }}` provides the basic permissions needed for both GitHub's anti-recursion guard prevents PRs created by the default `github.token` from triggering other workflows. This means your regular CI workflows (tests, linting, etc.) won't run automatically on the Version Packages PR — so you can't verify that the version bumps don't break anything before merging. -To fix this, provide a `BUMPY_GH_TOKEN` using either a **fine-grained PAT** or a **GitHub App token**. Bumpy uses this token to push the version branch and create the PR, which allows your CI workflows to trigger normally. +To fix this, provide a `BUMPY_GH_TOKEN` using either a **fine-grained PAT** or a **GitHub App token**. Bumpy uses this token to push the version branch, which allows your CI workflows to trigger normally. + +#### Controlling what the token is used for + +By default, `BUMPY_GH_TOKEN` is only used for git push operations. You can opt in to using it for other GitHub API calls: + +| Flag | Command | Effect | +| ---------------- | ------------ | --------------------------------------------- | +| `--pat-pr` | `ci release` | Create/edit the version PR as the token owner | +| `--pat-comments` | `ci check` | Post PR comments as the token owner | + +**When to use these flags:** Use them when `BUMPY_GH_TOKEN` belongs to a dedicated automation account (bot user or GitHub App). The PR and comments will appear from that account. + +**When NOT to use these flags:** If you're using a developer's personal PAT, leave these off. PRs and comments will appear from `github-actions[bot]`, which allows the developer to still review and approve the PR. Run `bumpy ci setup` for interactive guidance, or set it up manually: @@ -162,8 +175,8 @@ A classic npm access token. Create one at [npmjs.com → Access Tokens](https:// ## Environment variables summary -| Variable | Required | Used by | Description | -| ---------------- | ----------------- | ------------------------ | ------------------------------------------------- | -| `GH_TOKEN` | Yes | `ci check`, `ci release` | GitHub token for API access | -| `BUMPY_GH_TOKEN` | Recommended | `ci release` | PAT or App token so version PRs trigger workflows | -| `NPM_TOKEN` | If not using OIDC | `ci release` | npm access token for publishing | +| Variable | Required | Used by | Description | +| ---------------- | ----------------- | ------------------------ | ----------------------------------------------------------------- | +| `GH_TOKEN` | Yes | `ci check`, `ci release` | GitHub token for API access | +| `BUMPY_GH_TOKEN` | Recommended | `ci check`, `ci release` | PAT or App token — used for push, and optionally for PRs/comments | +| `NPM_TOKEN` | If not using OIDC | `ci release` | npm access token for publishing | diff --git a/llms.md b/llms.md index 020f868..7a2674c 100644 --- a/llms.md +++ b/llms.md @@ -349,10 +349,11 @@ Default flow: detects PM → packs tarball (resolves workspace:/catalog: protoco PR check — reports pending bump files and optionally comments on the PR with the release plan. -| Flag | Description | -| ------------------- | ------------------------------------------------------------- | -| `--comment` | Force PR commenting on/off (auto-detected in CI environments) | -| `--fail-on-missing` | Exit 1 if no bump files found | +| Flag | Description | +| ------------------- | --------------------------------------------------------------------- | +| `--comment` | Force PR commenting on/off (auto-detected in CI environments) | +| `--fail-on-missing` | Exit 1 if no bump files found | +| `--pat-comments` | Post PR comments using `BUMPY_GH_TOKEN` instead of default `GH_TOKEN` | Auto-detects PR number from `GITHUB_REF` in GitHub Actions. Also checks `BUMPY_PR_NUMBER` and `PR_NUMBER` env vars. @@ -365,6 +366,7 @@ Release automation — either creates a "Version Packages" PR or auto-publishes | `--auto-publish` | Version + publish directly instead of creating a PR | | `--tag ` | npm dist-tag for auto-publish mode | | `--branch ` | Branch name for version PR (default: `bumpy/version-packages`) | +| `--pat-pr` | Create/edit the version PR using `BUMPY_GH_TOKEN` | Default mode (`version-pr`): creates a branch, runs `bumpy version`, commits, and opens/updates a PR via `gh`. Merging that PR triggers publish. diff --git a/packages/bumpy/src/cli.ts b/packages/bumpy/src/cli.ts index d38e464..d7d06cc 100644 --- a/packages/bumpy/src/cli.ts +++ b/packages/bumpy/src/cli.ts @@ -105,6 +105,7 @@ async function main() { await ciCheckCommand(rootDir, { comment: ciFlags.comment !== undefined ? ciFlags.comment === true : undefined, failOnMissing: ciFlags['fail-on-missing'] === true, + patComments: ciFlags['pat-comments'] === true, }); } else if (subcommand === 'release') { const { ciReleaseCommand } = await import('./commands/ci.ts'); @@ -113,6 +114,7 @@ async function main() { mode, tag: ciFlags.tag as string | undefined, branch: ciFlags.branch as string | undefined, + patPr: ciFlags['pat-pr'] === true, }); } else if (subcommand === 'setup') { const { ciSetupCommand } = await import('./commands/ci-setup.ts'); diff --git a/packages/bumpy/src/commands/ci.ts b/packages/bumpy/src/commands/ci.ts index 599a73f..88dbf42 100644 --- a/packages/bumpy/src/commands/ci.ts +++ b/packages/bumpy/src/commands/ci.ts @@ -11,6 +11,41 @@ import { detectPackageManager } from '../utils/package-manager.ts'; import { createHash } from 'node:crypto'; import type { BumpyConfig, BumpFile, PackageManager, ReleasePlan, PlannedRelease } from '../types.ts'; +// ---- PAT-scoped gh helpers ---- + +/** + * Temporarily override GH_TOKEN with BUMPY_GH_TOKEN for a gh CLI call. + * + * Use `--pat-pr` / `--pat-comments` flags to opt in. This is useful when + * BUMPY_GH_TOKEN belongs to a dedicated automation/bot account. If you're + * using a developer's personal PAT, it's better to leave these flags off so + * that PRs and comments appear from github-actions[bot] — allowing the + * developer to still review and approve the PR. + */ +function requirePatToken(): string { + const token = process.env.BUMPY_GH_TOKEN; + if (!token) { + throw new Error('BUMPY_GH_TOKEN must be set when using --pat-pr or --pat-comments'); + } + return token; +} + +async function withPatToken(usePat: boolean, fn: () => Promise): Promise { + if (!usePat) return fn(); + const token = requirePatToken(); + const originalGhToken = process.env.GH_TOKEN; + process.env.GH_TOKEN = token; + try { + return await fn(); + } finally { + if (originalGhToken !== undefined) { + process.env.GH_TOKEN = originalGhToken; + } else { + delete process.env.GH_TOKEN; + } + } +} + // ---- Validation helpers ---- /** Validate a git branch name to prevent injection */ @@ -45,6 +80,7 @@ function ensureGitIdentity(rootDir: string, config: BumpyConfig): void { interface CheckOptions { comment?: boolean; // post a PR comment via gh (default: true in CI) failOnMissing?: boolean; // exit 1 if no bump files (default: false) + patComments?: boolean; // post PR comments using BUMPY_GH_TOKEN } /** @@ -84,7 +120,7 @@ export async function ciCheckCommand(rootDir: string, opts: CheckOptions): Promi if (shouldComment && prNumber) { const prBranch = detectPrBranch(rootDir); - await postOrUpdatePrComment(prNumber, formatNoBumpFilesComment(prBranch, pm), rootDir); + await postOrUpdatePrComment(prNumber, formatNoBumpFilesComment(prBranch, pm), rootDir, opts.patComments); } if (opts.failOnMissing) { @@ -111,7 +147,7 @@ export async function ciCheckCommand(rootDir: string, opts: CheckOptions): Promi if (shouldComment && prNumber) { const prBranch = detectPrBranch(rootDir); const comment = formatReleasePlanComment(plan, prBumpFiles, prNumber, prBranch, pm, plan.warnings); - await postOrUpdatePrComment(prNumber, comment, rootDir); + await postOrUpdatePrComment(prNumber, comment, rootDir, opts.patComments); } } @@ -121,6 +157,7 @@ interface ReleaseOptions { mode: 'auto-publish' | 'version-pr'; tag?: string; // npm dist-tag for auto-publish branch?: string; // branch name for version PR (default: "bumpy/version-packages") + patPr?: boolean; // create/edit PR using BUMPY_GH_TOKEN } /** @@ -153,7 +190,7 @@ export async function ciReleaseCommand(rootDir: string, opts: ReleaseOptions): P await autoPublish(rootDir, config, opts.tag); } else { const packageDirs = new Map([...packages.values()].map((p) => [p.name, p.relativeDir])); - await createVersionPr(rootDir, plan, config, packageDirs, opts.branch); + await createVersionPr(rootDir, plan, config, packageDirs, opts.branch, opts.patPr); } } @@ -263,6 +300,7 @@ async function createVersionPr( config: BumpyConfig, packageDirs: Map, branchName?: string, + patPr?: boolean, ): Promise { const branch = validateBranchName(branchName || config.versionPr.branch); const baseBranch = validateBranchName( @@ -310,19 +348,30 @@ async function createVersionPr( if (existingPr) { const validPr = validatePrNumber(existingPr); log.step(`Updating existing PR #${validPr}...`); - await runArgsAsync(['gh', 'pr', 'edit', validPr, '--title', config.versionPr.title, '--body-file', '-'], { - cwd: rootDir, - input: prBody, - }); + await withPatToken(!!patPr, () => + runArgsAsync(['gh', 'pr', 'edit', validPr, '--title', config.versionPr.title, '--body-file', '-'], { + cwd: rootDir, + input: prBody, + }), + ); log.success(`Updated PR #${validPr}`); } else { log.step('Creating version PR...'); const prTitle = config.versionPr.title; - const result = await runArgsAsync( - ['gh', 'pr', 'create', '--title', prTitle, '--body-file', '-', '--base', baseBranch, '--head', branch], - { cwd: rootDir, input: prBody }, + const result = await withPatToken(!!patPr, () => + runArgsAsync( + ['gh', 'pr', 'create', '--title', prTitle, '--body-file', '-', '--base', baseBranch, '--head', branch], + { cwd: rootDir, input: prBody }, + ), ); log.success(`Created PR: ${result}`); + if (!patPr) { + // Push again with the custom token now that the PR exists, so that a + // `pull_request: synchronize` event is generated and CI workflows trigger. + // (The initial push happened before the PR existed, and the PR creation + // event from GITHUB_TOKEN doesn't trigger workflows.) + pushWithToken(rootDir, branch); + } } // Switch back to the base branch @@ -525,7 +574,7 @@ function formatVersionPrBody(plan: ReleasePlan, preamble: string, packageDirs: M const COMMENT_MARKER = ''; -async function postOrUpdatePrComment(prNumber: string, body: string, rootDir: string): Promise { +async function postOrUpdatePrComment(prNumber: string, body: string, rootDir: string, usePat = false): Promise { const validPr = validatePrNumber(prNumber); const markedBody = `${COMMENT_MARKER}\n${body}`; @@ -540,13 +589,17 @@ async function postOrUpdatePrComment(prNumber: string, body: string, rootDir: st const commentId = existingComment?.split('\n')[0]?.trim(); if (commentId) { - await runArgsAsync( - ['gh', 'api', `repos/{owner}/{repo}/issues/comments/${commentId}`, '-X', 'PATCH', '-F', 'body=@-'], - { cwd: rootDir, input: markedBody }, + await withPatToken(usePat, () => + runArgsAsync( + ['gh', 'api', `repos/{owner}/{repo}/issues/comments/${commentId}`, '-X', 'PATCH', '-F', 'body=@-'], + { cwd: rootDir, input: markedBody }, + ), ); log.dim(' Updated PR comment'); } else { - await runArgsAsync(['gh', 'pr', 'comment', validPr, '--body-file', '-'], { cwd: rootDir, input: markedBody }); + await withPatToken(usePat, () => + runArgsAsync(['gh', 'pr', 'comment', validPr, '--body-file', '-'], { cwd: rootDir, input: markedBody }), + ); log.dim(' Posted PR comment'); } } catch (err) { From 243a1c78963ae1f26fae86e3675dc02786e04e11 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 20 Apr 2026 16:44:02 -0700 Subject: [PATCH 2/2] Add bump file for PAT PR/comments feature Co-Authored-By: Claude Opus 4.6 (1M context) --- .bumpy/pat-pr-and-comments.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .bumpy/pat-pr-and-comments.md diff --git a/.bumpy/pat-pr-and-comments.md b/.bumpy/pat-pr-and-comments.md new file mode 100644 index 0000000..1666b99 --- /dev/null +++ b/.bumpy/pat-pr-and-comments.md @@ -0,0 +1,5 @@ +--- +'@varlock/bumpy': minor +--- + +Add `--pat-pr` and `--pat-comments` flags to CI commands, allowing users to opt in to using `BUMPY_GH_TOKEN` for PR creation and comment posting. Also fixes CI not triggering on newly created version PRs.