Skip to content

feat(build): stamp a self build with the release it anticipates - #213

Merged
thecodedrift merged 2 commits into
mainfrom
feat/self-build-prerelease-version
Aug 30, 2026
Merged

feat(build): stamp a self build with the release it anticipates#213
thecodedrift merged 2 commits into
mainfrom
feat/self-build-prerelease-version

Conversation

@thecodedrift

Copy link
Copy Markdown
Member

The problem

build:self reported the committed package version. Its second step is init --no-interactive, which writes install.cliVersion into the committed .taskless/taskless.json — so on a main carrying 35 pending changesets, a local dogfood build wrote 0.10.2.

That is wrong twice over. It names a release that predates the tree the build came from, and it is byte-identical to what a real install of that release would write, so the file silently loses whatever it held with nothing to signal it.

Not hypothetical: this bit two separate agents in one afternoon, each time noticed only because someone read the diff before staging.

The fix

A self build now stamps <next>-self, where <next> is what the pending changesets propose. Today that is 0.11.0-self, which this PR commits.

scripts/next-version.ts derives it from changeset status, with two details that are easy to get wrong:

  • Selection is by package name, not releases[0]. The six @taskless/vale-* packages share the changesets workspace, so the index is the CLI only until something ships alongside it. nightly-pack.cjs documents the same trap.
  • --output= resolves against the CWD. --output=/tmp/x.json silently writes <cwd>/tmp/x.json; I hit this while building the script.

It falls back to the committed version when no changesets are pending, which is the state of main for the whole window after a release. A self build has to keep working there.

Why this falls back where nightly throws

resolveNightlyVersion refuses to guess, because a nightly with a wrong version sends agents to the wrong published package — a wrong string is worse than a failed build.

A self build is the opposite case. It is local, its invocation is a filesystem path rather than a package specifier, and nothing it emits is fetched by anyone. The -self suffix is what carries the meaning; the base is a convenience. So an unset or malformed env degrades to <committed>-self rather than blocking someone's local build. The asymmetry is spelled out in the code so it does not read as an oversight.

The suffix is safe for the ledger

reconcile-marker compares the numeric core (version.split("-")[0]), so 0.11.0-self and 0.11.0 are the same version to a reconciliation walk — exactly as a nightly and its release already are. A test pins that rather than leaving it as an assumption.

Verification

pnpm build:self writes install.cliVersion: "0.11.0-self"
dist-self/index.js reports taskless v0.11.0-self
pnpm build (prod) still reports 0.10.2 — the suffix does not leak
working tree after the build only taskless.json, no stray rewrites
pnpm test 998 passing

New tests cover the stamped value, the fallback with no base, four shapes of malformed base, and the numeric-core equality the ledger depends on.

Notes

  • The committed 0.11.0-self is the point rather than a side effect: it records that a self build last wrote the scaffold, and it is unmistakable in a diff if one does so again.

`build:self` reported the committed package version, so on a `main` carrying
unreleased work it wrote `install.cliVersion: "0.10.2"` into the committed
`.taskless/taskless.json` — a release that predates the tree, and a value
indistinguishable from what a real install of that release would write, so
the file silently lost whatever it held. That happened twice in one
afternoon, each time caught only because someone read the diff before
staging.

A self build now stamps `<next>-self`, where `<next>` is what the pending
changesets propose. `scripts/next-version.ts` reads it from `changeset
status` and falls back to the committed version when nothing is pending,
which is the state of `main` for the whole window after a release. Selection
is by package name rather than `releases[0]`: the six `@taskless/vale-*`
packages share the changesets workspace, so the index is the CLI only until
something ships alongside it.

Unlike the nightly resolver this falls back rather than throwing, and the
asymmetry is deliberate. A nightly that guesses sends agents to the wrong
published package, so a wrong string is worse than a failed build. A self
build is local, its invocation is a path rather than a package specifier,
and the suffix is what carries the meaning, so an unset env degrades instead
of blocking someone's local build.

The suffix is safe for the ledger: `reconcile-marker` compares the numeric
core, so `0.11.0-self` and `0.11.0` are the same version to a reconciliation
walk, exactly as a nightly and its release are. A test pins that.

Commits the resulting `0.11.0-self`, and verified the prod build still
reports `0.10.2`, so the suffix does not leak.
@thecodedrift
thecodedrift merged commit 066e5d8 into main Aug 30, 2026
6 checks passed
@thecodedrift
thecodedrift deleted the feat/self-build-prerelease-version branch August 30, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant