mirror of
https://github.com/bitsofchris/openaugi-obsidian-plugin.git
synced 2026-07-22 05:46:42 +00:00
Codify the release process so we stop drifting version files and stop leaving the repo advertising a version with no published release (the inconsistency that plausibly got the plugin auto-removed from the store). - scripts/release.sh: one-shot, self-verifying release. Bumps manifest.json, package.json AND versions.json in lockstep, runs tests+build, pushes, tags (no v prefix), waits for CI, PUBLISHES the draft immediately, then verifies root manifest == released asset == tag. Includes a store-listing health check. - tests/version-consistency.test.ts: guard test — fails the build if the three version files ever disagree (catches a forgotten versions.json bump before tagging). - docs/PUBLISHING.md: rewritten script-first; adds versions.json (was omitted), the publish-immediately rule, and a 'if de-listed from the store' runbook (ask #plugin-dev, community.obsidian.md portal, the 'entry already exists' gotcha). - CLAUDE.md: quick-summary now points at the script and the three files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
170 lines
7.7 KiB
Markdown
170 lines
7.7 KiB
Markdown
# Publishing a New Release
|
||
|
||
**name:** publish-release
|
||
**description:** How to cut and publish a new OpenAugi plugin version safely, keep it consistent with the Obsidian community store, and what to do if the plugin gets de-listed.
|
||
**when to use:** Any time you ship a new version of the plugin (features, fixes) or need to relist it in the community store.
|
||
|
||
---
|
||
|
||
## TL;DR — use the script
|
||
|
||
```bash
|
||
# 1. Write the release notes first (they become the GitHub release body):
|
||
# docs/release-notes/X.Y.Z.md
|
||
# 2. Then run:
|
||
./scripts/release.sh X.Y.Z
|
||
```
|
||
|
||
`release.sh` does the whole thing and **verifies it**: checks preconditions →
|
||
runs tests + build → bumps `manifest.json`, `package.json`, **and
|
||
`versions.json`** in lockstep → commits, pushes, tags → waits for CI → **publishes
|
||
the draft immediately** → verifies that the repo-root manifest, the released
|
||
manifest asset, and the tag all equal `X.Y.Z`. It stops loudly on any mismatch.
|
||
|
||
Everything below explains *why* it does what it does, and the manual fallback.
|
||
|
||
## The three things Obsidian actually checks
|
||
|
||
1. **`manifest.json` at the repo-root HEAD** — Obsidian reads only the `version`
|
||
field here to decide "what's the latest version."
|
||
2. **A GitHub release whose tag == that version** (no `v` prefix), with
|
||
`main.js`, `manifest.json`, and `styles.css` attached as assets. Obsidian
|
||
downloads the files from the *release*, not from the repo tree.
|
||
3. **`versions.json`** — maps each plugin version → minimum Obsidian version.
|
||
Used to decide whether a given user's Obsidian is new enough to be offered the
|
||
update.
|
||
|
||
If the root manifest advertises a version that has **no matching published
|
||
release**, the plugin is in an inconsistent state — and Obsidian's automated
|
||
validation can drop it from the store. Never leave that window open. (This is
|
||
what bit us on 0.6.0.)
|
||
|
||
## Version files — all THREE must move together
|
||
|
||
| File | Field | Notes |
|
||
|------|-------|-------|
|
||
| `manifest.json` | `version` | source of truth for the release tag |
|
||
| `package.json` | `version` | keep in sync (npm/build hygiene) |
|
||
| `versions.json` | add `"X.Y.Z": "<minAppVersion>"` | **easy to forget — it's why updates/relisting break** |
|
||
|
||
The `tests/version-consistency.test.ts` guard test fails the build if these ever
|
||
disagree. `npm test` therefore catches a forgotten `versions.json` bump before
|
||
you tag.
|
||
|
||
> Historical note: earlier versions of this doc listed only `manifest.json` and
|
||
> `package.json`. `versions.json` was missing entirely, which broke the
|
||
> `npm version` script and left the store metadata incomplete. Don't regress.
|
||
|
||
## Pre-release checklist
|
||
|
||
- [ ] Features/fixes merged to master; `git status` clean; local == `origin/master`
|
||
- [ ] `npm test` passes (includes the version-consistency guard) — see [TESTING.md](TESTING.md)
|
||
- [ ] `npm run build` clean (tsc + esbuild)
|
||
- [ ] Manually tested in a real vault (`npm run dev`, reload plugin, exercise the change)
|
||
- [ ] `docs/CODEBASE_MAP.md` / `README.md` updated for any behavior change
|
||
- [ ] Release notes written to `docs/release-notes/X.Y.Z.md`
|
||
|
||
## Release notes format
|
||
|
||
Write `docs/release-notes/X.Y.Z.md` before releasing. Claude: diff commits since
|
||
the previous tag and focus on user-facing changes.
|
||
|
||
```markdown
|
||
# X.Y.Z — <one-line theme>
|
||
|
||
## Added / Changed / Fixed / Deprecated
|
||
- <user-facing change>: <why the user cares>
|
||
|
||
## Notes
|
||
- <breaking changes or upgrade steps, if any>
|
||
```
|
||
|
||
## Manual fallback (if you can't use the script)
|
||
|
||
Only if `release.sh` can't run. Do the steps **in this order** and don't stop
|
||
before publishing:
|
||
|
||
```bash
|
||
# 1. Bump ALL THREE (or run: npm version X.Y.Z — see note below)
|
||
# manifest.json .version, package.json .version, versions.json add "X.Y.Z":"<minAppVersion>"
|
||
npm test && npm run build # guard test must be green
|
||
|
||
git add manifest.json package.json versions.json docs/release-notes/X.Y.Z.md
|
||
git commit -m "Release X.Y.Z"
|
||
git push origin master
|
||
|
||
git tag -a X.Y.Z -m "X.Y.Z" # annotated, no 'v' prefix
|
||
git push origin X.Y.Z # triggers .github/workflows/release.yml → draft
|
||
|
||
# 2. Wait for the workflow, then PUBLISH RIGHT AWAY (don't leave it a draft):
|
||
gh run watch "$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')" --exit-status
|
||
gh release edit X.Y.Z --draft=false --latest --notes-file docs/release-notes/X.Y.Z.md
|
||
|
||
# 3. Verify root manifest, released asset, and tag all say X.Y.Z:
|
||
curl -sL https://raw.githubusercontent.com/bitsofchris/openaugi-obsidian-plugin/master/manifest.json | grep version
|
||
curl -sL https://github.com/bitsofchris/openaugi-obsidian-plugin/releases/download/X.Y.Z/manifest.json | grep version
|
||
```
|
||
|
||
> `npm version X.Y.Z` also works: `.npmrc` sets `tag-version-prefix=""` (so the
|
||
> tag has no `v`), and the `version` npm script runs `version-bump.mjs` which
|
||
> updates `manifest.json` + `versions.json`, while npm bumps `package.json`. It
|
||
> commits and tags for you — but it does **not** push or publish, so you still
|
||
> owe steps 1–3 above (push, wait, publish, verify). The script does all of it.
|
||
|
||
## Community-store listing health
|
||
|
||
The plugin is listed in Obsidian's store via
|
||
[`obsidianmd/obsidian-releases`](https://github.com/obsidianmd/obsidian-releases).
|
||
That repo's `community-plugins.json` is now an **automated mirror** of Obsidian's
|
||
internal list (commits read `chore: Mirror community plugins and themes`), so you
|
||
can't fix listing by PR'ing that file — it gets overwritten.
|
||
|
||
Check whether we're currently listed:
|
||
|
||
```bash
|
||
curl -sL https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/community-plugins.json | grep -i openaugi
|
||
```
|
||
|
||
- **Listed** → core Settings → Community plugins → "Check for updates" works for users.
|
||
- **Not listed** → core update check can't see us; users must use **BRAT** or a
|
||
manual install. See the relisting steps below.
|
||
|
||
### If the plugin gets removed from the store
|
||
|
||
This happened on **2026-07-07** (removed in mirror commit `85db6f23`, bundled with
|
||
several unrelated plugins — an automated batch removal). The mirror commits carry
|
||
no per-plugin reason and Obsidian doesn't file an issue on your repo, so:
|
||
|
||
1. **Fix consistency first** (so a resubmit passes validation): run a clean
|
||
release with `release.sh` — root manifest version must equal a *published*
|
||
release with the three assets, `versions.json` present, `README`/`LICENSE`
|
||
present. (All true as of 0.6.0.)
|
||
2. **Ask Obsidian why + request reinstatement** — don't blindly resubmit an
|
||
auto-removal. Post in the Obsidian Discord **`#plugin-dev`** channel (fastest;
|
||
plugin-review team is active there) or the forum **Developers: Plugin & API**
|
||
category. Cite: repo URL, removal date `2026-07-07`, mirror commit `85db6f23`.
|
||
3. **If they tell you to resubmit:** the current path is the web portal at
|
||
**community.obsidian.md** → sign in with your Obsidian account → link GitHub →
|
||
Plugins → New plugin → enter the repo URL. (The old "PR to
|
||
community-plugins.json" flow is dead now that the file is bot-mirrored.)
|
||
4. **Gotcha — "An entry already exists for this repository":** because the old
|
||
entry is archived rather than deleted, resubmitting the same repo URL can be
|
||
rejected. Ask a moderator in `#plugin-dev` to delete the archived entry, then
|
||
resubmit.
|
||
|
||
Sources: Obsidian plugin submission docs; obsidian-releases repo (mirror
|
||
architecture); forum thread "Cannot resubmit plugin after archiving: 'An entry
|
||
already exists for this repository'".
|
||
|
||
## Troubleshooting
|
||
|
||
**Tag already exists / wrong version tagged**
|
||
```bash
|
||
git tag -d X.Y.Z
|
||
git push origin --delete X.Y.Z # if already pushed
|
||
# fix versions, re-run ./scripts/release.sh X.Y.Z
|
||
```
|
||
|
||
**Workflow failed** — `gh run view <id>`, fix, delete the tag, re-run the script.
|
||
|
||
**Release stuck as draft** — `gh release edit X.Y.Z --draft=false --latest`.
|