-
Notifications
You must be signed in to change notification settings - Fork 0
264 lines (242 loc) · 11.3 KB
/
publish.yml
File metadata and controls
264 lines (242 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
name: Publish
# Fired by `git tag v0.1.0 && git push --tags`. The job verifies the
# tag matches package.json#version (so a stray tag can't ship the
# wrong build), packages the extension, publishes to both the
# VS Code Marketplace and Open VSX, then attaches the .vsix to a
# GitHub release.
#
# Tag-naming convention:
# - `v0.1.0` (stable) → stable marketplace channel.
# - `v0.1.0-rc.1` (pre-release) → pre-release marketplace channel,
# GitHub release marked `prerelease`.
# Detection is by the presence of a `-` after the semver core; see
# the "Detect pre-release tag" step below.
#
# Required repo secrets:
# - VSCE_PAT — Azure DevOps Personal Access Token, scope
# `Marketplace > Acquire and Manage`. Bound to the
# `greylag-ci` publisher on marketplace.visualstudio.com.
# - OVSX_PAT — Open VSX access token from open-vsx.org user
# settings. Bound to the `greylag-ci` namespace
# after the Eclipse Contributor Agreement is signed.
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to (re)publish'
required: true
type: string
# Workflow default is read-only; the publish job widens to
# `contents: write` for the GitHub release step. GitHub Actions doesn't
# support step-level permissions, so this is the tightest scope
# available without splitting into two jobs.
permissions:
contents: read
jobs:
publish:
timeout-minutes: 30
runs-on: ubuntu-latest
# Gate the job on the `production` GitHub Environment so VSCE_PAT
# and OVSX_PAT are only readable from a run that has cleared
# whatever reviewers/branch rules the environment imposes. Anyone
# with push access can still fire workflow_dispatch, but the
# publish steps will block on the environment gate.
environment: production
permissions:
contents: write # needed by the "Create GitHub release" step
id-token: write # OIDC for sigstore signing via attest-build-provenance
attestations: write # store the SLSA provenance attestation on the repo
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0 # needed for the merge-base check below
# The `gh release create` step below uses an explicit
# GH_TOKEN env var, not the persisted credential, so we
# can lock the token out of .git/config (GHA-037).
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Verify tag matches package.json version
env:
# On tag push GITHUB_REF_NAME is the tag; on workflow_dispatch
# it's the dispatching branch, so prefer the explicit input.
REF_NAME: ${{ inputs.tag || github.ref_name }}
run: |
set -euo pipefail
tag="${REF_NAME#v}"
pkg=$(node -p "require('./package.json').version")
if [ "$tag" != "$pkg" ]; then
echo "::error::Tag v$tag does not match package.json version $pkg"
exit 1
fi
echo "Tag and package.json agree on version $pkg"
- name: Verify tag is reachable from main
env:
REF_NAME: ${{ inputs.tag || github.ref_name }}
run: |
set -euo pipefail
git fetch origin main
if ! git merge-base --is-ancestor "$REF_NAME" origin/main; then
echo "::error::Tag $REF_NAME is not reachable from origin/main — refusing to publish"
exit 1
fi
echo "Tag $REF_NAME is on main"
- name: Verify CHANGELOG has a section for this version
# publish.yml already enforces tag/version parity. This step
# closes the matching changelog-fold gap: if the "Unreleased"
# entries haven't been moved into a "## [X.Y.Z]" section, the
# release-notes extraction below would ship the wrong content.
run: |
set -euo pipefail
version=$(node -p "require('./package.json').version")
if ! grep -E "^## \[${version}\]" CHANGELOG.md > /dev/null; then
echo "::error::CHANGELOG.md is missing a '## [${version}]' section — fold the Unreleased entries before tagging"
exit 1
fi
echo "CHANGELOG section found for ${version}"
- name: Lint
run: npm run lint
- name: TypeScript compile
run: npm run compile
- name: Unit tests
run: npm test
- name: Bundle smoke
run: npm run smoke
- name: npm audit (prod deps, high+)
# Block the publish if a HIGH/CRITICAL advisory affects a
# runtime dependency. Dev-only deps are out of scope — they
# don't ship in the .vsix. Mirrors the CI gate so a vuln that
# lands between CI and tag-push still trips here.
run: npm audit --omit=dev --audit-level=high
- name: Detect pre-release tag
# A tag like `v0.2.0-rc.1` (anything with a `-` after the
# semver core) ships to the marketplace's pre-release channel
# and the GitHub release is marked prerelease. Stable tags
# (`v0.2.0`) ship to the stable channel. Detection is purely
# by the version string — keeps the convention discoverable
# via `git tag`.
run: |
set -euo pipefail
version=$(node -p "require('./package.json').version")
if [[ "$version" == *-* ]]; then
echo "Pre-release tag detected: $version"
echo "PRERELEASE_FLAG=--pre-release" >> "$GITHUB_ENV"
echo "GH_PRERELEASE=--prerelease" >> "$GITHUB_ENV"
else
echo "Stable tag: $version"
echo "PRERELEASE_FLAG=" >> "$GITHUB_ENV"
echo "GH_PRERELEASE=" >> "$GITHUB_ENV"
fi
- name: Package vsix
# vsce and ovsx are pinned devDependencies in package.json, so
# `npm ci` above installed the exact versions and Dependabot
# bumps them via the standard npm config. `npx` here resolves
# the local binary — no fresh fetch with PATs in env.
run: |
version=$(node -p "require('./package.json').version")
npx vsce package $PRERELEASE_FLAG --out "pipeline-check-${version}.vsix"
ls -lh "pipeline-check-${version}.vsix"
echo "VSIX_PATH=pipeline-check-${version}.vsix" >> $GITHUB_ENV
echo "SBOM_PATH=pipeline-check-${version}-sbom.cdx.json" >> $GITHUB_ENV
- name: Generate SBOM (CycloneDX)
# Scans package-lock.json so the SBOM captures the full
# production + transitive dep set, not just the tree-shaken
# bundle inside the .vsix. Output is attached to the GitHub
# release below so marketplace consumers can ingest it into
# their vuln-management pipeline.
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
format: cyclonedx-json
output-file: ${{ env.SBOM_PATH }}
# Suppress the action's own artifact upload — we ship the
# SBOM via `gh release create` below instead.
upload-artifact: false
- name: Attest build provenance
# Produces a signed SLSA build provenance attestation for the
# .vsix using GitHub's OIDC token and Sigstore's keyless
# signing flow. Satisfies both signing (cosign/sigstore) and
# SLSA provenance — consumers can verify with
# `gh attestation verify <vsix> --owner greylag-ci`.
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: ${{ env.VSIX_PATH }}
# The two registry publishes use `continue-on-error: true` so a
# transient failure in one registry doesn't strand the GitHub
# release. v1.0.2 hit exactly this: Open VSX returned an HTTP 405
# (registry-side hiccup) and `bash -e` aborted the job, which also
# skipped the release step — leaving the .vsix on Marketplace but
# no GitHub release for the tag. The final "Registry publish
# status" step below still marks the job red if either publish
# failed, so partial-success runs are visible in the run UI and
# to any branch-protection/notification wiring.
- name: Publish to VS Code Marketplace
id: publish-vsce
continue-on-error: true
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
run: |
npx vsce publish $PRERELEASE_FLAG \
--packagePath "$VSIX_PATH" \
--pat "$VSCE_PAT"
- name: Publish to Open VSX
id: publish-ovsx
continue-on-error: true
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
run: |
# Open VSX (ovsx >= 0.10) honours --pre-release the same
# way vsce does. Older versions ignore the flag silently,
# so this stays safe across minor bumps.
npx ovsx publish $PRERELEASE_FLAG "$VSIX_PATH" \
--pat "$OVSX_PAT"
- name: Create GitHub release
# Run as long as at least one registry accepted the publish —
# that's the case where consumers need somewhere to download
# the .vsix from. If both registries failed, skip (no point
# shipping a release tied to a version nobody can install) and
# let the registry-status step below fail the job.
if: |
always() && !cancelled() &&
(steps.publish-vsce.outcome == 'success' ||
steps.publish-ovsx.outcome == 'success')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(node -p "require('./package.json').version")
gh release create "v${version}" "$VSIX_PATH" "$SBOM_PATH" $GH_PRERELEASE \
--title "v${version}" \
--notes-file <(awk '/^## \[/{n++} n==2{exit} n==1{print}' CHANGELOG.md)
- name: Registry publish status
# continue-on-error on the publish steps lets the workflow
# reach the GH release on partial success, but the job itself
# must still fail if any registry rejected the publish so the
# failure is visible in the run UI.
#
# Only run if at least one publish step actually executed — we
# check `conclusion` (the post-continue-on-error roll-up, which
# is 'success' for any step that ran, and 'skipped' for steps
# that didn't reach execution). This keeps the status check
# quiet on lint/test/audit failures upstream of publish.
if: |
always() && !cancelled() &&
(steps.publish-vsce.conclusion == 'success' ||
steps.publish-ovsx.conclusion == 'success')
env:
VSCE_OUTCOME: ${{ steps.publish-vsce.outcome }}
OVSX_OUTCOME: ${{ steps.publish-ovsx.outcome }}
run: |
set -euo pipefail
echo "VS Code Marketplace publish: $VSCE_OUTCOME"
echo "Open VSX publish: $OVSX_OUTCOME"
if [ "$VSCE_OUTCOME" != "success" ] || [ "$OVSX_OUTCOME" != "success" ]; then
echo "::error::One or more registry publishes failed — see step logs above"
exit 1
fi
echo "Both registries accepted the publish."