@@ -27,20 +27,52 @@ permissions:
2727 contents : write
2828
2929jobs :
30- validate_inputs :
30+ authorize_actor :
31+ name : Authorize triggering user
32+ runs-on : ubuntu-latest
33+ steps :
34+ - name : Ensure actor has access
35+ uses : actions/github-script@v7
36+ with :
37+ script : |
38+ const { owner, repo } = context.repo;
39+ const username = context.actor;
40+
41+ const response = await github.rest.repos.getCollaboratorPermissionLevel({
42+ owner,
43+ repo,
44+ username,
45+ });
46+
47+ const allowed = ['admin', 'maintain'];
48+
49+ if (!allowed.includes(response.data.permission)) {
50+ core.setFailed(
51+ `${username} must have maintain or admin access to run this workflow. Current permission: ${response.data.permission}`
52+ );
53+ return;
54+ }
55+
56+ core.info(`${username} is authorized with permission: ${response.data.permission}`);
57+
58+ validate :
3159 name : Validate release inputs
60+ needs : authorize_actor
3261 runs-on : ubuntu-latest
62+ outputs :
63+ version : ${{ steps.set_variables.outputs.version }}
3364 steps :
34- - name : Validate tag_name
65+ - name : Validate
3566 shell : bash
3667 run : |
3768 if [ -z "${{ inputs.tag_name }}" ]; then
3869 echo "tag_name is required and cannot be empty" >&2
3970 exit 1
4071 fi
4172
42- if [[ ! "${{ inputs.tag_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]]; then
43- echo "tag_name must look like v1.0.3" >&2
73+ # Enforce SemVer 2.0.0 with a required leading "v" (e.g. v1.2.3, v1.2.3-rc.1, v1.2.3+build.7)
74+ if [[ ! "${{ inputs.tag_name }}" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$ ]]; then
75+ echo "tag_name must be valid SemVer with a leading v (for example v1.0.3 or v1.0.3-rc.1+build.7)" >&2
4476 exit 1
4577 fi
4678
@@ -49,27 +81,47 @@ jobs:
4981 echo "prerelease=${{ inputs.prerelease }}" >&2
5082 echo "draft=${{ inputs.draft }}" >&2
5183
84+ - name : Set Variables
85+ id : set_variables
86+ shell : bash
87+ run : |
88+ TAG_NAME="${{ inputs.tag_name }}"
89+ SEMANTIC_VERSION="${TAG_NAME#v}"
90+ echo "version=$SEMANTIC_VERSION" >> "$GITHUB_OUTPUT"
91+ echo "version=$SEMANTIC_VERSION" >&2
5292
5393 release_notes :
54- needs : validate_inputs
94+ needs : validate
5595 name : Generate release notes
56- uses : ./.github/workflows/generate-release-notes.yml
96+ runs-on : ubuntu-latest
97+ outputs :
98+ release_notes : ${{ steps.generate.outputs.release_notes }}
5799 permissions :
58100 contents : write
59- with :
60- tag_name : ${{ inputs.tag_name }}
61- target_commitish : ${{ inputs.target_commitish }}
62101
63- build :
64- needs : release_notes
65- runs-on : ubuntu-latest
102+ steps :
103+ - name : Generate release notes body
104+ id : generate
105+ uses : actions/github-script@v7
106+ env :
107+ TAG_NAME : ${{ inputs.tag_name }}
108+ TARGET_COMMITISH : ${{ inputs.target_commitish }}
109+ with :
110+ script : |
111+ const { owner, repo } = context.repo;
112+ const tag_name = process.env.TAG_NAME;
113+ const target_commitish = process.env.TARGET_COMMITISH;
66114
67- outputs :
68- tag_name : ${{ steps.release_meta.outputs.tag_name }}
69- release_name : ${{ steps.release_meta.outputs.release_name }}
115+ const response = await github.rest.repos.generateReleaseNotes({
116+ owner,
117+ repo,
118+ tag_name,
119+ target_commitish,
120+ });
70121
71- steps :
72- - name : Checkout code
122+ core.setOutput('release_notes', response.data.body || '');
123+
124+ - name : Checkout target branch
73125 uses : actions/checkout@v5
74126 with :
75127 ref : ${{ inputs.target_commitish }}
@@ -80,69 +132,23 @@ jobs:
80132 node-version : ' 24.x'
81133 cache : ' npm'
82134
83- - name : Install dependencies
84- run : npm install
85-
86- # - name: Validate tag matches package version
87- # id: release_meta
88- # shell: bash
89- # run: |
90- # PACKAGE_VERSION=$(node -p "require('./package.json').version")
91- # EXPECTED_TAG="v${PACKAGE_VERSION}"
92- # if [ "${{ inputs.tag_name }}" != "$EXPECTED_TAG" ]; then
93- # echo "Expected release tag $EXPECTED_TAG for package version $PACKAGE_VERSION, got ${{ inputs.tag_name }}" >&2
94- # exit 1
95- # fi
96-
97- # echo "tag_name=${{ inputs.tag_name }}" >> "$GITHUB_OUTPUT"
98- # echo "release_name=SL-VScode Plugin ${PACKAGE_VERSION} Release" >> "$GITHUB_OUTPUT"
99-
100- - name : Compile extension
101- run : npm run vscode:prepublish
102-
103- - name : Install vsce
104- run : npm install -g @vscode/vsce
105-
106- - name : Package extension
107- run : vsce package
108-
109- - name : Get package filename
110- id : package
135+ - name : Update version
111136 shell : bash
112137 run : |
113- PACKAGE_FILE=$(ls *.vsix | head -1)
114- echo "filename=$PACKAGE_FILE" >> "$GITHUB_OUTPUT"
115-
116- - name : Upload VSIX artifact
117- uses : actions/upload-artifact@v4
118- with :
119- name : vscode-extension-release-${{ github.run_id }}
120- path : ${{ steps.package.outputs.filename }}
121- retention-days : 30
122-
123- release :
124- name : Publish release
125- needs : [build, release_notes]
126- runs-on : ubuntu-latest
127-
128- steps :
129- - name : Checkout target branch
130- uses : actions/checkout@v5
131- with :
132- ref : ${{ inputs.target_commitish }}
138+ VERSION="${{ needs.validate.outputs.version }}"
139+ npm version "$VERSION" --no-git-tag-version --allow-same-version
133140
134141 - name : Write release notes file
135142 shell : bash
136143 env :
137- RELEASE_NOTES : ${{ needs.release_notes .outputs.release_notes }}
144+ RELEASE_NOTES : ${{ steps.generate .outputs.release_notes }}
138145 run : |
139146 printf '%s\n' "$RELEASE_NOTES" > release_notes.md
140147
141148 - name : Update CHANGELOG.md
142149 shell : bash
143150 run : |
144- VERSION="${{ needs.build.outputs.tag_name }}"
145- VERSION="${VERSION#v}"
151+ VERSION="${{ needs.validate.outputs.version }}"
146152 RELEASE_DATE=$(date -u +%Y-%m-%d)
147153
148154 {
@@ -173,6 +179,54 @@ jobs:
173179 mv CHANGELOG.new.md CHANGELOG.md
174180 cat CHANGELOG.md >&2
175181
182+ - name : Update README badges from package.json
183+ shell : bash
184+ run : |
185+ node - <<'NODE'
186+ const fs = require('fs');
187+
188+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
189+ let vscodeVersion = pkg.engines && pkg.engines.vscode ? String(pkg.engines.vscode) : '';
190+
191+ if (vscodeVersion.startsWith('^')) {
192+ vscodeVersion = `${vscodeVersion.slice(1)}+`;
193+ }
194+
195+ const versionBadge = `[](https://github.com/secondlife/sl-vscode-plugin)`;
196+ const licenseBadge = `[](LICENSE)`;
197+ const vscodeBadge = `[}-red.svg)](https://code.visualstudio.com/)`;
198+
199+ let readme = fs.readFileSync('README.md', 'utf8');
200+
201+ readme = readme.replace(/^\[!\[Version\]\(https:\/\/img\.shields\.io\/badge\/version-[^)]+\)\]\(https:\/\/github\.com\/secondlife\/sl-vscode-plugin\)$/m, versionBadge);
202+ readme = readme.replace(/^\[!\[License\]\(https:\/\/img\.shields\.io\/badge\/license-[^)]+\)\]\(LICENSE\)$/m, licenseBadge);
203+ readme = readme.replace(/^\[!\[VS Code\]\(https:\/\/img\.shields\.io\/badge\/VS%20Code-[^)]+\)\]\(https:\/\/code\.visualstudio\.com\/\)$/m, vscodeBadge);
204+
205+ fs.writeFileSync('README.md', readme);
206+ NODE
207+
208+ - name : Package modified files for downstream jobs
209+ shell : bash
210+ run : |
211+ git ls-files -m > modified-files.txt
212+ git ls-files --others --exclude-standard >> modified-files.txt
213+
214+ if [ ! -s modified-files.txt ]; then
215+ echo "No modified files detected" > no-modified-files.txt
216+ tar -czf release-notes-files.tgz no-modified-files.txt
217+ else
218+ tar -czf release-notes-files.tgz -T modified-files.txt
219+ fi
220+
221+ - name : Upload modified files artifact
222+ uses : actions/upload-artifact@v4
223+ with :
224+ name : release-notes-modified-files-${{ github.run_id }}
225+ path : |
226+ release-notes-files.tgz
227+ modified-files.txt
228+ retention-days : 1
229+
176230 # - name: Commit and push changelog update
177231 # shell: bash
178232 # run: |
@@ -195,9 +249,68 @@ jobs:
195249 # exit 0
196250 # fi
197251
198- # git commit -m "docs: update changelog for ${{ needs.build.outputs .tag_name }}"
252+ # git commit -m "docs: update changelog for ${{ inputs .tag_name }}"
199253 # git push origin HEAD:$TARGET_BRANCH
200254
255+ build :
256+ needs : release_notes
257+ runs-on : ubuntu-latest
258+
259+ steps :
260+ - name : Checkout code
261+ uses : actions/checkout@v5
262+ with :
263+ ref : ${{ inputs.target_commitish }}
264+
265+ - name : Setup Node.js
266+ uses : actions/setup-node@v6
267+ with :
268+ node-version : ' 24.x'
269+ cache : ' npm'
270+
271+ - name : Download modified files artifact
272+ uses : actions/download-artifact@v4
273+ with :
274+ name : release-notes-modified-files-${{ github.run_id }}
275+ path : .
276+
277+ - name : Apply modified files from release_notes job
278+ shell : bash
279+ run : |
280+ tar -xzf release-notes-files.tgz
281+
282+ - name : Install dependencies
283+ run : npm install
284+
285+ - name : Compile extension
286+ run : npm run vscode:prepublish
287+
288+ - name : Install vsce
289+ run : npm install -g @vscode/vsce
290+
291+ - name : Package extension
292+ run : vsce package
293+
294+ - name : Get package filename
295+ id : package
296+ shell : bash
297+ run : |
298+ PACKAGE_FILE=$(ls *.vsix | head -1)
299+ echo "filename=$PACKAGE_FILE" >> "$GITHUB_OUTPUT"
300+
301+ - name : Upload VSIX artifact
302+ uses : actions/upload-artifact@v4
303+ with :
304+ name : vscode-extension-release-${{ github.run_id }}
305+ path : ${{ steps.package.outputs.filename }}
306+ retention-days : 30
307+
308+ release :
309+ name : Publish release
310+ needs : [build, release_notes]
311+ runs-on : ubuntu-latest
312+
313+ steps :
201314 - name : Download VSIX artifact
202315 uses : actions/download-artifact@v4
203316 with :
@@ -210,6 +323,17 @@ jobs:
210323 PACKAGE_FILE=$(ls *.vsix | head -1)
211324 echo "filename=$PACKAGE_FILE" >> "$GITHUB_OUTPUT"
212325
326+ - name : Create release page
327+ id : release
328+ uses : secondlife-3p/action-gh-release@v1
329+ with :
330+ # name the release page for the branch
331+ name : " SL-VScode Plugin ${{ steps.version.outputs.version }} Release"
332+ prerelease : true
333+ body_path : release_notes.md
334+ target_commitish : ${{ github.sha }}
335+ files : ${{ steps.package.outputs.filename }}
336+
213337 # - name: Create GitHub release
214338 # uses: secondlife-3p/action-gh-release@v1
215339 # with:
0 commit comments