Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ permissions:
contents: write

jobs:
# The tag names the release and package.json is what electron-builder stamps
# into every artifact name and into the updater feed, so a mismatch publishes a
# build that disagrees with its own tag. Gating here means a bad tag fails
# before `create-draft` runs, so it leaves no orphan draft behind either.
guard:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'

# GITHUB_REF/GITHUB_REF_NAME identify the pushed tag, so this needs no tag
# lookup and no full history; it reads package.json and nothing else.
- name: Tag matches package.json
run: npm run check:version

# Creates the release as a draft BEFORE the platform builds, so both matrix
# jobs upload into the same release. Without this, macos and windows run
# concurrently, each finds no release for the tag and each creates its own
Expand All @@ -23,6 +45,7 @@ jobs:
# its stale assets into a fresh run is worse than rebuilding the release
# empty. Re-running a failed release run therefore starts from a clean slate.
create-draft:
needs: guard
runs-on: ubuntu-latest
timeout-minutes: 5

Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,27 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# check:version compares package.json against the newest release tag, so the
# tags have to be here. `fetch-tags: true` does NOT do it: in the shallow
# path actions/checkout only adds the checked-out ref's own tag to the
# refspec, and a pull request ref (`refs/pull/N/merge`) has no tag. All tags
# come with all history, and this repository is small enough that the full
# fetch is not worth optimising.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'npm'

# Deliberately before `npm ci`: it reads only package.json and the git tags,
# so a version that has fallen behind the newest release fails in seconds
# instead of after a full install.
- name: Version consistency
run: npm run check:version

- name: Install dependencies
run: npm ci

Expand Down
61 changes: 47 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,21 @@ npm run dev

## Commands

| Command | What it does |
| ------------------------ | -------------------------------------------------------------- |
| `npm run dev` | Start the app in development with HMR. |
| `npm run typecheck` | Typecheck the main/preload, renderer and test projects. |
| `npm test` | Run the Node test suite (`test/**/*.test.ts`). |
| `npm run lint` | ESLint (see the note on the current baseline below). |
| `npm run format` | Prettier over the whole repository. |
| `npm run build` | Typecheck, then bundle with electron-vite. |
| `npm run build:unpack` | `build`, then produce an unpacked app in `dist/`. |
| `npm run smoke:packaged` | Launch the packaged app's `--smoke-test` and check it starts. |
| `npm run eval:prepare` | One-time, networked: download the pinned eval embedding model. |
| `npm run eval` | Run the RAG eval harness offline; rewrites `docs/eval/`. |
| `npm run db:generate` | Generate a Drizzle migration from `src/main/db/schema.ts`. |
| `npm run db:studio` | Inspect the development database. |
| Command | What it does |
| ------------------------ | --------------------------------------------------------------- |
| `npm run dev` | Start the app in development with HMR. |
| `npm run typecheck` | Typecheck the main/preload, renderer and test projects. |
| `npm test` | Run the Node test suite (`test/**/*.test.ts`). |
| `npm run lint` | ESLint (see the note on the current baseline below). |
| `npm run check:version` | Fail when `package.json` disagrees with the newest release tag. |
| `npm run format` | Prettier over the whole repository. |
| `npm run build` | Typecheck, then bundle with electron-vite. |
| `npm run build:unpack` | `build`, then produce an unpacked app in `dist/`. |
| `npm run smoke:packaged` | Launch the packaged app's `--smoke-test` and check it starts. |
| `npm run eval:prepare` | One-time, networked: download the pinned eval embedding model. |
| `npm run eval` | Run the RAG eval harness offline; rewrites `docs/eval/`. |
| `npm run db:generate` | Generate a Drizzle migration from `src/main/db/schema.ts`. |
| `npm run db:studio` | Inspect the development database. |

## Before you open a pull request

Expand Down Expand Up @@ -129,6 +130,38 @@ when it does, and `breaking` when it breaks compatibility.
**Version bumps and releases are done by the maintainer** — don't change
`version` in `package.json` or add a tag in a PR.

`package.json`'s `version` is the single source of truth. electron-builder takes
the app version and every artifact name (`knownote-${version}-setup.exe`, the
`.dmg`, `latest.yml`) from it, the updater compares versions against it, and the
About panel reads it back out of the packaged app via `app.getVersion()`. The
git tag has to agree with it, and it has to be `v`-prefixed, because
`release.yml` triggers on `tags: v*.*.*` — a tag named `1.3.0` would push nothing
and publish no artifacts.

The recommended procedure, which cannot drift because one command does both
halves at once:

1. On a clean `main`, run `npm version <patch|minor|major>`. It bumps
`package.json` and `package-lock.json`, commits, and creates the `v<version>`
tag.
2. `git push --follow-tags`. Pushing the tag is what starts `release.yml`.

Bumping `package.json` by hand and tagging the same commit separately also
works, and is how earlier releases here were cut. The mechanism matters less than
the invariant: whatever bumps the version must land on `main` before the tag is
pushed, and the tag must be `v<version>`.

`scripts/check-version.mjs` (`npm run check:version`) enforces that invariant, and
both workflows run it:

- **tag build** (`release.yml`) — the tag must equal `package.json` **exactly**.
This is the release blocker: nothing is built or published on a mismatch, and
it runs before the draft release is created, so a bad tag leaves nothing behind.
- **pull request** (`verify.yml`) — `package.json` must not be **behind** the
newest tag. Equality is deliberately not required here: `npm version` is merged
before the tag is pushed, and during that window `main` is legitimately ahead
of the newest tag.

## Where things live

```plaintext
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"format": "prettier --write .",
"lint": "eslint --cache .",
"check:design": "node scripts/check-design-tokens.mjs",
"check:version": "node scripts/check-version.mjs",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck:test": "tsc -p tsconfig.test.json",
Expand Down
Loading
Loading