-
Notifications
You must be signed in to change notification settings - Fork 316
Add automated per-plugin versioning (NBGV) with /version-bump + weekly backstop #813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AbhitejJohn
wants to merge
6
commits into
main
Choose a base branch
from
abhitejjohn/per-plugin-version-strategy
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
462cf7d
Add automated per-plugin versioning (NBGV) with /version-bump + weekl…
AbhitejJohn 8c2b8c9
Merge branch 'main' into abhitejjohn/per-plugin-version-strategy
AbhitejJohn 766115f
Address Copilot review feedback
AbhitejJohn 22133dc
Harden version automation per maintainer review
AbhitejJohn 259c663
Document that manifest-only edits don't bump the version
AbhitejJohn 284695d
Merge branch 'main' into abhitejjohn/per-plugin-version-strategy
AbhitejJohn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "version": 1, | ||
| "isRoot": true, | ||
| "tools": { | ||
| "nbgv": { | ||
| "version": "3.10.85", | ||
| "commands": [ | ||
| "nbgv" | ||
| ], | ||
| "rollForward": false | ||
| } | ||
|
AbhitejJohn marked this conversation as resolved.
|
||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| # Admin-triggered version stamping. A maintainer comments `/version-bump` on a | ||
| # PR; this computes each changed plugin's post-merge version with NBGV and writes | ||
| # it into both manifests on the PR branch, then pushes the commit. | ||
| # | ||
| # Scope: same-repo (in-repo) branches only — the workflow cannot push to a fork's | ||
| # branch. Fork PRs are deferred to the weekly backstop (weekly-version-sync.yml). | ||
| # | ||
| # NOTE: issue_comment always runs the workflow YAML from the default branch, so | ||
| # edits here only take effect once merged to main. | ||
| name: version-bump-command | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
|
|
||
| concurrency: | ||
| group: version-bump-${{ github.event.issue.number }} | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| issues: write | ||
|
|
||
| jobs: | ||
| bump: | ||
| if: >- | ||
| github.event.issue.pull_request && | ||
| startsWith(github.event.comment.body, '/version-bump') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Check actor permissions | ||
| id: perms | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| ACTOR: ${{ github.event.comment.user.login }} | ||
| run: | | ||
| PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${ACTOR}/permission" --jq '.permission') | ||
| echo "Actor ${ACTOR} has permission: $PERMISSION" | ||
| if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" && "$PERMISSION" != "maintain" ]]; then | ||
| echo "::error::/version-bump requires write access" | ||
| gh api --method POST "repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments" \ | ||
| -f body="@${ACTOR} \`/version-bump\` requires write access to this repository." >/dev/null | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Resolve PR | ||
| id: pr | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| PR=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}") | ||
| HEAD_REPO=$(echo "$PR" | jq -r '.head.repo.full_name') | ||
| BASE_REPO=$(echo "$PR" | jq -r '.base.repo.full_name') | ||
| HEAD_REF=$(echo "$PR" | jq -r '.head.ref') | ||
| HEAD_SHA=$(echo "$PR" | jq -r '.head.sha') | ||
| BASE_SHA=$(echo "$PR" | jq -r '.base.sha') | ||
| { | ||
| echo "head_ref=$HEAD_REF" | ||
| echo "head_sha=$HEAD_SHA" | ||
| echo "base_sha=$BASE_SHA" | ||
| } >> "$GITHUB_OUTPUT" | ||
| if [[ "$HEAD_REPO" != "$BASE_REPO" ]]; then | ||
| echo "is_fork=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "is_fork=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Reject fork PRs | ||
| if: steps.pr.outputs.is_fork == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| gh api --method POST "repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments" \ | ||
| -f body="\`/version-bump\` only works on branches in this repository. This is a fork PR — the weekly version sync will stamp it after merge." >/dev/null | ||
| echo "Fork PR — skipping." | ||
|
|
||
| - name: Checkout PR branch | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| # Pin to the exact head SHA that was authorized, not the mutable branch | ||
| # name — a race-push after the maintainer comments cannot change what runs. | ||
| ref: ${{ steps.pr.outputs.head_sha }} | ||
| fetch-depth: 0 | ||
| persist-credentials: true | ||
|
|
||
| # SECURITY: this job runs in the privileged default-branch context with a | ||
| # contents:write token. Never execute tooling that the PR author controls. | ||
| # Overlay the version tooling, the tool manifest, the SDK pin, and the locked | ||
| # NuGet config from main so only trusted code runs and tools can only be | ||
| # restored from nuget.org; the PR's plugin *content* is consumed as data. | ||
| # These overlays are reverted before the commit step so the PR's own copies | ||
| # of these files are never modified. | ||
| - name: Pin trusted tooling from main | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| run: | | ||
| # NOTE: do NOT pass --depth here. A shallow fetch writes .git/shallow and flips the | ||
| # WHOLE repo to shallow (even though checkout used fetch-depth: 0), and NBGV refuses to | ||
| # compute version height on a shallow repo. fetch-depth: 0 already fetched main, so this | ||
| # full-depth fetch is cheap and keeps the repo non-shallow. | ||
| git fetch --no-tags origin main | ||
| git checkout FETCH_HEAD -- \ | ||
| eng/version/Sync-PluginVersions.ps1 \ | ||
| eng/version/nuget.config \ | ||
| .config/dotnet-tools.json \ | ||
| global.json | ||
|
|
||
| - name: Setup .NET SDK | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 | ||
| with: | ||
| # nbgv targets net8.0; global.json pins an SDK that ships only a preview runtime, onto | ||
| # which a release-targeted tool will not roll forward. Install an 8.0.x runtime so nbgv | ||
| # has a runtime to execute on, alongside the global.json SDK. | ||
| dotnet-version: '8.0.x' | ||
| global-json-file: global.json | ||
|
|
||
| - name: Restore tools | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| run: dotnet tool restore --configfile eng/version/nuget.config | ||
|
|
||
| - name: Stamp plugin versions | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| id: stamp | ||
| shell: pwsh | ||
| env: | ||
| BASE_SHA: ${{ steps.pr.outputs.base_sha }} | ||
| run: | | ||
| $head = git rev-parse HEAD | ||
| $mergeBase = git merge-base $env:BASE_SHA $head | ||
|
AbhitejJohn marked this conversation as resolved.
|
||
| if (-not $mergeBase) { | ||
| throw "Could not determine a merge base between base ($env:BASE_SHA) and head ($head). Ensure main is fully fetched (non-shallow)." | ||
| } | ||
|
|
||
| $report = & "$PWD/eng/version/Sync-PluginVersions.ps1" ` | ||
| -BaseCommit $mergeBase -HeadCommit $head -PredictSquashMerge -OnlyChanged -Write | | ||
| ConvertFrom-Json | ||
|
|
||
| if (-not $report -or $report.Count -eq 0) { | ||
| "stamped=false" >> $env:GITHUB_OUTPUT | ||
| "summary=No plugin content needs a version bump in this PR." >> $env:GITHUB_OUTPUT | ||
| exit 0 | ||
| } | ||
|
|
||
| $rows = ($report | ForEach-Object { "- ``$($_.plugin)``: $($_.current) → **$($_.computed)**" }) -join "`n" | ||
| "stamped=true" >> $env:GITHUB_OUTPUT | ||
| # Multi-line outputs need a heredoc-style delimiter. | ||
| $delim = "EOF_SUMMARY_$(Get-Random)" | ||
| "summary<<$delim" >> $env:GITHUB_OUTPUT | ||
| "Stamped plugin versions:`n$rows" >> $env:GITHUB_OUTPUT | ||
| "$delim" >> $env:GITHUB_OUTPUT | ||
|
|
||
| - name: Commit and push | ||
| if: steps.pr.outputs.is_fork == 'false' && steps.stamp.outputs.stamped == 'true' | ||
| env: | ||
| HEAD_REF: ${{ steps.pr.outputs.head_ref }} | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| # Drop the main-overlaid tooling so only the regenerated manifests are committed; | ||
| # the PR's own versions of these files are restored to the working tree + index. | ||
| git checkout HEAD -- \ | ||
| eng/version/Sync-PluginVersions.ps1 \ | ||
| eng/version/nuget.config \ | ||
| .config/dotnet-tools.json \ | ||
| global.json | ||
| git add plugins/*/plugin.json plugins/*/.codex-plugin/plugin.json | ||
| git commit -m "Stamp plugin versions via /version-bump" | ||
| git push origin "HEAD:${HEAD_REF}" | ||
|
|
||
| - name: Comment result | ||
| if: steps.pr.outputs.is_fork == 'false' && always() | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| SUMMARY: ${{ steps.stamp.outputs.summary }} | ||
| run: | | ||
| if [[ -z "$SUMMARY" ]]; then SUMMARY="Could not compute plugin versions — see the workflow run for details."; fi | ||
| gh api --method POST "repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments" \ | ||
| -f body="$SUMMARY" >/dev/null | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| # Weekly backstop. Computes every plugin's authoritative version on `main` with | ||
| # NBGV and, for any plugin whose content changed without a matching version bump, | ||
| # opens ONE pull request that stamps the correct versions and explains why. | ||
| # | ||
| # This is the safety net that makes per-PR bumping optional: anything a | ||
| # contributor or the /version-bump command missed is reconciled here. Running on | ||
| # main (post-merge) is the single source of truth, so the computed heights can | ||
| # never collide across PRs. | ||
| name: weekly-version-sync | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: '0 9 * * 1' # Mondays 09:00 UTC | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: weekly-version-sync | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| sync: | ||
| # Never run on forks (avoids burning a fork owner's Actions minutes). | ||
| if: github.repository == 'dotnet/skills' | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| SYNC_BRANCH: bot/weekly-version-sync | ||
| steps: | ||
| - name: Checkout main | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: true | ||
|
|
||
| - name: Setup .NET SDK | ||
| uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 | ||
| with: | ||
| # nbgv targets net8.0; global.json pins an SDK that ships only a preview runtime, onto | ||
| # which a release-targeted tool will not roll forward. Install an 8.0.x runtime so nbgv | ||
| # has a runtime to execute on, alongside the global.json SDK. | ||
| dotnet-version: '8.0.x' | ||
| global-json-file: global.json | ||
|
|
||
| - name: Restore tools | ||
| run: dotnet tool restore --configfile eng/version/nuget.config | ||
|
|
||
| - name: Compute and stamp versions | ||
| id: stamp | ||
| shell: pwsh | ||
| run: | | ||
| # Authoritative compute on main HEAD; write drifted plugins in place. | ||
| $report = & "$PWD/eng/version/Sync-PluginVersions.ps1" -OnlyChanged -Write | ConvertFrom-Json | ||
|
|
||
| if (-not $report -or $report.Count -eq 0) { | ||
| "has_changes=false" >> $env:GITHUB_OUTPUT | ||
| "No plugin versions drifted — nothing to sync." >> $env:GITHUB_STEP_SUMMARY | ||
| exit 0 | ||
| } | ||
|
|
||
| # Build the PR body, explaining each bump with the commits that caused it. | ||
| $sections = foreach ($r in $report) { | ||
| $name = $r.plugin | ||
| $lastStamp = git log -1 --format='%H' -- "plugins/$name/plugin.json" | ||
| $range = if ($lastStamp) { "$lastStamp..HEAD" } else { 'HEAD' } | ||
| $commits = git log --format='- %h %s' $range -- "plugins/$name" ` | ||
| ":(exclude)plugins/$name/plugin.json" ` | ||
| ":(exclude)plugins/$name/.codex-plugin/plugin.json" | ||
| $why = if ($commits) { ($commits -join "`n") } else { '- (no attributable commits found)' } | ||
| @( | ||
| "### ``$name``: $($r.current) → $($r.computed)" | ||
| "" | ||
| "Changes since the last version bump:" | ||
| "" | ||
| $why | ||
| "" | ||
| ) -join "`n" | ||
| } | ||
|
|
||
| $body = @( | ||
| "Automated weekly version sync. The following plugins changed since their" | ||
| "last version bump; this PR stamps the NBGV-computed version into their" | ||
| "``plugin.json`` and ``.codex-plugin/plugin.json``." | ||
| "" | ||
| "Patch numbers are derived from git history — review the reasons below, then merge." | ||
| "" | ||
| ($sections -join "`n") | ||
| ) -join "`n" | ||
|
|
||
| Set-Content -Path pr-body.md -Value $body | ||
| "has_changes=true" >> $env:GITHUB_OUTPUT | ||
| $body >> $env:GITHUB_STEP_SUMMARY | ||
|
|
||
| - name: Open or update sync PR | ||
| if: steps.stamp.outputs.has_changes == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git switch -c "$SYNC_BRANCH" | ||
| git add plugins/*/plugin.json plugins/*/.codex-plugin/plugin.json | ||
| git commit -m "Weekly plugin version sync" | ||
| git push --force origin "$SYNC_BRANCH" | ||
|
|
||
| EXISTING=$(gh pr list --head "$SYNC_BRANCH" --state open --json number --jq '.[0].number // empty') | ||
| if [[ -n "$EXISTING" ]]; then | ||
| gh pr edit "$EXISTING" --body-file pr-body.md | ||
| echo "Updated existing PR #$EXISTING" | ||
| else | ||
| gh pr create \ | ||
| --base main \ | ||
| --head "$SYNC_BRANCH" \ | ||
| --title "Weekly plugin version sync" \ | ||
| --body-file pr-body.md | ||
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.