Skip to content
Achievement Watcher Next

Commit and release workflow

This is the canonical checklist for Shirowwww/Achievement-Watcher-Next. Other project documents link here instead of duplicating the release process.

On this page

Commit rules

  1. Start with git status --short --branch and preserve unrelated changes.
  2. Split work by concern. Code, tests and the matching CHANGELOG.md entry may stay together; unrelated docs, dependency refreshes and CI fixes get separate commits.
  3. Stage explicit paths, inspect git diff --cached, then commit.
  4. Use short English Conventional Commit subjects, for example:
    • fix: sync dependency lockfile
    • feat: add controller UI navigation
    • docs: clarify notification setup
    • chore: update runtime and dependencies
  5. Do not add generated-by or unrelated co-authoring attribution to commits or public-facing text.
  6. Never rewrite already-pushed history unless the user explicitly requests it.

Release preparation

Use a new SemVer version. A published version is immutable: never replace its installer to force an update, because clients only update to a higher version.

Update the same version in all of these places:

  • app/package.json
  • app/package-lock.json (root package entries)
  • watchdog/package.json
  • watchdog/package-lock.json (root package entries)
  • CHANGELOG.md: move relevant Unreleased entries under the dated version
  • RELEASE_NOTES.md: title, highlights and installer filename

The README release badge is dynamic and does not require a manual version edit. Do not hand-edit generated app/dist/latest.yml or latest-linux.yml; the builds create them.

A release is one version for both systems: the Windows installer and the Linux AppImage are built from the same commit and published together.

Regenerate the styled site changelog and release discovery files after dating the changelog entry:

node tools/site/changelog.js
node tools/site/discovery.js

Commit docs/changelog.md, docs/releases.xml and docs/sitemap.xml with the release changes. The site workflow rejects them when they drift from CHANGELOG.md or the public guide inventory.

Documentation accuracy check

