release: v1.5.0 — daily PyPI poll for newer engine + version realignm… #8
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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." |