Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ Keep the section headings; fill what applies, delete what doesn't.

## Type of change

<!-- Keep one: -->
- [ ] Bug fix (non-breaking)
- [ ] New feature (non-breaking)
- [ ] Breaking change (existing behaviour differs afterwards)
- [ ] Documentation / CI / chore
<!-- Keep one. A maintainer applies the matching label at merge; that label sets
the release version bump. -->
- [ ] Bug fix (non-breaking) → `fix` (patch)
- [ ] New feature (non-breaking) → `feature` (minor)
- [ ] Breaking change (existing behaviour differs afterwards) → `breaking` (major)
- [ ] Documentation / CI / chore → `docs` / `ci` / `chore` (patch)

## How was this tested?

Expand Down
65 changes: 65 additions & 0 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Release Drafter config.
#
# Keeps a single DRAFT GitHub Release continuously up to date as PRs merge to
# main. The maintainer reviews that draft and clicks "Publish" to ship — which
# creates the tag and triggers publish.yml. Nothing publishes automatically.
#
# The next version is inferred from the labels on the merged PRs:
# major → "breaking" minor → "feature"/"enhancement"
# patch → everything else (the default)
#
# A version label is set by the maintainer when they merge (the PR template shows
# the mapping; unlabeled defaults to patch). The `autolabeler` block below is
# kept ready but only runs if a pull_request trigger is added to the workflow —
# see the note in .github/workflows/release-drafter.yml.

name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'

categories:
- title: '✨ Features'
labels: ['feature', 'enhancement']
- title: '🐛 Fixes'
labels: ['fix', 'bug']
- title: '📝 Docs / CI / chore'
labels: ['docs', 'ci', 'chore', 'dependencies']

change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
change-title-escapes: '\<*_&'

version-resolver:
major:
labels: ['breaking', 'major']
minor:
labels: ['feature', 'enhancement', 'minor']
patch:
labels: ['fix', 'bug', 'docs', 'ci', 'chore', 'dependencies', 'patch']
default: patch

# Apply a label from the branch name or a conventional-commit-style title prefix.
autolabeler:
- label: 'breaking'
title: ['/!:/', '/^[a-z]+(\(.+\))?!:/i']
- label: 'feature'
branch: ['/^feat(ure)?\//']
title: ['/^feat(\(.+\))?:/i']
- label: 'fix'
branch: ['/^(fix|bugfix|hotfix)\//']
title: ['/^fix(\(.+\))?:/i']
- label: 'docs'
branch: ['/^docs?\//']
title: ['/^docs(\(.+\))?:/i']
- label: 'ci'
branch: ['/^ci\//']
title: ['/^ci(\(.+\))?:/i']
- label: 'chore'
branch: ['/^chore\//']
title: ['/^chore(\(.+\))?:/i']

template: |
## What's changed

$CHANGES

**Contributors:** $CONTRIBUTORS
**Full changelog:** https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
67 changes: 33 additions & 34 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
# Publish on `v*` tag push.
# Publish when a GitHub Release is published.
#
# What this does:
# 1. Checks out the tagged commit.
# 2. Verifies the tag matches package.json's version (catches "I pushed
# v2.0.1 but forgot to bump package.json" mistakes before they ship).
# 3. Compiles TypeScript, packages a .vsix.
# 4. Publishes to the VS Code Marketplace (vsce) using the VSCE_PAT secret.
# 5. Publishes to Open VSX Registry (ovsx) using the OVSX_PAT secret.
# 6. Attaches the .vsix to the GitHub Release for that tag, auto-generating
# release notes from PRs merged since the previous release.
# Merge-and-auto-release flow:
# 1. PRs merge to main; release-drafter keeps a DRAFT Release up to date with
# categorised notes + the next semver version (see release-drafter.yml).
# 2. When ready to ship, a maintainer reviews that draft and clicks
# "Publish release" — the single approval gesture.
# 3. Publishing fires this workflow, which:
# - checks out the released commit,
# - stamps package.json's version from the release tag (so nobody
# hand-bumps it — the tag is the source of truth),
# - compiles + packages a .vsix,
# - publishes to the VS Code Marketplace (VSCE_PAT) and Open VSX (OVSX_PAT),
# - attaches the .vsix to the Release.
#
# Usage:
# git tag v2.0.1 && git push origin v2.0.1
# ↳ this workflow then runs end to end. ~5 min wall-clock.
#
# Or trigger manually from Actions → Publish Extension → Run workflow
# (workflow_dispatch — useful if a publish fails and we want to retry).
# Trigger manually from Actions → Publish Extension → Run workflow
# (workflow_dispatch — useful to retry a failed Marketplace/Open VSX publish).

name: Publish Extension

on:
push:
tags:
- 'v*'
release:
types: [published]
workflow_dispatch:

permissions:
contents: write # needed by softprops/action-gh-release to create / attach assets
contents: write # needed by softprops/action-gh-release to attach the .vsix asset

jobs:
publish:
Expand All @@ -43,18 +41,16 @@ jobs:
- name: Install dependencies
run: npm ci

