Skip to content

Commit 2277996

Browse files
vicperdanaCopilot
andcommitted
feat(release): channel-aware workflows + RELEASING runbook
Phase 1 of the release-strategy work. - Add scripts/extract-release-notes.ps1: shared CHANGELOG section extractor consumed by all three release workflows. - release-psdocs.yml / release-psdocs-azure.yml: - Parse tag into base version + optional preview suffix. - Validate base version against PSDocs.psd1 / PSDocs.Azure.psd1 ModuleVersion (fail fast on mismatch). - Extract per-version notes from CHANGELOG.md and pass via --notes-file to gh release create. - Mark GitHub Release as --prerelease when tag carries a SemVer prerelease suffix. - release-vscode.yml: - Two-tag-prefix design (vscode-v* stable + vscode-preview-v* preview) because VS Marketplace does not accept SemVer prerelease suffixes in package.json version. - Validate version against package.json. - Route channel through the build's existing -Channel param so preview publishes as vicperdana.psdocs-vscode-preview. - Add RELEASING.md: end-to-end runbook covering one-time secret + environment setup (PSGALLERY_API_KEY, VSCE_PAT, release env with required reviewers), per-package release steps, pre-release flow, troubleshooting, and rollback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9cbc765 commit 2277996

5 files changed

Lines changed: 503 additions & 26 deletions

File tree

