The compiler version in Cargo.toml is canonical, and every artifact that carries
the Pyfun version tracks it — one Pyfun version, the same number everywhere a user
can read one. That set is Cargo.toml, editors/vscode/package.json,
editors/jetbrains/build.gradle.kts and editors/emacs/pyfun-mode.el; the wheel and
the Jupyter kernel derive theirs. Two artifacts are deliberately versioned on their
own because they change on their own schedule and a user reads them as a grammar
rather than as a compiler: editors/zed/extension.toml and
editors/tree-sitter-pyfun/tree-sitter.json, each bumped when the grammar changes.
An editor artifact is never left behind because "it didn't change": a user whose
extension says 0.2.0 while pyfun --version says 0.3.0 has no way to tell whether
that is fine or a broken install. Version numbers are cheap; that doubt is not.
- Bump
versionin all four files, to the same number:Cargo.toml(canonical; run any test to refreshCargo.lock)editors/vscode/package.json(+ aCHANGELOG.mdentry — "no client changes" is a fine entry)editors/jetbrains/build.gradle.ktseditors/emacs/pyfun-mode.el(the;; Version:header — MELPA reads it)
- Commit,
git tag vX.Y.Z, push the tag →wheels.ymlpublishespyfun-langto PyPI (Trusted Publishing) and attaches the.vsixto the GitHub release. The attached.vsixis named frompackage.json, not the tag — bumping in step 1 is what keeps the release page coherent. - Publish the editor artifacts (they do not ride the tag):
npx @vscode/vsce packageineditors/vscode/, then VS Code Marketplace: upload the.vsixvia the web UI (https://marketplace.visualstudio.com/manage/publishers/pyfun — the CLI auth path is broken; do not fight it).- Open VSX:
npx ovsx publish <vsix> -p $OPEN_VSX_APIKEY. - JetBrains:
./gradlew publishPluginineditors/jetbrains/(needsJETBRAINS_PERMANENT_TOKENand JDK 21; the wrapper fetches Gradle itself, so nothing has to be installed) — but see the acceptance gate below. The upload then waits on JetBrains moderation, so the plugins API keeps reporting the previous version for a while; that is not a failed publish. Use the wrapper, never a baregradle— Gradle is not installed on the dev machine, and each release that reached for it lost time rediscovering that.
- Verify:
pip install "pyfun-lang[jupyter]==X.Y.Z"in a clean venv, thenpip install -U "pyfun-lang[jupyter]"in the day-to-day Python env so the Jupyter kernel is not stranded on the previous compiler. - Record it: update the Distribution ledger in
ROADMAP.mdwith each registry's verified version and date (check the APIs, not the previous line), in one follow-up PR. It cannot ride the release PR because the ledger records what the tag published, which does not exist until after it. One ledger PR per release; anything a registry does later (moderation clearing, a manual upload) waits for the next substantive change.
Follows automatically — no action: Mason registry (Renovate bumps the pinned
PyPI version), MELPA (builds pyfun-mode.el from main HEAD), nvim-lspconfig
(unversioned), the Jupyter kernel (reads the installed wheel's version).
The grammar is pinned by commit in several places; bump the pins:
editors/zed/extension.toml— updaterev, bump the extensionversion, then PR the new submodule pin tozed-industries/extensions.- Helix — PR the new
revin upstreamlanguages.toml. - nvim-treesitter — PR the new revision in their parser registry.
- Keep
editors/nvim/queries/pyfun/highlights.scmandeditors/zed/languages/pyfun/highlights.scmin sync witheditors/tree-sitter-pyfun/queries/highlights.scm.
Gate on acceptance. Steps 1–3 above, and the JetBrains publish in "Every release", apply only to registries whose initial submission has been accepted — check the ROADMAP Distribution ledger for current status before opening any follow-up PR or pushing an update. Never send version/rev bumps to a registry whose first submission is still in review (it reads as pestering) or was declined (nvim-lspconfig and Mason are deferred until Pyfun has adoption evidence; nvim-treesitter upstream is archived with no successor). Bumping the version in the repo is never gated — only publishing is, so the in-repo number stays true to the release even when a registry is not ready for it. One-time PR blockers already handled: Zed's CLA is signed; MELPA wanted the
Assisted-by:header that's now inpyfun-mode.el.
Tokens live in the gitignored editors/.env (OPEN_VSX_APIKEY,
JETBRAINS_PERMANENT_TOKEN). A tag is irreversible — versions on PyPI cannot
be reused after a yank.