From 67c9cd0622d48466bc1440fbff14f2c963e491e7 Mon Sep 17 00:00:00 2001 From: James Foster Date: Thu, 27 Aug 2026 14:36:26 -0700 Subject: [PATCH] Correct and expand the release instructions in CONTRIBUTING.md Findings from cutting 1.8.11 through 1.8.14: - npm version also reformats package.json in npm's own JSON style, and format:check does not cover package.json, so the reformat would land in the release commit unnoticed. Document the restore. - ovsx refuses a duplicate rather than double-publishing; the message is "already published, but currently isn't active", not "already exists". The advice to re-run vsce directly stands, but for a different reason. - A CLI success message means the upload was accepted, not that the version is live; it takes ~2-22 minutes and either registry can lead. Add how to verify against each registry's API. - Step 5's .vsix is not the artifact that gets uploaded; both publish commands repackage into their own temp file. - Step 2 was narrower than the gate every PR is held to. - Note that a branch cut before the last Unreleased -> X.Y.Z rename lands its changelog bullet in the already-released section. Also documents ovsx login, verify-pat as a pre-flight, and the Azure PAT requirements (all accessible organizations + Marketplace > Manage). Co-Authored-By: Claude Opus 5 (1M context) --- CONTRIBUTING.md | 60 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad444828..e4135107 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -131,19 +131,65 @@ CI runs on **GitHub Actions**. The [Health Check workflow](.github/workflows/hea ## Publishing a release -1. `npm version --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 --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 --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 + ``` + + 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. + +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: `). 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. +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 # 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; 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 (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 ` is a one-time step that does not need repeating.