.github/workflows/release-psdocs-azure.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,45 @@ jobs:
2929
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
3030
Install-Module -Name InvokeBuild -MinimumVersion 5.4.0 -Scope CurrentUser -Force
3131
32-
- name: Extract version from tag
32+
- name: Parse tag and detect channel
3333
id: version
34+
shell: pwsh
3435
run: |
35-
VERSION=${GITHUB_REF#refs/tags/psdocs-azure-v}
36-
echo "version=$VERSION" >> $GITHUB_OUTPUT
37-
36+
$full = '${{ github.ref_name }}' -replace '^psdocs-azure-v', ''
37+
$base = ($full -split '-', 2)[0]
38+
$isPrerelease = $full.Contains('-')
39+
$channel = if ($isPrerelease) { 'preview' } else { 'stable' }
40+
"full=$full" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
41+
"base=$base" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
42+
"channel=$channel" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
43+
"is_prerelease=$($isPrerelease.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
44+
Write-Host "Tag: ${{ github.ref_name }} -> full=$full base=$base channel=$channel"
45+
46+
- name: Validate version matches manifest
47+
shell: pwsh
48+
run: |
49+
$manifestPath = './packages/psdocs-azure/src/PSDocs.Azure/PSDocs.Azure.psd1'
50+
$manifest = Import-PowerShellDataFile -Path $manifestPath
51+
$expected = '${{ steps.version.outputs.base }}'
52+
if ($manifest.ModuleVersion -ne $expected) {
53+
Write-Error "Manifest ModuleVersion '$($manifest.ModuleVersion)' does not match tag base version '$expected'. Update $manifestPath before tagging."
54+
exit 1
55+
}
56+
Write-Host "Manifest ModuleVersion '$($manifest.ModuleVersion)' matches tag base '$expected'."
57+
58+
- name: Extract release notes
59+
shell: pwsh
60+
run: |
61+
./scripts/extract-release-notes.ps1 `
62+
-ChangelogPath ./CHANGELOG.md `
63+
-Version '${{ steps.version.outputs.full }}' `
64+
-OutputPath ./release-notes.md
65+
3866
- name: Build
3967
shell: pwsh
4068
working-directory: packages/psdocs-azure
4169
run: |
42-
Invoke-Build Build -File ./pipeline.build.ps1 -Build '${{ steps.version.outputs.version }}'
70+
Invoke-Build Build -File ./pipeline.build.ps1 -Build '${{ steps.version.outputs.full }}'
4371
4472
- name: Publish to PSGallery
4573
shell: pwsh
@@ -51,7 +79,9 @@ jobs:
5179
- name: Create GitHub Release
5280
env:
5381
GH_TOKEN: ${{ github.token }}
82+
PRERELEASE_FLAG: ${{ steps.version.outputs.is_prerelease == 'true' && '--prerelease' || '' }}
5483
run: |
5584
gh release create "${{ github.ref_name }}" \
56-
--title "PSDocs.Azure v${{ steps.version.outputs.version }}" \
57-
--notes-file CHANGELOG.md
85+
--title "PSDocs.Azure v${{ steps.version.outputs.full }}" \
86+
--notes-file ./release-notes.md \
87+
$PRERELEASE_FLAG

.github/workflows/release-psdocs.yml

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,46 @@ jobs:
2929
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
3030
Install-Module -Name InvokeBuild -MinimumVersion 5.4.0 -Scope CurrentUser -Force
3131
32-
- name: Extract version from tag
32+
- name: Parse tag and detect channel
3333
id: version
34+
shell: pwsh
3435
run: |
35-
VERSION=${GITHUB_REF#refs/tags/psdocs-v}
36-
echo "version=$VERSION" >> $GITHUB_OUTPUT
37-
36+
$full = '${{ github.ref_name }}' -replace '^psdocs-v', ''
37+
$base = ($full -split '-', 2)[0]
38+
$isPrerelease = $full.Contains('-')
39+
$channel = if ($isPrerelease) { 'preview' } else { 'stable' }
40+
"full=$full" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
41+
"base=$base" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
42+
"channel=$channel" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
43+
"is_prerelease=$($isPrerelease.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
44+
Write-Host "Tag: ${{ github.ref_name }} -> full=$full base=$base channel=$channel"
45+
46+
- name: Validate version matches manifest
47+
shell: pwsh
48+
run: |
49+
$manifestPath = './packages/psdocs/src/PSDocs/PSDocs.psd1'
50+
$manifest = Import-PowerShellDataFile -Path $manifestPath
51+
$expected = '${{ steps.version.outputs.base }}'
52+
if ($manifest.ModuleVersion -ne $expected) {
53+
Write-Error "Manifest ModuleVersion '$($manifest.ModuleVersion)' does not match tag base version '$expected'. Update $manifestPath before tagging."
54+
exit 1
55+
}
56+
Write-Host "Manifest ModuleVersion '$($manifest.ModuleVersion)' matches tag base '$expected'."
57+
58+
- name: Extract release notes
59+
id: notes
60+
shell: pwsh
61+
run: |
62+
./scripts/extract-release-notes.ps1 `
63+
-ChangelogPath ./packages/psdocs/CHANGELOG.md `
64+
-Version '${{ steps.version.outputs.full }}' `
65+
-OutputPath ./release-notes.md
66+
3867
- name: Build
3968
shell: pwsh
4069
working-directory: packages/psdocs
4170
run: |
42-
Invoke-Build Build -File ./pipeline.build.ps1 -Build '${{ steps.version.outputs.version }}'
71+
Invoke-Build Build -File ./pipeline.build.ps1 -Build '${{ steps.version.outputs.full }}'
4372
4473
- name: Publish to PSGallery
4574
shell: pwsh
@@ -51,7 +80,9 @@ jobs:
5180
- name: Create GitHub Release
5281
env:
5382
GH_TOKEN: ${{ github.token }}
83+
PRERELEASE_FLAG: ${{ steps.version.outputs.is_prerelease == 'true' && '--prerelease' || '' }}
5484
run: |
5585
gh release create "${{ github.ref_name }}" \
56-
--title "PSDocs v${{ steps.version.outputs.version }}" \
57-
--notes-file packages/psdocs/CHANGELOG.md
86+
--title "PSDocs v${{ steps.version.outputs.full }}" \
87+
--notes-file ./release-notes.md \
88+
$PRERELEASE_FLAG

.github/workflows/release-vscode.yml

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
# Release VS Code Extension (Stable)
2-
# Triggered by vscode-v* tags for stable releases
1+
# Release VS Code Extension
2+
# Stable: tag `vscode-vX.Y.Z` -> extension `vicperdana.psdocs-vscode`
3+
# Preview: tag `vscode-preview-vX.Y.Z` -> extension `vicperdana.psdocs-vscode-preview` (separate Marketplace listing)
4+
#
5+
# VS Code Marketplace does not accept SemVer prerelease suffixes (e.g. -preview.1) in
6+
# the package version, so the channel is encoded in the tag prefix and routed to the
7+
# build pipeline via -Channel. Both channels use plain X.Y.Z versions in package.json.
38

49
name: Release VS Code Extension
510

611
on:
712
push:
813
tags:
914
- 'vscode-v*'
15+
- 'vscode-preview-v*'
1016

1117
permissions:
1218
contents: write
1319

1420
jobs:
1521
release:
16-
name: Release Stable
22+
name: Release
1723
runs-on: ubuntu-latest
1824
environment: release
1925

@@ -34,22 +40,60 @@ jobs:
3440
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
3541
Install-Module -Name InvokeBuild -MinimumVersion 5.4.0 -Scope CurrentUser -Force
3642
37-
- name: Extract version from tag
43+
- name: Parse tag and detect channel
3844
id: version
45+
shell: pwsh
46+
run: |
47+
$tag = '${{ github.ref_name }}'
48+
if ($tag -match '^vscode-preview-v(.+)$') {
49+
$version = $Matches[1]
50+
$channel = 'preview'
51+
$isPrerelease = $true
52+
} elseif ($tag -match '^vscode-v(.+)$') {
53+
$version = $Matches[1]
54+
$channel = 'stable'
55+
$isPrerelease = $false
56+
} else {
57+
Write-Error "Tag '$tag' does not match expected pattern vscode-v* or vscode-preview-v*"
58+
exit 1
59+
}
60+
if ($version -notmatch '^\d+\.\d+\.\d+$') {
61+
Write-Error "Version '$version' must be plain SemVer (X.Y.Z) — VS Code Marketplace does not accept prerelease suffixes."
62+
exit 1
63+
}
64+
"version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
65+
"channel=$channel" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
66+
"is_prerelease=$($isPrerelease.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
67+
Write-Host "Tag: $tag -> version=$version channel=$channel"
68+
69+
- name: Validate version matches package.json
70+
shell: pwsh
71+
run: |
72+
$pkg = Get-Content ./packages/vscode-extension/package.json -Raw | ConvertFrom-Json
73+
$expected = '${{ steps.version.outputs.version }}'
74+
if ($pkg.version -ne $expected) {
75+
Write-Error "package.json version '$($pkg.version)' does not match tag version '$expected'. Update packages/vscode-extension/package.json before tagging."
76+
exit 1
77+
}
78+
Write-Host "package.json version '$($pkg.version)' matches tag '$expected'."
79+
80+
- name: Extract release notes
81+
shell: pwsh
3982
run: |
40-
VERSION=${GITHUB_REF#refs/tags/vscode-v}
41-
echo "version=$VERSION" >> $GITHUB_OUTPUT
42-
echo "Releasing version: $VERSION"
83+
./scripts/extract-release-notes.ps1 `
84+
-ChangelogPath ./packages/vscode-extension/CHANGELOG.md `
85+
-Version '${{ steps.version.outputs.version }}' `
86+
-OutputPath ./release-notes.md
4387
4488
- name: Install dependencies
4589
working-directory: packages/vscode-extension
4690
run: npm ci
4791

48-
- name: Build stable channel
92+
- name: Build (${{ steps.version.outputs.channel }})
4993
shell: pwsh
5094
working-directory: packages/vscode-extension
5195
run: |
52-
Invoke-Build Build -Channel 'stable' -Build '${{ steps.version.outputs.version }}'
96+
Invoke-Build Build -Channel '${{ steps.version.outputs.channel }}' -Build '${{ steps.version.outputs.version }}'
5397
5498
- name: Find VSIX file
5599
id: vsix
@@ -68,14 +112,18 @@ jobs:
68112
working-directory: packages/vscode-extension/out/package
69113
env:
70114
VSCE_PAT: ${{ secrets.VSCE_PAT }}
115+
PRE_RELEASE_FLAG: ${{ steps.version.outputs.is_prerelease == 'true' && '--pre-release' || '' }}
71116
run: |
72-
npx @vscode/vsce publish --packagePath "${{ steps.vsix.outputs.file }}" --pat "$VSCE_PAT"
117+
npx @vscode/vsce publish --packagePath "${{ steps.vsix.outputs.file }}" --pat "$VSCE_PAT" $PRE_RELEASE_FLAG
73118
74119
- name: Create GitHub Release
75120
env:
76121
GH_TOKEN: ${{ github.token }}
122+
PRERELEASE_FLAG: ${{ steps.version.outputs.is_prerelease == 'true' && '--prerelease' || '' }}
123+
TITLE_SUFFIX: ${{ steps.version.outputs.is_prerelease == 'true' && ' (Preview)' || '' }}
77124
run: |
78125
gh release create "${{ github.ref_name }}" \
79-
--title "VS Code Extension v${{ steps.version.outputs.version }}" \
80-
--notes-file packages/vscode-extension/CHANGELOG.md \
126+
--title "VS Code Extension v${{ steps.version.outputs.version }}$TITLE_SUFFIX" \
127+
--notes-file ./release-notes.md \
128+
$PRERELEASE_FLAG \
81129
"${{ steps.vsix.outputs.path }}"

0 commit comments

Comments
 (0)