Skip to content
Closed
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
60 changes: 53 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,65 @@ CI runs on **GitHub Actions**. The [Health Check workflow](.github/workflows/hea

## Publishing a release

1. `npm version <X.Y.Z> --no-git-tag-version` — bumps `package.json`'s `version` and `package-lock.json`'s two root fields (`version` and `packages."".version`) atomically. Don't hand-edit these or find-and-replace the version string across the lockfile: the version can collide with an unrelated dependency's own version elsewhere in `package-lock.json` (e.g. `1.8.11` matches `typed-rest-client@1.8.11`), corrupting that entry. `--no-git-tag-version` skips npm's own commit/tag, since steps 3-4 below handle that. Then promote the `[Unreleased]` section in `CHANGELOG.md` to a new dated `[X.Y.Z]` heading. Sweep `main` since the last release for merged PRs that didn't add their own changelog entries.
2. `npm run compile && npm test`
Verify both tokens resolve before you build anything — a missing or expired one is far cheaper to find now than after the commit and tag exist:

```sh
npx @vscode/vsce verify-pat gemtalksystems
npx ovsx verify-pat gemtalksystems
```

1. `npm version <X.Y.Z> --no-git-tag-version` — bumps `package.json`'s `version` and `package-lock.json`'s two root fields (`version` and `packages."".version`) atomically. Don't hand-edit these or find-and-replace the version string across the lockfile: the version can collide with an unrelated dependency's own version elsewhere in `package-lock.json` (e.g. `1.8.11` matches `typed-rest-client@1.8.11`), corrupting that entry. `--no-git-tag-version` skips npm's own commit/tag, since steps 3-4 below handle that.

**`npm version` also reformats `package.json`.** It rewrites the whole file in npm's own JSON style, expanding the compact `devEngines` object and the `enum` arrays to one entry per line (~20 spurious lines). `npm run format` only globs `*.{ts,mts,cts,js,mjs,cjs}`, so **`format:check` will not catch this** and the reformat lands in the release commit. Undo it and change only the version line:

```sh
npm version <X.Y.Z> --no-git-tag-version # updates package.json + package-lock.json
git checkout package.json # discard npm's reformat (the lockfile keeps its bump)
# then edit package.json's "version" line by hand — it is the only occurrence of the old version
```
Comment on lines +143 to +149

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🚫 I verified this claim, and it's problematic; I think we should remove it. At some point in the past, someone manually reformatted the package.json the wrong way, so the first time you attempted to create a release, npm fixed the formatting. But it seems you manually reverted that. The format should be the correct one in main right now, so these instructions are not really needed, but also are the wrong instructions. npm owns the format, so if it applies a reformat it will leave the file in the correct one.


Then promote the `[Unreleased]` section in `CHANGELOG.md` to a new dated `[X.Y.Z]` heading. Sweep `main` since the last release for merged PRs that didn't add their own changelog entries — `git log --oneline vX.Y.Z..HEAD --merges --grep="Merge pull request"` lists them. **Check the previous release's section too:** a branch cut before the last `[Unreleased]` → `[X.Y.Z]` rename merges its bullet into that now-released section, so the entry claims to have shipped in a version that never contained it. Move any such entry into the new section.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💭 --merges --grep="Merge pull request" only finds PRs that landed as merge commits — a squash-merged PR and anything committed straight to main are both invisible to it. 7dc5233, the commit this PR re-applies, is exactly that second case, so the sweep would have missed it.

Would a plain git log --oneline vX.Y.Z..HEAD (or --no-merges) be safer here? Noisier, but the point of the sweep is to catch the entries nobody wrote down, and those are the likeliest to have arrived by an unusual route.


2. `npm run lint && npm run format:check && npm run compile && npm test` — the full gate, matching the one every PR is held to.
3. Commit the version + changelog changes (e.g. `Release X.Y.Z: <one-line summary>`).
4. `git tag -a vX.Y.Z -m "Release X.Y.Z"` — annotated tag, on the release commit.
5. `npx @vscode/vsce package` — produces `gemstone-ide-X.Y.Z.vsix` in the repo root. The previous version's `.vsix` is gitignored but stays on disk; delete it to keep the root tidy.
6. `npm run publish` — runs `vsce publish` then `ovsx publish` for the VS Code Marketplace and Open VSX. If `vsce publish` times out on the Azure DevOps Gallery API (it happens), re-run `npx @vscode/vsce publish` directly — don't re-run `npm run publish`, since the ovsx step will then double-publish and fail with "already exists."
5. `npx @vscode/vsce package` — produces `gemstone-ide-X.Y.Z.vsix` in the repo root. The previous version's `.vsix` is gitignored but stays on disk; delete it to keep the root tidy. Note this file is **not** what gets uploaded: both commands in step 6 repackage into their own temp `.vsix`. It is for local inspection and archival.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💭 There's an npm run package script (vsce package) and CLAUDE.md documents it as the packaging command — should this step use it, so the two don't drift?

6. `npm run publish` — runs `vsce publish` then `ovsx publish` for the VS Code Marketplace and Open VSX. If `vsce publish` times out on the Azure DevOps Gallery API (it happens), re-run `npx @vscode/vsce publish` directly rather than `npm run publish`, so the ovsx half doesn't run twice. If it does run twice it is harmless — `ovsx` refuses a duplicate rather than publishing one — but re-running the whole script obscures which half actually succeeded.
7. `git push origin main && git push origin vX.Y.Z` — push the commit and the tag (the tag does not piggyback on the branch push).

You must be logged in with Personal Access Tokens for both publishers. To set up credentials:
### A success message is not a live release

Both CLIs print success as soon as the **upload** is accepted. The version then takes anywhere from ~2 to ~22 minutes to become publicly queryable, and the two registries are independent — either can be first. During that window:

- Open VSX answers `Extension not found` for the new version, and `ovsx publish` run again reports `already published, but currently isn't active and therefore not visible`. That message means *wait*, not *retry* — it has always resolved on its own. It is also proof the upload landed.
- The Marketplace omits the version from gallery queries, so `vsce show` still reports the previous one.

Verify against each registry rather than trusting the CLI output:

```sh
# Open VSX
curl -s https://open-vsx.org/api/gemtalksystems/gemstone-ide | jq -r .version

# VS Code Marketplace
npx @vscode/vsce show gemtalksystems.gemstone-ide
```

### Credentials

Each registry takes a personal access token, tied to your own account rather than to the publisher. Store both once and neither publish step needs anything in the environment:

```sh
npx @vscode/vsce login gemtalksystems # VS Code Marketplace
npx ovsx create-namespace gemtalksystems -p <token> # Open VSX (one-time)
npx ovsx login gemtalksystems # Open VSX
```

`ovsx publish` reads `OVSX_PAT` from the environment (or a stored token).
`vsce` reads `VSCE_PAT` from the environment or its stored login; `ovsx` reads `OVSX_PAT` or its stored token. `npx @vscode/vsce ls-publishers` shows what is stored locally — an empty list means no credentials **on this machine**, not that the publisher is unregistered.

The **VS Code Marketplace** token comes from Azure DevOps (`dev.azure.com` → User settings → Personal Access Tokens). Two settings matter, and both are easy to get wrong:

- **Organization: All accessible organizations** — a token scoped to a single organization fails with a 401 that reads like a bad token.
- **Scopes: Marketplace → Manage** (under "Show all scopes").

Your account must also be a member of the `gemtalksystems` publisher; <https://marketplace.visualstudio.com/manage/publishers/gemtalksystems> loads if it is and 404s if it isn't. Azure DevOps shows a PAT once, at creation, and stores it hashed — a lost token is replaced, never recovered.

The **Open VSX** token comes from <https://open-vsx.org> (sign in with GitHub → user settings → Access Tokens), and your account must belong to the `gemtalksystems` namespace. The namespace itself already exists; `npx ovsx create-namespace gemtalksystems -p <token>` is a one-time step that does not need repeating.