# Belt-and-braces: refuse to publish if the tag doesn't match the
# version actually baked into package.json (which is what vsce reads).
- name: Verify tag matches package.json version
if: startsWith(github.ref, 'refs/tags/v')
# The release tag is the single source of truth for the version. Stamp it
# into package.json so vsce packages the right number — no human bump, no
# tag-vs-package.json mismatch possible. Build-time only (not committed).
- name: Set version from release tag
if: github.event_name == 'release'
run: |
TAG=${GITHUB_REF#refs/tags/v}
VER=$(node -p "require('./package.json').version")
if [ "$TAG" != "$VER" ]; then
echo "::error::Tag v$TAG does not match package.json version $VER"
exit 1
fi
echo "OK: tag v$TAG matches package.json $VER"
TAG="${{ github.event.release.tag_name }}"
VER="${TAG#v}"
npm version "$VER" --no-git-tag-version --allow-same-version
echo "package.json version set to $VER (from release $TAG)"

- name: Compile
run: npm run compile
Expand All @@ -72,11 +68,14 @@ jobs:
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}

- name: Attach .vsix to GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
# The Release already exists (the maintainer just published it) and already
# has release-drafter's notes — so we only upload the asset, never
# regenerate the body.
- name: Attach .vsix to the GitHub Release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: claude-code-usage.vsix
generate_release_notes: true
tag_name: ${{ github.event.release.tag_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 changes: 34 additions & 0 deletions .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Drafts the next GitHub Release as changes land on main.
#
# On every push to main, release-drafter (re)builds a single DRAFT Release with
# categorised notes and the next semver version inferred from the merged PRs'
# labels (config in .github/release-drafter.yml). Nothing is published here — the
# maintainer reviews the draft and clicks "Publish release" when ready, which
# creates the tag and triggers publish.yml.
#
# Why push-to-main only (no pull_request trigger): Release Drafter reads its
# config from the *default* branch, and GitHub forces the default GITHUB_TOKEN
# read-only for PRs opened from forks — so a pull_request autolabeler couldn't
# label external contributions anyway. Maintainers set the version label when
# they merge (the PR template shows the mapping; unlabeled defaults to patch).
# To enable branch/title autolabeling for same-repo PRs later, add a
# `pull_request_target` trigger plus `pull-requests: write`.

name: Release Drafter

on:
push:
branches: [main]

permissions:
contents: read

jobs:
update_release_draft:
permissions:
contents: write # create / update the draft release
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thank you for addressing this issue.
As a security engineer, I totally support this change.
In the same line, please make sure that the commit is pinned instead of attached to a version such as @v6

Should release-drafter/release-drafter be compromised and publish a new evil release e.g. v6.6.6. You are compromised by supply chain.
If you pin the commit instead you won't be impacted.

Suggested change
- uses: release-drafter/release-drafter@v6
- uses: release-drafter/release-drafter@693d20e7c1ce1a81d3a41962f85914253b518449 #v7.3.1

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 changes: 11 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,17 @@ npx @vscode/vsce package # build a .vsix
```

- **F5** launches the Extension Development Host for manual testing.
- **Releases are automated.** Pushing a `v*` tag to the upstream repo triggers
`.github/workflows/publish.yml`, which compiles, packages, publishes to the
VS Code Marketplace + Open VSX, and attaches the `.vsix` to a GitHub Release.
The workflow verifies the tag matches `package.json`'s `version`. So to
release: bump `version`, update `CHANGELOG.md`, merge to `main`, then
`git tag vX.Y.Z && git push <upstream> vX.Y.Z`.
- Versioning follows semver: bug-only → patch (`2.0.x`); new user-facing
feature → minor (`2.x.0`); breaking UX → major.
- **Releases are merge-and-auto-release.** As labelled PRs merge to `main`,
`.github/workflows/release-drafter.yml` keeps a **draft GitHub Release** up to
date (categorised notes + next semver version from the PR labels). To ship, a
maintainer reviews that draft and clicks **Publish** — which creates the tag
and triggers `.github/workflows/publish.yml`. Publish checks out the released
commit, stamps `package.json`'s version *from the release tag* (no hand-bump),
compiles, packages, publishes to the VS Code Marketplace + Open VSX, and
attaches the `.vsix`. So to release: just **Publish the draft Release** — no
manual `version` bump, no `git tag`.
- Versioning follows semver and is driven by PR labels: `fix`/none → patch
(`2.0.x`); `feature` → minor (`2.x.0`); `breaking` → major.

## Conventions

Expand Down
18 changes: 16 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,22 @@ welcome.

## Releases

Releases are automated: pushing a `v*` tag triggers the publish workflow
(Marketplace + Open VSX + GitHub Release). Maintainers handle tagging.
Releases are automated and contributor-friendly — you never touch versions or
tags:

1. **Open a PR** (a `fix/…`, `feat/…` or `docs/…` branch name keeps the intent
clear). A maintainer applies the matching label when merging — that label sets
the version bump (`feature` → minor, `breaking` → major, otherwise patch;
unlabeled → patch).
2. **A maintainer merges it.** [Release Drafter](https://github.com/release-drafter/release-drafter)
then adds your change to a continuously-updated **draft GitHub Release** and
recomputes the next version.
3. **To ship, a maintainer reviews that draft and clicks Publish.** That creates
the tag and triggers `publish.yml`, which packages and pushes to the VS Code
Marketplace + Open VSX and attaches the `.vsix`.

Because changes ship by **merging** your PR — not by re-applying it — your commit
authorship and the PR's *merged* status are preserved.

## Code of conduct

Expand Down