| name | release |
| description | Use when releasing, shipping, or cutting a new version of Yesterlog — bumping the version, publishing a build, tagging a release, or cutting a hotfix for 4gray/yesterlog. Triggers on "release", "ship a version", "cut a release", "publish a build", "hotfix". |
| argument-hint | [patch|minor|major] |
Release Yesterlog
End-to-end release flow for the public 4gray/yesterlog Electron + Vite + React app (npm package and Snap name yesterlog). Pushing a vX.Y.Z tag triggers .github/workflows/release.yml, which tests, builds and code-signs/notarizes macOS, builds Windows/Linux, creates a DRAFT GitHub Release, and uploads the Snap to edge. You then verify every artifact, curate the notes, promote the verified Snap revision, and hand the user the draft URL. Do not publish the GitHub Release unless the user explicitly asks.
The bump type comes from $ARGUMENTS ($1): patch, minor, or major. Default to patch when none is given.
Procedure
1. Preconditions
2. Commit the change
Commit the real fix/feature with a conventional-commit subject and a body explaining the why:
git add -A
git commit -m "fix: <subject>" -m "<why this change>"
Verify the tree is now clean (must print nothing):
git status --porcelain
2b. Refresh release screenshots (feature releases)
If the release changes a primary view, refresh its screenshot for the notes/docs before bumping — the tag must include it, and GitHub Pages (main:/docs, public) serves it for the notes. Capture with the demo seed:
npm run screenshots -- --seed release --today 2026-06-17 --views today --out docs/screenshots/v<NEW>
- Screenshots are auto-compressed to a palette PNG by
scripts/capture-screenshots.mjs (a full-view 1440×1000 shot is ~30 KB instead of ~120 KB), so committing them to docs/ stays cheap. Do not hand-optimize.
- Commit the PNG(s) under
docs/screenshots/v<NEW>/. If you refreshed the whole set, also bump the screenshots/v…/ paths in README.md and docs/index.html so they don't point at the old version.
- Reference the shot in the notes (step 7) via its public Pages URL — it renders inline:
https://4gray.github.io/yesterlog/screenshots/v<NEW>/<theme>-<view>.png (e.g. .../v2.1.0/dark-today.png). Confirm it's live with curl -sI <url> (expect 200) once main is pushed.
3. Bump version + create the tag
Run the project script for the chosen bump type (default patch):
npm run release:patch
Each expands to npm version <type> -m "chore(release): v%s", which in one step:
- updates
"version" in package.json and package-lock.json,
- creates a
chore(release): vX.Y.Z commit, and
- creates the annotated tag
vX.Y.Z.
Capture the new version and tag for later steps (read it from package.json — don't hardcode a version, it drifts every release):
NEW=$(node -p "require('./package.json').version")
TAG="v$NEW"
echo "$TAG"
4. Push the commit and the tag
npm run release:push
The tag push (vX.Y.Z, matching on: push: tags: "v*.*.*") is what triggers the Release workflow.
5. Find the pipeline run, then watch it in the BACKGROUND
Resolve the run id for this tag (the workflow runs against the tag ref):
RUN_ID=$(gh run list --workflow=release.yml --branch "$TAG" --limit 1 --json databaseId -q '.[0].databaseId')
echo "$RUN_ID"
Watch it to completion in the background (the build takes ~5-6 min) so you're notified when it finishes:
gh run watch "$RUN_ID" --exit-status --interval 20
Run that gh run watch command with run_in_background: true.
6. What the pipeline does
Jobs run in order, all from the tagged commit:
test (ubuntu-latest) — npm ci, npm run test, npx playwright install --with-deps chromium, npm run e2e:renderer, npm run build.
build (needs: test, matrix, fail-fast: false) — three platforms in parallel:
- macOS (
macos-latest, artifact yesterlog-macos): runs npm run dist:mac -- -c.mac.forceCodeSigning=true with code-signing + notarization (the mac build config has notarize: true and hardenedRuntime: true). Produces release/*.dmg, release/*.zip.
- Windows (
windows-latest, artifact yesterlog-windows): npm run dist:win, unsigned. Produces release/*.exe, release/*.zip.
- Linux (
ubuntu-latest, artifact yesterlog-linux): npm run dist:linux, unsigned. Produces release/*.AppImage, release/*.deb, release/*.tar.gz.
snap (needs: test, ubuntu-latest) — builds yesterlog_<version>_amd64.snap and uploads the workflow artifact yesterlog-snap.
publish_snap (needs: snap, ubuntu-latest, release tags only) — uploads the exact Snap artifact to Snapcraft edge when SNAP_STORE_PUBLISH_ENABLED is enabled.
publish (needs: [build, snap], ubuntu-latest, only when startsWith(github.ref, 'refs/tags/v')) — downloads all artifacts into release-assets/, then creates the release if it doesn't exist, or gh release upload --clobber if it does:
gh release create "$TAG_NAME" "${assets[@]}" \
--draft \
--title \
--generate-notes \
--verify-tag
The result is a DRAFT GitHub Release titled vX.Y.Z with .dmg/.zip/.exe/.AppImage/.deb/.tar.gz attached and auto-generated notes.
7. Confirm the draft and curate the release notes
After the run succeeds, confirm the draft exists:
gh release view "$TAG" --json isDraft,url,assets -q '{isDraft, url, assets: [.assets[].name]}'
Replace the auto-generated notes with a curated, user-facing changelog. Determine the previous tag for the compare link:
PREV=$(git tag --sort=-v:refname | grep -v "^$TAG$" | head -1)
Write /tmp/notes.md (group changes under headings like Highlights, Fixes, Improvements — describe user-visible impact, not raw commit subjects). For a feature release, embed the screenshot captured in step 2b via its public Pages URL (it renders inline; a committed private-repo raw/asset URL would NOT). ALWAYS end with the compare link:
## What's new in vX.Y.Z
### Highlights
- ...

### Fixes
- ...
**Full changelog:** https://github.com/4gray/yesterlog/compare/vPREV...vX.Y.Z
Apply the notes to the draft (this does not publish it):
gh release edit "$TAG" --title "$TAG" --notes-file /tmp/notes.md
8. Verify and promote the Snap revision
On Ubuntu, install the exact edge revision produced by the workflow and verify startup, the clean first-run profile, Jira/network access, browser links, notifications, and desktop integration:
snap info yesterlog
sudo snap install yesterlog --edge
sudo snap refresh yesterlog --edge
After verification, promote that same revision without rebuilding it:
snapcraft promote yesterlog --from-channel=edge --to-channel=candidate
snapcraft promote yesterlog --from-channel=candidate --to-channel=stable
snap info yesterlog
Never release a different revision to candidate or stable. For the first Yesterlog release, confirm the public stable listing before making the legacy Snap private.
9. Hand the URL to the user — leave it as a DRAFT
Give the user the draft release URL and tell them it's ready for review:
gh release view "$TAG" --json url -q .url
Stop here. The release stays a DRAFT for the user to review and Publish themselves. Only publish when the user explicitly asks, with:
gh release edit "$TAG" --draft=false
Gotchas
- The shipped version comes from the BUILD, not from any runtime string. In the renderer,
import.meta.env.VITE_APP_VERSION is injected at build time by a Vite define in vite.config.ts ("import.meta.env.VITE_APP_VERSION": JSON.stringify(packageJson.version)), read in src/App.tsx and src/api/native.ts. In the Electron main process, app.getVersion() (electron/main.ts) reads the bundled package.json. CI builds from the tagged commit, so the version bump must be committed + tagged BEFORE the build — which is exactly why the tag push is the trigger. Never hand-edit a version string anywhere else; bump only via npm version (step 3).
- Clean tree is mandatory.
npm version (inside release:patch/minor/major) refuses a dirty working tree and aborts. Commit your real change first (step 2).
- A local
npm run dist:mac is UNSIGNED (it runs with --publish never and no signing secrets) and is stamped with whatever package.json version exists at that moment. Use it only for quick personal testing — never as the release artifact. The signed/notarized macOS build only happens in CI via repo secrets (MAC_CSC_LINK, MAC_CSC_KEY_PASSWORD, APPLE_API_KEY_BASE64, APPLE_API_KEY_ID, APPLE_API_ISSUER, APPLE_TEAM_ID). If any macOS signing secret is missing, the macOS build job fails fast at the "Check macOS signing secrets" step; Windows and Linux need no signing.
- The tag pattern must be semver
vX.Y.Z. Only v*.*.* triggers the release on push. A manual workflow_dispatch run executes test + build but skips publish (gated on startsWith(github.ref, 'refs/tags/v')), so no release is created from a branch dispatch.
- The publish job is idempotent. Re-running it when the release already exists uses
gh release upload --clobber rather than recreating it, so re-runs overwrite assets instead of duplicating the release.
- Identity is consistent: GitHub repo
4gray/yesterlog, npm package , Snap , electron-builder , and Pages .