Skip to content

Commit c8e55dc

Browse files
committed
ci: add opt-in dual-marketplace extension release workflow
1 parent aba509f commit c8e55dc

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
name: Publish DuckyScript Cookbook
2+
3+
# Deliberately manual: no pushes, tags or releases trigger publication.
4+
# Launch via Actions > Publish DuckyScript Cookbook > Run workflow.
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: "New stable version (e.g. 1.2.0); use the same version to retry a partial publish"
10+
required: true
11+
type: string
12+
default: "1.2.0"
13+
skip_openvsx:
14+
description: "Retry only: skip Open VSX if THIS version was already published there"
15+
required: true
16+
type: boolean
17+
default: false
18+
skip_marketplace:
19+
description: "Retry only: skip VS Marketplace if THIS version was already published there"
20+
required: true
21+
type: boolean
22+
default: false
23+
24+
permissions:
25+
contents: write
26+
id-token: write
27+
28+
concurrency:
29+
group: duckyscript-extension-release
30+
cancel-in-progress: false
31+
32+
jobs:
33+
publish:
34+
name: Build and publish one VSIX to both registries
35+
runs-on: ubuntu-latest
36+
timeout-minutes: 30
37+
steps:
38+
- name: Check out main, including tags
39+
uses: actions/checkout@v4
40+
with:
41+
fetch-depth: 0
42+
43+
- name: Set up Node.js 22
44+
uses: actions/setup-node@v4
45+
with:
46+
node-version: '22'
47+
48+
- name: Validate release request and prepare version
49+
env:
50+
RELEASE_VERSION: ${{ inputs.version }}
51+
SKIP_OPENVSX: ${{ inputs.skip_openvsx }}
52+
SKIP_MARKETPLACE: ${{ inputs.skip_marketplace }}
53+
shell: bash
54+
run: |
55+
set -euo pipefail
56+
if [[ "$GITHUB_REF" != 'refs/heads/main' ]]; then
57+
echo 'Publishing is restricted to the main branch.' >&2
58+
exit 1
59+
fi
60+
if [[ ! "$RELEASE_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
61+
echo 'version must be stable semantic versioning, for example 1.2.0.' >&2
62+
exit 1
63+
fi
64+
if [[ "$SKIP_OPENVSX" == true && "$SKIP_MARKETPLACE" == true ]]; then
65+
echo 'Both registries cannot be skipped.' >&2
66+
exit 1
67+
fi
68+
if git rev-parse -q --verify "refs/tags/v${RELEASE_VERSION}" >/dev/null; then
69+
echo 'A GitHub release tag already exists for this version; refusing to republish.' >&2
70+
exit 1
71+
fi
72+
node <<'NODE'
73+
const fs = require('node:fs');
74+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
75+
const desired = process.env.RELEASE_VERSION;
76+
const [a, b, c] = pkg.version.split('.').map(Number);
77+
const [x, y, z] = desired.split('.').map(Number);
78+
if (![a, b, c].every(Number.isInteger)) throw Error('Current package version is not stable semver');
79+
const order = x - a || y - b || z - c;
80+
if (order < 0) throw Error('Requested version must not be lower than the current package version');
81+
if (order > 0 && (process.env.SKIP_OPENVSX === 'true' || process.env.SKIP_MARKETPLACE === 'true')) {
82+
throw Error('Skipping a registry is allowed only when retrying an already prepared version');
83+
}
84+
if (order > 0) {
85+
const changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
86+
const heading = '## [Unreleased]';
87+
if (!changelog.includes(heading)) throw Error('CHANGELOG.md is missing its Unreleased section');
88+
fs.writeFileSync('CHANGELOG.md', changelog.replace(
89+
heading,
90+
'## [Unreleased]\n\n## [' + desired + '] - ' + new Date().toISOString().slice(0, 10)
91+
));
92+
pkg.version = desired;
93+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
94+
}
95+
console.log('Preparing ' + pkg.publisher + '.' + pkg.name + ' v' + desired);
96+
NODE
97+
echo "VSIX=duckyscriptcookbook-${RELEASE_VERSION}.vsix" >> "$GITHUB_ENV"
98+
99+
- name: Install and compile the extension
100+
run: |
101+
set -euo pipefail
102+
# No package-lock.json currently exists in this repository.
103+
npm install --no-audit --no-fund --package-lock=false
104+
npm run compile
105+
106+
- name: Install pinned publisher tools
107+
run: npm install --global --no-audit --no-fund @vscode/vsce@4.0.0 ovsx@1.2.0
108+
109+
- name: Package and inspect VSIX
110+
env:
111+
INPUT_VERSION: ${{ inputs.version }}
112+
run: |
113+
set -euo pipefail
114+
vsce package --no-dependencies --out "$VSIX"
115+
unzip -Z1 "$VSIX" > /tmp/ducky-vsix-files.txt
116+
grep -Fxq 'extension/out/extension.js' /tmp/ducky-vsix-files.txt
117+
grep -Fxq 'extension/package.json' /tmp/ducky-vsix-files.txt
118+
grep -Fxq 'extension/README.md' /tmp/ducky-vsix-files.txt
119+
unzip -p "$VSIX" extension/package.json | node -e '
120+
let s="";process.stdin.on("data",x=>s+=x).on("end",()=>{
121+
const pkg=JSON.parse(s);if(pkg.version!==process.env.INPUT_VERSION)process.exit(1);
122+
});'
123+
124+
- name: Save package even if a registry later fails
125+
uses: actions/upload-artifact@v4
126+
with:
127+
name: DuckyScriptCookbook-${{ inputs.version }}
128+
path: ${{ env.VSIX }}
129+
if-no-files-found: error
130+
131+
- name: Commit version and compiled output to main
132+
env:
133+
RELEASE_VERSION: ${{ inputs.version }}
134+
run: |
135+
set -euo pipefail
136+
git config user.name 'github-actions[bot]'
137+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
138+
git add package.json CHANGELOG.md out/
139+
if ! git diff --cached --quiet; then
140+
git commit -m "chore(release): prepare v${RELEASE_VERSION}"
141+
git push origin HEAD:main
142+
fi
143+
144+
- name: Publish the VSIX to Open VSX
145+
if: ${{ !inputs.skip_openvsx }}
146+
env:
147+
OVSX_PAT: ${{ secrets.OVSX_PAT }}
148+
run: |
149+
set -euo pipefail
150+
if [[ -n "$OVSX_PAT" ]]; then
151+
ovsx publish "$VSIX"
152+
else
153+
ovsx publish "$VSIX" --trusted-publishing
154+
fi
155+
156+
- name: Publish the SAME VSIX to Visual Studio Marketplace
157+
if: ${{ !inputs.skip_marketplace }}
158+
env:
159+
VSCE_PAT: ${{ secrets.VSCE_PAT }}
160+
run: |
161+
set -euo pipefail
162+
if [[ -n "$VSCE_PAT" ]]; then
163+
vsce publish --packagePath "$VSIX"
164+
else
165+
vsce publish --packagePath "$VSIX" --oidc
166+
fi
167+
168+
- name: Create GitHub release with the published VSIX
169+
env:
170+
GH_TOKEN: ${{ github.token }}
171+
RELEASE_VERSION: ${{ inputs.version }}
172+
run: |
173+
set -euo pipefail
174+
gh release create "v${RELEASE_VERSION}" "$VSIX" \
175+
--target "$(git rev-parse HEAD)" \
176+
--title "v${RELEASE_VERSION}" \
177+
--generate-notes

0 commit comments

Comments
 (0)