Files
software-mansion__argent/scripts/next-canary-version.test.mjs
Ignacy Łątka c4a9f0bd8a ci: publish a "next" canary on every push to main (#423)
**Update after team discussion:** the open question from the original
version of this PR ("should we fire `next` on every push to main?") got
a yes, so this now does exactly that — continuous canary publishing from
`main`. Original tag-sync wiring is still here too. Branch is merged up
to current `main`.

### what it does

every push to main publishes a canary prerelease under the `next`
dist-tag, so `next` always tracks the tip of main.

- **"Publish prerelease to npm (next)"** now also triggers on `push:
branches: [main]`. on that path it derives a version, stamps it into
`packages/argent/package.json`, and publishes `--tag next`. the manual
`workflow_dispatch` path is untouched (gated behind `if:
github.event_name == 'workflow_dispatch'`).
- **version scheme** = `<patch-bump of the highest published
version>-next.<counter>`. a prerelease of the next patch still outranks
the current `latest` (so `next` lands above it) but stays under the
eventual stable. the **counter resets per base**: next index after the
highest `<base>-next.<k>` already on npm, or 0 if the base has no
canaries yet — so each patch line starts at `.0`:

  ```
  0.13.1-next.0, 0.13.1-next.1, ...   ← latest is 0.13.0
       ... 0.13.1 ships stable ...
  0.13.2-next.0, 0.13.2-next.1, ...   ← counter restarts
  ```

lives in `scripts/next-canary-version.mjs` (+ tests) as a pure function
of the published-versions list, reusing the semver helpers from the sync
script.
- **`argent -v` reports the right thing.** the CLI reads its version
from `package.json` at runtime, so stamping is enough — and there's an
in-pipeline step that runs the built `argent --version` and fails the
publish if it doesn't match the version about to ship. verified
end-to-end against the real registry: computed `0.13.1-next.0`, stamped,
built, `argent --version` → `0.13.1-next.0`.
- **collisions self-heal.** the counter is registry-derived, so canary
publishes are serialized (a `concurrency` group) to kill the concurrent
race; the publish step additionally retries a version-exists collision
(npm read-after-write lag on back-to-back runs) by recomputing. any
other publish failure still fails fast.
- canary builds pull the floating `argent-main` / `radon-main` signed
binaries. sync workflow still fires after every publish (self-heals
`next`, and still does the real work after a *stable* release).

### stuff to sanity-check before merge

- this publishes to npm on **every** merge to main — a lot of versions +
a full build/publish run each time. fine if that's the intent (it's the
whole point), just calling it out.
- **native binaries on the canary path are the floating `argent-main`
tag** — if radon's binary build lags a native-source change on main, a
canary can ship fresh JS against slightly-older signed
dylibs/ax-service/APK with no signal (the *missing* case fails loud +
safe; the *stale* case is silent). there's no per-commit signed-binary
build to pin against, so this is inherent to canary-from-main rather
than something this PR can fix — flagging it as the one real "preview
channel can be skewed" caveat.
- during a rapid burst of merges, GitHub's concurrency supersedes
intermediate queued runs — published `-next.N` stay contiguous from 0,
but not literally every commit in a burst gets its own canary.
- like any push trigger, it only goes live once this is on main.
2026-07-01 18:01:53 +02:00

45 lines
1.6 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { computeCanaryVersion } from "./next-canary-version.mjs";
test("first canary of a new base starts at 0", () => {
assert.equal(computeCanaryVersion(["0.12.0", "0.12.1", "0.13.0"]), "0.13.1-next.0");
});
test("increments the counter within the same base", () => {
assert.equal(computeCanaryVersion(["0.13.0", "0.13.1-next.0", "0.13.1-next.1"]), "0.13.1-next.2");
});
test("resets to 0 when the base advances (a new stable shipped)", () => {
// 0.13.1 is now stable; its old 0.13.1-next.* belong to a finished line.
assert.equal(
computeCanaryVersion(["0.13.0", "0.13.1-next.0", "0.13.1-next.1", "0.13.1"]),
"0.13.2-next.0"
);
});
test("continues after the highest existing next.k (tolerates gaps)", () => {
assert.equal(computeCanaryVersion(["0.13.0", "0.13.1-next.0", "0.13.1-next.5"]), "0.13.1-next.6");
});
test("counts manual-dispatch canaries of the same base too", () => {
assert.equal(computeCanaryVersion(["0.13.0", "0.13.1-next.3"]), "0.13.1-next.4");
});
test("ignores non-next prereleases of the same base", () => {
assert.equal(computeCanaryVersion(["0.13.0", "0.13.1-beta.9", "0.13.1-next.0"]), "0.13.1-next.1");
});
test("no stable yet: continues the highest prerelease's lineage", () => {
assert.equal(computeCanaryVersion(["0.1.0-next.0", "0.1.0-next.5"]), "0.1.0-next.6");
});
test("skips unparseable versions", () => {
assert.equal(computeCanaryVersion(["garbage", "1.2.3", ""]), "1.2.4-next.0");
});
test("throws when there are no parseable versions", () => {
assert.throws(() => computeCanaryVersion(["", "x"]), /no parseable/);
});