| name | release |
| description | How to cut and publish a release of node-telegram-bot-api (version bump, CHANGELOG, PR to master, npm publish, git tag, GitHub Release, optional Telegram announcement). Use whenever asked to release, publish a new version, bump the version, or "do the X.Y.Z release". Covers the exact step order, the CHANGELOG entry convention, the sanity gate, and the credential gotchas (read-only NPM_TOKEN, missing gh CLI, no create-release MCP tool). v2 uses bun.lock (no package-lock.json). |
Releasing node-telegram-bot-api
End-to-end release flow. Replace X.Y.Z with the target version (example below uses 2.0.0).
Releases ship from master; work happens on a short-lived version/X.Y.Z branch.
v2 notes. The lockfile is bun.lock (the v1 redesign dropped package-lock.json).
bun.lock pins dependencies only, not the root package version, so a version bump
changes package.json and nothing else - there is no lockfile to regenerate or commit.
A prerelease like -alpha.1 / -rc.0 publishes under a non-latest tag (see §8).
0. Orient first (don't assume)
git status
node -p "require('./package.json').version"
npm view node-telegram-bot-api dist-tags
Decide the new version and whether it should become npm latest (a stable release does;
a prerelease like -rc.0 should publish under a different tag with --tag next).
1. Branch
git checkout -b version/X.Y.Z
2. README + CHANGELOG
-
README rarely needs changes (the version comes from an npm badge, not hard-coded). Only
touch it if a documented fact changed.
-
CHANGELOG.md must have an entry that matches the existing convention exactly. Every
released version uses:
## [X.Y.Z][X.Y.Z] - YYYY-MM-DD
with a matching link-def at the very bottom of the file:
[X.Y.Z]:https://github.com/yagop/node-telegram-bot-api/releases/tag/vX.Y.Z
and the [Unreleased] compare link bumped to the new tag:
[Unreleased]:https://github.com/yagop/node-telegram-bot-api/compare/vX.Y.Z...master
Use the actual release date (today), - (ASCII hyphen, not an em dash), and keep an empty
## [Unreleased][Unreleased] section at the top.
v2 has no separate doc build. The published API surface is the generated type defs in
src/types/schemas.ts (regenerated by update-bot-api, not as part of a release), so there
is no doc/api.md or generate:docs step here.
3. Bump version (package.json only, no git tag yet)
npm version X.Y.Z --no-git-tag-version
This updates package.json only. Verify bun.lock is untouched:
node -p "require('./package.json').version"
git diff --exit-code bun.lock
4. Sanity gate (must pass before publishing)
npm run check
npm run build
dist/ is gitignored (built on publish via prepublishOnly), so it is NOT committed.
CI additionally runs the unit suite on the Node 22/24/26 matrix - see the run-tests skill.
5. Commit + push
Only two files change: CHANGELOG.md and package.json. The repo's release commits use the
bare version as the message:
git add CHANGELOG.md package.json
git commit -m "X.Y.Z"
git push -u origin version/X.Y.Z
6. PR to master, wait for green CI, merge
gh may not be available here, and the GitHub MCP toolset for this environment is read/list-only. Create/merge the PR via the GitHub web UI (or the GitHub REST API if you have a GITHUB_TOKEN).
- Open a PR head=
version/X.Y.Z base=master, title X.Y.Z.
- Wait for all checks to pass. Expect: Typecheck (src + test + examples), Unit tests (Node
22/24/26 + Bun), Build (dist); AppVeyor runs the same unit suite on Windows (Node 20/22);
Integration is skipped on PRs (no secrets).
- Merge the PR (merge commit).
7. Pull master
git checkout master && git pull origin master
node -p "require('./package.json').version"
8. npm publish
Auth from NPM_TOKEN (env). Write it without printing the value, dry-run first, then publish:
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$HOME/.npmrc"
npm whoami
npm publish --dry-run
npm publish
npm view node-telegram-bot-api version
⚠️ Read-only-token gotcha. npm whoami succeeding does NOT mean the token can publish.
If npm publish returns E403 ... You may not perform that action with these credentials
(and npm profile get also 403s), the token is read-only / too narrowly scoped. A 403 is a
clean failure - nothing was uploaded. Fix: use an npm Automation token, or a Granular
token with Read and write that includes this package. If you can't get one, the maintainer
publishes manually with their OTP: ! cd /workspace && npm publish.
9. Tag the release
The CHANGELOG link-def points at releases/tag/vX.Y.Z, and every prior release has a matching
annotated tag. Tag the released master HEAD and push:
git tag -a vX.Y.Z -m "X.Y.Z" $(git rev-parse master)
git push origin vX.Y.Z
10. GitHub Release
There is no create-release path via local tooling: gh isn't installed, and the GitHub MCP
server only exposes read-only release tools (get_latest_release, get_release_by_tag,
list_releases) - no create/update. Two options:
- REST API (needs a token). If
GITHUB_TOKEN is in the session env (a PAT with repo, or
fine-grained with Contents: write), POST to the releases endpoint. Build the JSON body from
a notes file so newlines/quotes are escaped safely (see snippet below). Note: setting the var
in a separate terminal won't reach the session - it must be injected the same way NPM_TOKEN
is. Body = the CHANGELOG X.Y.Z section verbatim (it's self-contained, no reference links),
with a compare/<prev>...vX.Y.Z line appended. Use make_latest: "true", prerelease: false.
- Web UI (no token).
https://github.com/yagop/node-telegram-bot-api/releases/new?tag=vX.Y.Z
-> title X.Y.Z, paste the notes, check Set as the latest release.
REST snippet (run with node, token from env, never printed):
import { readFile } from "node:fs/promises";
const token = process.env.GITHUB_TOKEN;
if (!token) { console.error("GITHUB_TOKEN not set"); process.exit(2); }
const body = await readFile(new URL("./RELEASE_NOTES.md", import.meta.url), "utf8");
const res = await fetch("https://api.github.com/repos/yagop/node-telegram-bot-api/releases", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "ntba-release-script",
},
body: JSON.stringify({ tag_name: "vX.Y.Z", name: "X.Y.Z", body, make_latest: "true", prerelease: false }),
});
console.log(res.status, (await res.json()).html_url ?? "");
11. (Optional) Announce on Telegram
Draft a short channel post (emojis, Telegram markdown *bold* / inline `code`) with the
few most relevant changes + the migration link + npm i node-telegram-bot-api. Keep it short.
Done-checklist