Skip to content

release: v1.5.0 — daily PyPI poll for newer engine + version realignm… #8

release: v1.5.0 — daily PyPI poll for newer engine + version realignm…

release: v1.5.0 — daily PyPI poll for newer engine + version realignm… #8

Workflow file for this run

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."