Do this for every release, not only ones that “feel” doc-worthy - stale docs accumulate silently between releases otherwise:

  • README.md “What it does” list: add a phrase for any genuinely new capability this release ships (a new source, a new tool, a reliability/data-safety change). Bug fixes belong in CHANGELOG.md, not this list. If nothing list-worthy shipped, leave it alone.
  • docs/comparison.md: only touch a row if this release changed the behavior it describes. Do not re-verify the whole table on every release - that is a separate, occasional audit (see below), not a per-release step.
  • Stale absolute paths: if this release changed a user-data path, filename or directory (the 3.5.3 %APPDATA%\Achievement Watcher 3.0 split from the legacy %APPDATA%\Achievement Watcher is the precedent), grep the whole repo for the old form - public docs, .github/ISSUE_TEMPLATE/, and the local CLAUDE.md/AGENTS.md notes all drift independently and have shipped stale paths before.
  • Broken doc links: confirm every [text](path#anchor) added or touched this release actually resolves (a moved/renamed file or a renamed heading breaks the anchor silently). A quick script that resolves every relative link in every tracked .md file against the filesystem is cheap insurance - see git history around 2026-08-05 for one.

Occasional: re-verifying the comparison table against competing projects

Do this only when asked, or when a competing project’s fork clearly changed - not every release. When you do it: check the competitor’s actual source (main.js/preload.js/renderer HTML, fetched via gh api repos/<owner>/<repo>/contents/<path> -H "Accept: application/vnd.github.raw"), not just their README. A README is a marketing summary and routinely under-describes real features. A missing README mention is not evidence of a missing feature - grep the code before downgrading a ✅ to a ❌.

Clean validation

From the repository root in PowerShell:

git diff --check

Push-Location app
npm ci
npm test
node ../tools/audit.js
Pop-Location

Push-Location watchdog
npm ci
npm test
node ../tools/audit.js
Pop-Location

tools/audit.js is npm audit --omit=dev --audit-level=high with a short list of advisories accepted by id, each with its reason; an accepted one blocks again once its package has a fix.

If npm ci skipped install scripts, npm run build stops on a missing registry-js addon (3.12.0 shipped without it and scanned libraries through reg.exe). Run npm rebuild registry-js --foreground-scripts in app/ and build again.

The app suite includes locale completeness. If a native optional dependency is unavailable on the current machine, record the exact limitation; do not silently skip a failed check.

Before release, confirm the four package and lockfile root versions match:

node -e "for (const p of ['app/package.json','app/package-lock.json','watchdog/package.json','watchdog/package-lock.json']) console.log(p, require('./' + p).version)"

Build and artifact checks

Build on Windows from app/:

Push-Location app
npm run build
Pop-Location

Expected files:

  • app/dist/Achievement.Watcher.Setup.<version>.exe
  • app/dist/Achievement.Watcher.Setup.<version>.exe.blockmap
  • app/dist/Achievement.Watcher.Portable.<version>.zip
  • app/dist/latest.yml

Check that latest.yml names the exact installer and version. Verify its SHA-512 against the built installer:

$version = (Get-Content app/package.json | ConvertFrom-Json).version
$installer = "app/dist/Achievement.Watcher.Setup.$version.exe"
$expected = [Convert]::ToBase64String(
  [Security.Cryptography.SHA512]::HashData([IO.File]::ReadAllBytes($installer))
)
Get-Content app/dist/latest.yml
$expected

The documentation points readers at a VirusTotal report for the current installer, keyed on its SHA-256. Print the new one and update every place that names it, so the link does not keep pointing at the previous release:

Get-FileHash "app/dist/Achievement.Watcher.Setup.$version.exe" -Algorithm SHA256

Upload the installer to VirusTotal once so the report exists: looking a hash up never creates one, so a link to a file nobody submitted is a dead link. Then replace the previous hash everywhere it appears. It is in README.md, docs/faq.md, docs/troubleshooting.md, docs/index.html and the nine docs/assets/i18n/*.json files, which repeat the site sentence in each language. A plain search for the old hash across the repository finds them all; the version number beside it only changes in the first three.

Also smoke-test the packaged runtime and the affected feature path. For runtime inspection, temporarily use Electron as Node and always remove the variable:

$env:ELECTRON_RUN_AS_NODE = '1'
& 'app/dist/win-unpacked/Achievement Watcher.exe' -e "console.log(process.versions)"
Remove-Item Env:ELECTRON_RUN_AS_NODE

The build runs npm prune --omit=dev in watchdog/. Restore its development dependencies afterward with npm install from watchdog/.

Linux AppImage

The AppImage is built on Linux, from the same commit as the installer. Either run the manual Linux build workflow (.github/workflows/build-linux.yml) on that commit and download its linux-appimage artifact, or build on a Linux machine:

(cd watchdog && npm ci --ignore-scripts)
(cd app && npm ci --ignore-scripts && npx patch-package && npm run build:linux)

Expected files:

  • app/dist/Achievement.Watcher.<version>.AppImage
  • app/dist/latest-linux.yml

build/build-linux.js fails the build unless the AppImage is executable, its embedded app-update.yml points at Shirowwww/Achievement-Watcher-Next, and latest-linux.yml carries this version and the AppImage’s SHA-512. Then start the AppImage once on Linux and check the affected feature path. It needs FUSE 2 (libfuse2, libfuse2t64 on Ubuntu 24.04).

The AppImage is not code-signed: an update is trusted through HTTPS and the SHA-512 in latest-linux.yml.

Push, CI and GitHub release

  1. Ensure commits are atomic and the worktree contains no accidental files.
  2. Push main to origin and wait for .github/workflows/test.yml to pass, the Linux job included.
  3. Create the GitHub release only after CI succeeds, uploading the Windows updater assets, the portable archive, and the AppImage with latest-linux.yml:
$version = (Get-Content app/package.json | ConvertFrom-Json).version
$target = git rev-parse HEAD
gh release create "v$version" `
  "app/dist/Achievement.Watcher.Setup.$version.exe" `
  "app/dist/Achievement.Watcher.Setup.$version.exe.blockmap" `
  "app/dist/Achievement.Watcher.Portable.$version.zip" `
  "app/dist/latest.yml" `
  "app/dist/Achievement.Watcher.$version.AppImage" `
  "app/dist/latest-linux.yml" `
  --repo Shirowwww/Achievement-Watcher-Next `
  --target $target `
  --title "Achievement Watcher Next $version" `
  --notes-file RELEASE_NOTES.md
  1. Verify the release page exposes the installer, portable ZIP, blockmap, latest.yml, the AppImage and latest-linux.yml, and that both public manifests are downloadable. Once an AppImage has been published, every later release must carry latest-linux.yml: without it, each installed AppImage reports an error on every update check.
  2. Refresh the version the website prints under its download buttons, and commit it:
node tools/site/release-data.js
git add docs/data/release.json
git commit -m "docs: publish <version> on the site"

The file is read from the site’s own origin rather than from the GitHub API, so a visitor never spends an unauthenticated rate limit to see a version number. Missing or stale, the pages fall back to their own wording; they never show a wrong version.

Identifiers that must not change

The product was renamed to Achievement Watcher Next in 3.9.0, but several identifiers deliberately kept their historical value because an existing install is keyed on them. Changing any of these breaks upgrades rather than merely renaming something, and test/core/branding.test.js pins them:

Identifier Value Why it stays
appId (AppUserModelID) io.github.shirowwww.achievement.watcher Windows matches toasts and taskbar pins against it; it also names the autostart registry value, which the uninstaller removes by that name
executableName Achievement Watcher Fixes the .exe name, install directory and uninstaller filename; the autostart registry value stores that full path, and the Watchdog spawns the app by it
app.setName() Achievement Watcher Names the main log file
Installer artifact Achievement.Watcher.Setup.<version>.exe Referenced by latest.yml; published releases are immutable
updaterCacheDirName achievement-watcher-updater Existing partially downloaded updates live there
Linux executableName achievement-watcher The Watchdog starts the app by that name on Linux
AppImage artifact Achievement.Watcher.<version>.AppImage Referenced by latest-linux.yml and named by the site
Legacy data folders Achievement Watcher 3.0, Achievement Watcher Import sources for the one-way migration; never renamed or deleted

The repository rename

The repository was renamed Achievement-Watcher-3.0 → Achievement-Watcher-Next for 3.9.0. Clients released before 3.9.0 shipped an app-update.yml naming the old repository, so they reach new releases only through GitHub’s permanent rename redirect. That redirect was verified end to end against electron-updater’s own HTTP stack - the releases.atom feed, the latest.yml channel file and the installer asset all follow the 301 transparently.

Never create a new repository at Shirowwww/Achievement-Watcher-3.0. Doing so takes over the old path, kills the redirect and permanently strands every client older than 3.9.0. Releases must also stay on this repository: moving them to a different owner or repo breaks the same path.

Auto-update proof

An updater check is only meaningful from an installed lower version to the newly published higher version.

  1. Keep the previous stable installer available and install/run that version.
  2. Publish the new higher version and its matching latest.yml, installer and blockmap.
  3. Launch the previous installed version normally (not npm start).
  4. Confirm logs show the GitHub feed check, the new version download and the restart prompt. electron-updater’s own log names a differential download (“Download block maps”); a line “Cannot download differentially, fallback to full download” is not a failure, but note it. [updater] update signer accepted must appear before the install: an unsigned or unpinned installer is refused (see INSTALLER_AND_UPDATES.md).
  5. Accept restart, then confirm the running app reports the new version.

On Linux the same proof starts from the previous AppImage, run as a file the user owns. After “Update now”, electron-updater replaces it with Achievement.Watcher.<version>.AppImage and restarts it; the log then reports the new version, and the autostart entry, when enabled, points at the new file. There is no signer line on Linux: the SHA-512 in latest-linux.yml is the check.

Do not claim auto-update success from source mode, an unpacked build, or a same-version asset replacement.

Validation log

  • 3.5.2 (2026-08-04): maintainer confirmed the installed-app auto-update from 3.5.1 to 3.5.2 (feed check, download, restart prompt and reported version).

Final handoff

  • Confirm git status --short --branch is clean and synchronized.
  • Report tests, build output, CI result, release URL and updater result separately.
  • If the user asked to relaunch the application, relaunch the installed build only after validation is complete.