Skip to content

Release and Publish VS Code Extension #95

Release and Publish VS Code Extension

Release and Publish VS Code Extension #95

Workflow file for this run

# MIT License
#
# Copyright (c) 2026 Mickaël CANOUIL
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
name: Release and Publish VS Code Extension
concurrency:
group: release
cancel-in-progress: false
on:
workflow_dispatch:
inputs:
type:
type: choice
description: Type
options:
- release
- pre-release
default: release
date:
type: string
description: 'Date ("YYYY-MM-DD" or "today")'
default: today
version:
type: string
description: "Version"
default: "minor"
jobs:
bump-version:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
env:
BRANCH: ci/bump-version
COMMIT: "ci: bump version for release :rocket:"
BUMP_VERSION: ${{ inputs.version }}
outputs:
version: ${{ steps.bump-version.outputs.version }}
release_title: ${{ steps.bump-version.outputs.release_title }}
summary: ${{ steps.bump-version.outputs.summary }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Quarto CLI
uses: quarto-dev/quarto-actions/setup@v2
with:
version: pre-release
- name: Create GitHub App token
uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_KEY }}
- name: Get GitHub App User ID
id: get-user-id
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
run: |
echo "user-id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "${GITHUB_OUTPUT}"
- name: Configure Environment for Git User
env:
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
USER_ID: ${{ steps.get-user-id.outputs.user-id }}
run: |
if [ -z "${APP_SLUG}" ]; then
USER_NAME="github-actions[bot]"
USER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"
else
USER_NAME="${APP_SLUG}[bot]"
USER_EMAIL="${USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com"
fi
git config --global user.name "${USER_NAME}"
git config --global user.email "${USER_EMAIL}"
- name: Check for "unreleased" in CHANGELOG.md
id: check_unreleased
shell: bash
run: |
if grep -q "Unreleased" CHANGELOG.md; then
echo "UNRELEASED_FOUND=true" >> ${GITHUB_OUTPUT}
else
echo "UNRELEASED_FOUND=false" >> ${GITHUB_OUTPUT}
fi
- name: Set up Node.js
if: ${{ steps.check_unreleased.outputs.UNRELEASED_FOUND == 'true' }}
uses: actions/setup-node@v6
- name: Install dependencies
if: ${{ steps.check_unreleased.outputs.UNRELEASED_FOUND == 'true' }}
shell: bash
run: npm install
- name: Install Visual Studio Code Extension Manager
if: ${{ steps.check_unreleased.outputs.UNRELEASED_FOUND == 'true' }}
shell: bash
run: npm install -g @vscode/vsce
- name: Bump Version / Commit / Push CHANGELOG.md
if: ${{ steps.check_unreleased.outputs.UNRELEASED_FOUND == 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
COMMIT: "ci: bump version for release :rocket:"
DATE: ${{ github.event.inputs.date }}
shell: bash
run: |
if git show-ref --quiet refs/heads/${BRANCH}; then
echo "Branch ${BRANCH} already exists."
git branch -D "${BRANCH}"
git push origin --delete "${BRANCH}"
fi
git checkout -b "${BRANCH}"
if [ "${DATE}" = "today" ]; then
DATE=$(date +%Y-%m-%d)
else
DATE=${DATE}
fi
# vsce package ${BUMP_VERSION} -m "${COMMIT}"
npm version ${BUMP_VERSION} --no-git-tag-version
npm run sync-metadata
VERSION=$(jq -r .version package.json)
RELEASE_DATE="${VERSION} (${DATE})"
sed -i "s/Unreleased/${RELEASE_DATE}/" CHANGELOG.md
sed -i "s/^version:.*/version: ${VERSION}/" CITATION.cff
sed -i "s/^date-released:.*/date-released: \"${DATE}\"/" CITATION.cff
git add CHANGELOG.md || echo "No changes to add"
git add CITATION.cff || echo "No changes to add"
git add packages/*/package.json || echo "No changes to add"
git add package.json || echo "No changes to add"
git add package-lock.json || echo "No changes to add"
npm run docs:build
git add docs/ || echo "No changes to add"
git commit -m "${COMMIT}" || echo "No changes to commit"
git push --force origin ${BRANCH} || echo "No changes to push"
git status
sleep 5
gh pr create --fill-first --base "${GITHUB_REF_NAME}" --head "${BRANCH}" --label "Type: CI/CD :robot:"
sleep 5
gh pr merge --auto --squash --delete-branch
sleep 5
package:
runs-on: ubuntu-latest
needs: bump-version
permissions:
contents: read
id-token: write
attestations: write
outputs:
version: ${{ steps.set-version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Update branch
run: |
git fetch --all
git checkout main
git pull origin main
- name: Set up Node.js
uses: actions/setup-node@v6
- name: Install dependencies
shell: bash
run: npm install
- name: Install Visual Studio Code Extension Manager
shell: bash
run: npm install -g @vscode/vsce
- name: Set version
id: set-version
shell: bash
run: |
current_version=$(jq -r .version package.json)
version_line=$(grep -n '"version"' package.json | cut -d: -f1)
echo "VERSION=${current_version}" >> ${GITHUB_ENV}
echo "version=${current_version}" >> ${GITHUB_OUTPUT}
echo "::notice file=package.json,line=${version_line}::${current_version}"
- name: Set changelog
env:
VERSION: ${{ env.VERSION }}
shell: bash
run: |
awk -v version="^## ${VERSION}.*" '
$0 ~ version {flag=1; next}
/^## / && flag {flag=0}
flag
' CHANGELOG.md >"CHANGELOG-${VERSION}.md"
echo "CHANGELOG=CHANGELOG-${VERSION}.md" >> ${GITHUB_ENV}
- name: Append installation instructions to changelog
env:
WIZARD_VERSION: ${{ env.VERSION }}
CHANGELOG: ${{ env.CHANGELOG }}
REPO: ${{ github.repository }}
DOCS_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}
shell: bash
run: |
envsubst '${WIZARD_VERSION} ${REPO} ${DOCS_URL}' \
< .github/workflows/assets/install-instructions.md \
>> "${CHANGELOG}"
- name: Package extension
env:
TYPE: ${{ github.event.inputs.type }}
shell: bash
run: |
if [ "${TYPE}" = "pre-release" ]; then
vsce package --pre-release
else
vsce package
fi
- name: Package core library
shell: bash
run: |
cd packages/core
npm pack
mv quarto-wizard-core-*.tgz "${GITHUB_WORKSPACE}/"
- name: Package schema library
shell: bash
run: |
cd packages/schema
npm pack
mv quarto-wizard-schema-*.tgz "${GITHUB_WORKSPACE}/"
- name: Package snippets library
shell: bash
run: |
cd packages/snippets
npm pack
mv quarto-wizard-snippets-*.tgz "${GITHUB_WORKSPACE}/"
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v4
with:
subject-path: |
${{ github.workspace }}/quarto-wizard-${{ env.VERSION }}.vsix
${{ github.workspace }}/quarto-wizard-core-${{ env.VERSION }}.tgz
${{ github.workspace }}/quarto-wizard-schema-${{ env.VERSION }}.tgz
${{ github.workspace }}/quarto-wizard-snippets-${{ env.VERSION }}.tgz
- name: Upload release bundle
uses: actions/upload-artifact@v7
with:
name: release-bundle
path: |
quarto-wizard-${{ env.VERSION }}.vsix
quarto-wizard-core-${{ env.VERSION }}.tgz
quarto-wizard-schema-${{ env.VERSION }}.tgz
quarto-wizard-snippets-${{ env.VERSION }}.tgz
CHANGELOG-${{ env.VERSION }}.md
if-no-files-found: error
retention-days: 30
release-github:
runs-on: ubuntu-latest
needs: package
permissions:
contents: write
env:
VERSION: ${{ needs.package.outputs.version }}
GH_REPO: ${{ github.repository }}
steps:
- name: Create GitHub App token
uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_KEY }}
- name: Download release bundle
uses: actions/download-artifact@v8
with:
name: release-bundle
- name: Create or update GitHub release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
TYPE: ${{ github.event.inputs.type }}
CHANGELOG: CHANGELOG-${{ env.VERSION }}.md
shell: bash
run: |
ASSETS=(
"./quarto-wizard-${VERSION}.vsix#Quarto Wizard (vsix)"
"./quarto-wizard-core-${VERSION}.tgz#Quarto Wizard Core (tgz)"
"./quarto-wizard-schema-${VERSION}.tgz#Quarto Wizard Schema (tgz)"
"./quarto-wizard-snippets-${VERSION}.tgz#Quarto Wizard Snippets (tgz)"
)
if gh release view "${VERSION}" >/dev/null 2>&1; then
echo "::notice::Release ${VERSION} already exists; refreshing assets and notes."
FILES=()
for asset in "${ASSETS[@]}"; do
FILES+=("${asset%%#*}")
done
gh release upload "${VERSION}" "${FILES[@]}" --clobber
gh release edit "${VERSION}" --notes-file "${CHANGELOG}"
exit 0
fi
PRERELEASE_FLAG=""
if [ "${TYPE}" = "pre-release" ]; then
PRERELEASE_FLAG="--prerelease"
fi
gh release create "${VERSION}" \
"${ASSETS[@]}" \
${PRERELEASE_FLAG} \
--title "${VERSION}" \
--notes-file "${CHANGELOG}" \
--generate-notes
publish-marketplace:
runs-on: ubuntu-latest
needs: [package, release-github]
permissions: {}
env:
VERSION: ${{ needs.package.outputs.version }}
steps:
- name: Set up Node.js
uses: actions/setup-node@v6
- name: Install Visual Studio Code Extension Manager
shell: bash
run: npm install -g @vscode/vsce
- name: Download release bundle
uses: actions/download-artifact@v8
with:
name: release-bundle
- name: Publish to Visual Studio Marketplace
env:
VS_MARKETPLACE_TOKEN: ${{ secrets.VS_MARKETPLACE_TOKEN }}
TYPE: ${{ github.event.inputs.type }}
shell: bash
run: |
PUBLISHED=$(vsce show mcanouil.quarto-wizard --json 2>/dev/null \
| jq -r --arg v "${VERSION}" '.versions[]? | select(.version == $v) | .version' \
| head -n 1) || PUBLISHED=""
if [ -n "${PUBLISHED}" ]; then
echo "::notice::mcanouil.quarto-wizard@${VERSION} already on Marketplace, skipping."
exit 0
fi
PRERELEASE_FLAG=""
if [ "${TYPE}" = "pre-release" ]; then
PRERELEASE_FLAG="--pre-release"
fi
vsce publish ${PRERELEASE_FLAG} \
--packagePath "quarto-wizard-${VERSION}.vsix" \
--pat "${VS_MARKETPLACE_TOKEN}"
publish-openvsx:
runs-on: ubuntu-latest
needs: [package, release-github]
permissions: {}
env:
VERSION: ${{ needs.package.outputs.version }}
steps:
- name: Set up Node.js
uses: actions/setup-node@v6
- name: Download release bundle
uses: actions/download-artifact@v8
with:
name: release-bundle
- name: Publish to Open VSX Registry
env:
OPEN_VSX_REGISTRY_TOKEN: ${{ secrets.OPEN_VSX_REGISTRY_TOKEN }}
TYPE: ${{ github.event.inputs.type }}
shell: bash
run: |
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
"https://open-vsx.org/api/mcanouil/quarto-wizard/${VERSION}")
if [ "${STATUS}" = "200" ]; then
echo "::notice::mcanouil/quarto-wizard@${VERSION} already on Open VSX, skipping."
exit 0
fi
PRERELEASE_FLAG=""
if [ "${TYPE}" = "pre-release" ]; then
PRERELEASE_FLAG="--pre-release"
fi
npx --yes ovsx publish ${PRERELEASE_FLAG} \
"quarto-wizard-${VERSION}.vsix" \
--pat "${OPEN_VSX_REGISTRY_TOKEN}"