From 1aea72ce934116893f93982bdf3b0ab2fb1aa5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=AF=BC=EC=A0=95=EC=9D=98=20=EB=A7=A5=EB=B6=81?= Date: Thu, 1 Oct 2026 19:50:11 +0900 Subject: [PATCH] ci: automate Marketplace publishing from main --- .github/workflows/publish-extension.yml | 96 ++++++++++++++++++------- CHANGELOG.md | 2 +- docs/release-checklist.md | 11 +-- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index fef59a8..5cd9c5f 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -2,22 +2,74 @@ name: Publish Extension on: push: - tags: - - 'v*' + branches: + - main + workflow_dispatch: concurrency: - group: publish-extension-${{ github.ref }} + group: publish-extension-main cancel-in-progress: false permissions: contents: read jobs: + detect_release: + name: detect release + runs-on: ubuntu-latest + outputs: + should_publish: ${{ steps.release.outputs.should_publish }} + tag: ${{ steps.release.outputs.tag }} + version: ${{ steps.release.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Detect unpublished package version + id: release + shell: bash + run: | + set -euo pipefail + + package_version=$(node -p "require('./package.json').version") + release_tag="v${package_version}" + + if [[ ! "$package_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Package version $package_version is not a supported release version." + exit 1 + fi + + echo "version=$package_version" >> "$GITHUB_OUTPUT" + echo "tag=$release_tag" >> "$GITHUB_OUTPUT" + + if git show-ref --verify --quiet "refs/tags/$release_tag"; then + echo "Version $package_version is already tagged; skipping Marketplace publish." + echo "should_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + escaped_version=${package_version//./\\.} + if ! grep -Eq "^## ${escaped_version} - [0-9]{4}-[0-9]{2}-[0-9]{2}$" CHANGELOG.md; then + echo "CHANGELOG.md does not contain a dated $package_version release heading." + exit 1 + fi + + echo "Version $package_version has not been published yet." + echo "should_publish=true" >> "$GITHUB_OUTPUT" + publish: name: publish + needs: detect_release + if: needs.detect_release.outputs.should_publish == 'true' runs-on: ubuntu-latest + permissions: + contents: write env: DSLFORGE_TELEMETRY_PROXY_ENDPOINT: ${{ vars.DSLFORGE_TELEMETRY_PROXY_ENDPOINT }} + RELEASE_TAG: ${{ needs.detect_release.outputs.tag }} + RELEASE_VERSION: ${{ needs.detect_release.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v7 @@ -33,25 +85,6 @@ jobs: - name: Install dependencies run: npm ci - - name: Verify tag matches package version - run: | - package_version=$(node -p "require('./package.json').version") - tag_version="${GITHUB_REF_NAME#v}" - - if [ "$package_version" != "$tag_version" ]; then - echo "Tag version $tag_version does not match package.json version $package_version." - exit 1 - fi - - - name: Verify tag commit is on main - run: | - git fetch origin main - - if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then - echo "Release tags must point to a commit that is already on origin/main." - exit 1 - fi - - name: Run format check run: npm run format:check @@ -65,16 +98,29 @@ jobs: run: npm exec -- vsce ls - name: Package extension - run: npm exec -- vsce package --out "dslforge-${GITHUB_REF_NAME#v}.vsix" + run: npm exec -- vsce package --out "dslforge-${RELEASE_VERSION}.vsix" - name: Upload extension package uses: actions/upload-artifact@v7 with: - name: dslforge-${{ github.ref_name }} + name: dslforge-${{ needs.detect_release.outputs.tag }} path: dslforge-*.vsix if-no-files-found: error + - name: Create release tag + id: release_tag + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag --annotate "$RELEASE_TAG" "$GITHUB_SHA" --message "Release $RELEASE_TAG" + git push origin "refs/tags/$RELEASE_TAG" + - name: Publish extension + id: marketplace_publish env: VSCE_PAT: ${{ secrets.VSCE_PAT }} - run: npm exec -- vsce publish --packagePath "dslforge-${GITHUB_REF_NAME#v}.vsix" -p "$VSCE_PAT" + run: npm exec -- vsce publish --packagePath "dslforge-${RELEASE_VERSION}.vsix" -p "$VSCE_PAT" + + - name: Remove release tag after publish failure + if: always() && steps.release_tag.outcome == 'success' && steps.marketplace_publish.outcome != 'success' + run: git push origin --delete "$RELEASE_TAG" diff --git a/CHANGELOG.md b/CHANGELOG.md index 448f29a..4cec03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. ## 0.3.2 - 2026-10-01 -- Hardened the tag-based release workflow with GitHub Actions v7, a pinned `@vscode/vsce` toolchain, package-content verification, and archived VSIX artifacts. +- Automated Marketplace publishing for untagged package versions promoted to `main`, with release-tag rollback on publish failure, GitHub Actions v7, a pinned `@vscode/vsce` toolchain, package-content verification, and archived VSIX artifacts. - Added CI packaging checks so pull requests verify the same extension payload used by releases. - Added telemetry policy and proxy regression coverage for opt-out behavior, endpoint selection, sensitive-field filtering, payload validation, sink failures, and rate limiting. - Fixed client telemetry filtering so allowlisted aggregate fields such as output kind, context file counts, and apply target counts are preserved without allowing workspace content or paths. diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 1731dcf..9255e65 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -35,7 +35,7 @@ Completed in this audit: - reviewed AI preview apply logic now has dedicated fixture coverage for file-target parsing, bundle selection, and conflict detection - telemetry client policy and proxy validation now have automated regression coverage - pull requests now verify the extension package payload with pinned `@vscode/vsce` -- tagged releases now archive the generated VSIX before publishing +- untagged package versions promoted to `main` now publish automatically and archive the generated VSIX - `preview: true` remains intentional for `0.3.2` - `dslforge-0.3.2.vsix` installs successfully in an isolated VS Code profile - the `VSCE_PAT` secret and `DSLFORGE_TELEMETRY_PROXY_ENDPOINT` repository variable are configured @@ -44,8 +44,8 @@ Completed in this audit: - complete manual VS Code smoke for the Xtext fixtures and AI preview/apply path - confirm publisher access and Marketplace login path -- review the exact release commit state before `vsce publish` -- publish intentionally with the confirmed publisher account +- review the exact release commit state before merging the version change to `main` +- confirm the automatic Marketplace publish and generated release tag succeed ## Deferred Or Optional For This Workstream @@ -120,14 +120,15 @@ These items must be true before `vsce publish`: - `package.json.bugs.url` - `package.json.homepage` - git `origin` -- Release tagging is intentional, but Marketplace publish now runs automatically from the `Publish Extension` GitHub Actions workflow when a `v*` tag is pushed +- Marketplace publish runs automatically when `main` contains a package version without a matching `v*` tag +- the workflow validates the dated changelog heading, packages and publishes the extension, then keeps the generated tag only when Marketplace publish succeeds ## Publisher Checklist - Confirm Marketplace publisher exists for `emfpdlzj` - Confirm you can authenticate with the publisher account - Confirm the pinned `@vscode/vsce` version installs through `npm ci` -- Confirm the `VSCE_PAT` GitHub Actions secret exists before pushing a release tag +- Confirm the `VSCE_PAT` GitHub Actions secret exists before merging a new package version to `main` - Prefer an Entra ID-based publishing path for longer-term automation planning - If doing a one-off manual publish, confirm the current official authentication path you will use before release day