From 0cacabb0391b24c392d44e1198288c663fa849f9 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Mon, 18 May 2026 17:06:27 +0800 Subject: [PATCH 1/2] ci: harden OfficeCLI bump PR creation --- .github/workflows/officecli-bump.yml | 62 ++++++++++++++----- .../github/officecli-bump-workflow.test.ts | 20 ++++-- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/.github/workflows/officecli-bump.yml b/.github/workflows/officecli-bump.yml index 912be7ff9..41a9fce2c 100644 --- a/.github/workflows/officecli-bump.yml +++ b/.github/workflows/officecli-bump.yml @@ -12,9 +12,7 @@ on: - cron: "17 3 * * 1" permissions: - contents: write - pull-requests: write - issues: write + contents: read jobs: officecli-bump: @@ -69,6 +67,19 @@ jobs: if: ${{ steps.versions.outputs.current_version == steps.versions.outputs.latest_version }} run: echo "Bundled OfficeCLI is already at ${{ steps.versions.outputs.current_version }}." + - name: Validate bump pull request token + if: ${{ steps.versions.outputs.current_version != steps.versions.outputs.latest_version && inputs.dry_run != true }} + shell: bash + env: + OFFICECLI_BUMP_TOKEN: ${{ secrets.OFFICECLI_BUMP_TOKEN }} + run: | + set -euo pipefail + + if [ -z "${OFFICECLI_BUMP_TOKEN:-}" ]; then + echo "::error title=Missing OFFICECLI_BUMP_TOKEN::Set repository secret OFFICECLI_BUMP_TOKEN to a bot token allowed to push ci/officecli-bump-* branches and create pull requests." + exit 1 + fi + - name: Bump manifest and verify all bundled assets if: ${{ steps.versions.outputs.current_version != steps.versions.outputs.latest_version }} id: verify @@ -119,7 +130,7 @@ jobs: if: ${{ steps.versions.outputs.current_version != steps.versions.outputs.latest_version }} shell: bash env: - GH_TOKEN: ${{ github.token }} + OFFICECLI_BUMP_TOKEN: ${{ secrets.OFFICECLI_BUMP_TOKEN }} INPUT_DRY_RUN: ${{ inputs.dry_run }} LATEST_VERSION: ${{ steps.versions.outputs.latest_version }} RELEASE_URL: ${{ steps.versions.outputs.release_url }} @@ -140,14 +151,6 @@ jobs: exit 1 fi - if existing_pr="$(gh pr list --state open --base dev --head "$branch" --json url --jq '.[0].url // empty')"; then - if [ -n "$existing_pr" ]; then - echo "Open bump PR already exists: $existing_pr" - echo "Skipping duplicate OfficeCLI bump." - exit 0 - fi - fi - echo "Verified targets:" while IFS= read -r target; do [ -n "$target" ] && printf ' - %s\n' "$target" @@ -170,13 +173,30 @@ jobs: exit 0 fi + if [ -z "${OFFICECLI_BUMP_TOKEN:-}" ]; then + echo "::error title=Missing OFFICECLI_BUMP_TOKEN::Set repository secret OFFICECLI_BUMP_TOKEN to a bot token allowed to push ci/officecli-bump-* branches and create pull requests." + exit 1 + fi + + export GH_TOKEN="$OFFICECLI_BUMP_TOKEN" + git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" gh auth setup-git git switch -c "$branch" git add packages/desktop-electron/bundled-tools.json skills/ + if git diff --cached --quiet; then + echo "No OfficeCLI bump changes were produced after verification; skipping branch and PR update." + exit 0 + fi git commit -m "$title and sync skills" - git push --set-upstream origin "$branch" + if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + echo "Updating existing bump branch: $branch" + git fetch origin "$branch" + git push --force-with-lease=refs/heads/"$branch" --set-upstream origin "$branch" + else + git push --set-upstream origin "$branch" + fi body_file="$(mktemp)" cat > "$body_file" < { const checkout = steps.find((step) => step.uses?.startsWith("actions/checkout@")) const setupNode = steps.find((step) => step.uses?.startsWith("actions/setup-node@")) const setupBun = steps.find((step) => step.uses?.startsWith("oven-sh/setup-bun@")) + const validateToken = steps.find((step) => step.name === "Validate bump pull request token") + const createPr = steps.find((step) => step.name === "Create bump pull request") expect(parsed.name).toBe("officecli-bump") expect(parsed.on?.workflow_dispatch).toEqual({ @@ -27,9 +29,7 @@ describe("officecli bump workflow", () => { }) expect(parsed.on?.schedule).toEqual([{ cron: "17 3 * * 1" }]) expect(parsed.permissions).toEqual({ - contents: "write", - "pull-requests": "write", - issues: "write", + contents: "read", }) expect(checkout?.uses).toBe("actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd") @@ -45,11 +45,23 @@ describe("officecli bump workflow", () => { expect(workflow).toContain( "bun packages/desktop-electron/scripts/prepare-officecli.ts --platform \"$platform\" --arch \"$arch\"", ) + expect(validateToken?.if).toBe( + "${{ steps.versions.outputs.current_version != steps.versions.outputs.latest_version && inputs.dry_run != true }}", + ) + expect(validateToken?.env?.OFFICECLI_BUMP_TOKEN).toBe("${{ secrets.OFFICECLI_BUMP_TOKEN }}") + expect(validateToken?.run).toContain("Missing OFFICECLI_BUMP_TOKEN") + expect(createPr?.env?.OFFICECLI_BUMP_TOKEN).toBe("${{ secrets.OFFICECLI_BUMP_TOKEN }}") + expect(createPr?.env).not.toHaveProperty("GH_TOKEN") expect(workflow).toContain("Dry run requested; skipping branch push and PR creation.") expect(workflow).toContain("gh auth setup-git") + expect(workflow).toContain('git ls-remote --exit-code --heads origin "$branch"') + expect(workflow).toContain('git fetch origin "$branch"') + expect(workflow).toContain('git push --force-with-lease=refs/heads/"$branch" --set-upstream origin "$branch"') + expect(workflow).toContain("gh pr edit") expect(workflow).toContain("--label enhancement") expect(workflow).toContain("--label ci") expect(workflow).toContain("--label upstream") - expect(workflow).toContain("Closes #330.") + expect(workflow).toContain("Follow-up to #330.") + expect(workflow).not.toContain("Closes #330.") }) }) From 18fb2f5f24ada9e63ff96ec8944968ad84622ddd Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Mon, 18 May 2026 17:18:11 +0800 Subject: [PATCH 2/2] ci: fix OfficeCLI bump update path --- .github/workflows/officecli-bump.yml | 25 +++++++++---------- .../github/officecli-bump-workflow.test.ts | 9 ++++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/officecli-bump.yml b/.github/workflows/officecli-bump.yml index 41a9fce2c..c0e9ec54e 100644 --- a/.github/workflows/officecli-bump.yml +++ b/.github/workflows/officecli-bump.yml @@ -192,7 +192,7 @@ jobs: git commit -m "$title and sync skills" if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then echo "Updating existing bump branch: $branch" - git fetch origin "$branch" + git fetch origin "refs/heads/$branch:refs/remotes/origin/$branch" git push --force-with-lease=refs/heads/"$branch" --set-upstream origin "$branch" else git push --set-upstream origin "$branch" @@ -257,18 +257,17 @@ jobs: - [x] I am targeting \`dev\`, and my PR title and commit messages use Conventional Commits in English EOF - if existing_pr="$(gh pr list --state open --base dev --head "$branch" --json url --jq '.[0].url // empty')"; then - if [ -n "$existing_pr" ]; then - echo "Open bump PR already exists: $existing_pr" - echo "Updating PR title and body instead of creating a duplicate." - gh pr edit "$existing_pr" \ - --title "$title" \ - --body-file "$body_file" \ - --add-label enhancement \ - --add-label ci \ - --add-label upstream - exit 0 - fi + existing_pr="$(gh pr list --state open --base dev --head "$branch" --json url --jq '.[0].url // empty')" + if [ -n "$existing_pr" ]; then + echo "Open bump PR already exists: $existing_pr" + echo "Updating PR title and body instead of creating a duplicate." + gh pr edit "$existing_pr" \ + --title "$title" \ + --body-file "$body_file" \ + --add-label enhancement \ + --add-label ci \ + --add-label upstream + exit 0 fi gh pr create \ diff --git a/packages/opencode/test/github/officecli-bump-workflow.test.ts b/packages/opencode/test/github/officecli-bump-workflow.test.ts index 5f0784ec2..f0d34839f 100644 --- a/packages/opencode/test/github/officecli-bump-workflow.test.ts +++ b/packages/opencode/test/github/officecli-bump-workflow.test.ts @@ -55,9 +55,16 @@ describe("officecli bump workflow", () => { expect(workflow).toContain("Dry run requested; skipping branch push and PR creation.") expect(workflow).toContain("gh auth setup-git") expect(workflow).toContain('git ls-remote --exit-code --heads origin "$branch"') - expect(workflow).toContain('git fetch origin "$branch"') + expect(workflow).toContain('git fetch origin "refs/heads/$branch:refs/remotes/origin/$branch"') expect(workflow).toContain('git push --force-with-lease=refs/heads/"$branch" --set-upstream origin "$branch"') + expect(workflow).toContain( + 'existing_pr="$(gh pr list --state open --base dev --head "$branch" --json url --jq \'.[0].url // empty\')"', + ) + expect(workflow).not.toContain('if existing_pr="$(gh pr list') expect(workflow).toContain("gh pr edit") + expect(workflow).toContain("--add-label enhancement") + expect(workflow).toContain("--add-label ci") + expect(workflow).toContain("--add-label upstream") expect(workflow).toContain("--label enhancement") expect(workflow).toContain("--label ci") expect(workflow).toContain("--label upstream")