# Releasing dsh-update-status
Releases are built from immutable Git tags and published by GitHub Actions through npm Trusted Publishing (GitHub OIDC). The repository stores **no** `NPM_TOKEN`, npm access token, or OTP. npm packages are immutable: never reuse a version that npm has accepted.
## npm Trusted Publisher
The public package at [npmjs.com/package/dsh-update-status](https://www.npmjs.com/package/dsh-update-status) is bound to this repository:
| npm field | Value |
| --- | --- |
| Provider | GitHub Actions |
| Owner | `idoall` |
| Repository | `dsh-update-status` |
| Workflow filename | `release.yml` |
| Environment | Leave blank |
The workflow path must remain `.github/workflows/release.yml`. The `publish` job receives only a short-lived GitHub OIDC token through `id-token: write`. Do not add an `NPM_TOKEN` GitHub secret.
The first `0.1.0` package was bootstrapped once with a token because npm cannot attach a Trusted Publisher until the package exists. Later versions must use OIDC only.
## What a release tag does
Pushing a tag matching `v*` triggers [`.github/workflows/release.yml`](../.github/workflows/release.yml):
1. Require `vX.Y.Z` to exactly match `package.json`’s `X.Y.Z` version **and** require `docs/releases/vX.Y.Z.md` to exist with both a Chinese and an English section anchor.
2. Install dependencies with a frozen lockfile, run tests, build, and pack exactly one `.tgz` artifact.
3. Upload that artifact and `SHA256SUMS` as a GitHub Actions artifact.
4. Publish that same artifact to npm with Trusted Publishing and the `latest` dist-tag.
5. Create (or update) the GitHub Release with that notes file as its body and attach the tarball and checksum **only after** the publish job succeeds.
If `dsh-update-status@X.Y.Z` already exists on npm, the immutable npm package is left unchanged and the workflow continues safely to the GitHub Release step.
## Release notes are hand-written and bilingual
Every release has exactly one file: `docs/releases/vX.Y.Z.md`. The GitHub Release body **is** that file — the workflow passes it with `--notes-file` and never uses `--generate-notes`, because commit titles do not tell a user what changed for them, what it breaks, or what they must do.
Write it in the layout the DeepSeek Harness release pages use, for example [dsh-v0.1.6-alpha.1](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.6-alpha.1):
```markdown
[中文](#cn-v0.1.3) | [English](#en-v0.1.3)
插件 0.1.3 — 已验证 DeepSeek Harness 0.1.6-alpha.1
### 版本对应
### 问题修复
### 改进
### 兼容性与升级
Plugin 0.1.3 — verified against DeepSeek Harness 0.1.6-alpha.1
### Version matrix
### Bug Fixes
### Improvements
### Compatibility and upgrade
```
Rules:
- The two `` / `` anchors are mandatory and must match the tag: the gate greps for `` and ``.
- Chinese section first, English section second; both must carry the same facts — not a summary of the other language.
- Lead with the version matrix: which plugin version was verified against which DeepSeek Harness release, and which of them are actually on npm.
- Write every entry as *what changed → who is affected → what the user must do*. State the verified DSH release, the exact upgrade command, and any remaining limitation explicitly.
- A never-published version has no release page of its own. When a published version carries an unpublished one's fix, document both in the shipped release's notes (see `v0.1.3`, which covers the unpublished `0.1.2`).
- Keep the same facts in `CHANGELOG.md`; the release notes may be longer and user-facing, but they must not contradict it.
## Before every release
Never release an uncommitted worktree or reuse a published npm version.
1. Write `docs/releases/vX.Y.Z.md` (bilingual, hand-written — see above) **and** the matching `CHANGELOG.md` entry in the same commit.
2. Update the version matrix in `README.md` and `README.zh.md`: the verified DeepSeek Harness release(s), the npm status of every version, and which version a user on which DSH release should install.
3. Bump `package.json` and `dsh.compatibility.dshReleases` together with `VERIFIED_DSH_VERSIONS` in `src/shared/types.ts`.
4. Verify:
```sh
git switch main
git pull --ff-only
git status --short
pnpm install --frozen-lockfile
pnpm run verify
node -p "require('./package.json').version"
```
`git status --short` must be empty, and the printed version must be the intended unpublished version.
For an extra local artifact check:
```sh
rm -rf .tmp/release && mkdir -p .tmp/release
pnpm pack --pack-destination .tmp/release
shasum -a 256 .tmp/release/dsh-update-status-*.tgz
npm pack --dry-run .tmp/release/dsh-update-status-*.tgz
```
Confirm npm does not already own the exact version:
```sh
VERSION="$(node -p "require('./package.json').version")"
npm view "dsh-update-status@$VERSION" version || true
```
## Publish a version
After the matching source commit is on `main`:
```sh
VERSION="$(node -p "require('./package.json').version")"
git tag -a "v$VERSION" -m "dsh-update-status v$VERSION"
git push origin "v$VERSION"
```
Watch the **Release** workflow in GitHub Actions. It publishes npm and creates the GitHub Release; do not run `npm publish` locally for a tag-managed release. Never reuse `0.1.0`.
## Verify the public package
After the workflow succeeds:
```sh
VERSION="$(node -p "require('./package.json').version")"
npm view "dsh-update-status@$VERSION" name version dist-tags repository --json
npm view dsh-update-status dist-tags --json
VERIFY_DIR="$(mktemp -d)"
cd "$VERIFY_DIR"
npm pack "dsh-update-status@$VERSION"
npm install --ignore-scripts "./dsh-update-status-$VERSION.tgz"
node -e "const p=require('./node_modules/dsh-update-status/package.json'); console.log(p.name,p.version,p.dsh?.client?.platform)"
```
The final command must print:
```text
dsh-update-status X.Y.Z web
```
Then install the package in a disposable DSH profile and manually verify the sidebar badge, its three dot states (green when up to date, grey pulse while checking, amber halo when a newer release exists — the chip fill itself must not change), the panel, release-channel selection, cache-duration setting, and mobile drawer behavior:
```sh
TEST_HOME="$(mktemp -d)"
DSH_HOME="$TEST_HOME" dsh plugin --profile web add "dsh-update-status@$VERSION"
DSH_HOME="$TEST_HOME" dsh web
```
Stop the disposable DSH Web process after verification. The plugin remains advisory: it does not install, restart, roll back, download, or replace DSH files.
## Candidate builds without publication
Use **Actions → Build release candidate → Run workflow** to make a downloadable package and checksum without publishing npm or creating a GitHub Release. This is useful for pre-tag DSH UI verification.