Versioning, channels, and the runbook for every kind of release. Branch model in branching-and-release.md; what CI does at each step in ci.md.
Nerdbank.GitVersioning computes the version from version.json, the same as the framework repo.
version.json'sversionpins the Fallout release line this extension targets (10.4). The third component is the git height and moves on its own.- The build asserts the declared line still matches the referenced
Fallout.Common. Bumping one without the other fails the build rather than shipping a version that misstates what it targets. versionHeightOffsetexists because height restarts whenversion.json'sversionchanges. Marketplace versions must increase monotonically, so the offset keeps the sequence moving forward.
dotnet nbgv get-version # the number this commit would ship asBoth registries take exactly three integers. vsce refuses a semver prerelease outright:
The VS Marketplace doesn't support prerelease versions
So an RC is an ordinary triple carrying a pre-release bit in the VSIX manifest (Microsoft.VisualStudio.Code.PreRelease), and -rc.N lives only on the git tag and the GitHub release.
A version is pre-release or stable, never both. Publishing 10.4.30 as a marketplace pre-release burns that number, forcing GA to 10.4.31. Release candidates therefore stop at GitHub.
That argument does not apply to the preview channel: the patch is a git height, so every build already has a unique number and stable is always a later height. Nothing gets consumed that a stable release wants.
flowchart LR
DEV["develop"] -->|every push| PREV["preview<br/><i>rolling pre-release</i>"]
MAIN["main / support/*"] -->|"v* tag"| GH["github-releases"]
GH -.->|"opt-in flag<br/>+ approval"| VSM["vs-marketplace"]
GH -.->|"opt-in flag<br/>+ approval"| OVSX["open-vsx"]
style PREV fill:#1d4e6f,color:#fff
style GH fill:#2d6a4f,color:#fff
style VSM fill:#7f4f24,color:#fff
style OVSX fill:#7f4f24,color:#fff
| Channel | Trigger | Gating |
|---|---|---|
preview (rolling) |
every push to develop |
none |
github-releases |
any v* tag |
none |
vs-marketplace |
dispatch opt-in flag | flag + approval |
open-vsx |
dispatch opt-in flag | flag + approval |
A tag push never reaches a marketplace. Promotion is deliberate: set the flag, then approve the environment — two independent layers, matching how Fallout gates nuget.org.
Every push to develop builds a .vsix, uploads it as a per-run workflow artifact, and replaces the asset on a rolling preview GitHub pre-release.
Both, deliberately: the release asset has a stable URL and is what you install from, but the next push replaces it — so the per-run artifact is the fixed record of what a given commit produced.
gh release download preview -R Fallout-build/Fallout.Extensions.VSCode -p '*.vsix' --clobber
code --install-extension fallout.vsix --forceInstalling is a manual step by design; the build never touches your editor. VS Code only auto-updates extensions it obtained from a gallery, so this is a one-shot install — see #6 for the self-hosted-gallery idea that would change that.
flowchart TD
A["develop is where you want it"] --> B{"Needs a<br/>stabilisation window?"}
B -->|"No — ship develop as-is"| C["PR develop → main"]
B -->|"Yes"| D["Cut release/X.Y.Z from develop"]
D --> E["Fix only on the release branch"]
E --> F["PR release/X.Y.Z → main"]
C --> G["Rebase-merge into main"]
F --> G
G --> H["Tag main → publish.yml fires"]
H --> I["GitHub release created"]
F -.->|"then"| J["Port stabilisation commits<br/>back to develop"]
I -.->|"optional, later"| K["Promote to marketplaces"]
git switch develop && git pull --ff-only
gh pr create --base main --title "Release" --label skip-changelog
# merge with REBASE (see below), then:
git switch main && git pull --ff-only
dotnet nbgv get-version # confirm the number
gh release create v10.4.30 --target main --generate-notesgit switch -c release/10.4.30 develop
git push -u origin release/10.4.30
# … fixes land here by PR, feature work continues on develop …
gh pr create --base main
# merge, tag as above, then port the stabilisation commits back:
git switch -c chore/port-10.4.30 develop
git cherry-pick <fix-sha>…
gh pr create --base developgh release create v10.4.30-rc.1 --target main --prerelease --generate-notesThe tag must match the version nbgv computes for that commit — the workflow packages from the checked-out tag, not from the tag name, so a mismatch ships a .vsix whose version disagrees with its release.
Merge with rebase, not squash, into
main. Squashing collapses a whole release into one commit, losing the per-change history on the production branch. Merge commits are disabled and linear history is enforced, so rebase is the option that keeps commits intact. Repeated rebase-merges stay clean across releases —git rebasedrops already-applied commits by patch-id. Full reasoning and the one edge case in branching-and-release.md.
"Merge back to develop" is a cherry-pick or a second PR here, not a literal merge. GitFlow assumes merge commits; this repo enforces linear history. The effect is the same — the fix must reach
develop, or it ships to users and then disappears on the next release — but the mechanism is a PR carrying the same changes.
Production is broken and it can't wait for the next release.
git switch main && git pull --ff-only
git switch -c hotfix/10.4.31
# … fix …
gh pr create --base main --label bug
# merge, then:
gh release create v10.4.31 --target main --generate-notesThen get it onto the trunk — this step is not optional:
git switch -c bugfix/port-10.4.31 develop
git cherry-pick <fix-sha>
gh pr create --base develop --label skip-changelogsupport/vX.Y serves an older Fallout line. Tags on it fire the same pipeline — validate-ref accepts main and support/vX.Y.
git switch support/v10.4 && git pull --ff-only
# … fix lands by PR …
gh release create v10.4.99 --target support/v10.4 --generate-notesWhether to forward-port is a judgement call: those lines exist because the trunk moved on, so the same bug often doesn't exist there. Check, don't assume.
Nothing reaches a marketplace from a tag. Promotion is an explicit act:
gh workflow run publish.yml -f tag=v10.4.30 -f publish-to-marketplaces=trueBoth vs-marketplace and open-vsx then pause for approval on the run page (Review deployments). Approve each. The job publishes the exact artifact the pack job produced (--skip PackVsix), not a rebuild.
You can rehearse the wiring without burning a release: set the flag, wait for the approval prompt, then cancel without approving.
Both CLIs are invoked with --skip-duplicate, so re-running is idempotent on whatever already landed:
gh workflow run publish.yml -f tag=v10.4.30 -f publish-to-marketplaces=trueGenerated by GitHub from merged PR labels — the taxonomy lives in .github/release.yml and matches the framework repo's. Apply one category label per PR (enhancement, bug, breaking-change, security, documentation, dependencies), or skip-changelog for housekeeping.
There is no CHANGELOG.md. The labels are the changelog: a hand-maintained file would duplicate them, and its version heading can't be written correctly in advance since the patch is a git height not settled until the release is cut. If the marketplace page ever needs a rendered changelog, generate it into the .vsix at pack time rather than reinstating a tracked file.
| Secret | Used by | Scope |
|---|---|---|
VSCE_PAT |
vs-marketplace |
Azure DevOps PAT, Marketplace > Manage |
OVSX_TOKEN |
open-vsx |
Open VSX access token (mapped to OVSX_PAT) |
Both are read from the environment by the CLIs, never passed as process arguments. Verify without publishing:
dotnet fallout VerifyVsixCredentials