v0.1.1
Internal
-
Changelog entries are now recorded as one fragment file per change in
changelog.d/,
assembled into a version section byscripts/assemble-changelog.shat release, so
parallel pull requests never conflict onCHANGELOG.md. Seechangelog.d/README.md. -
Four byte-identical private copies of
escapeRegExpin build-time TypeScript are
gone, replaced by the right tool for each of the two contexts they were serving.
The three JavaScript call sites (lint-island-imports.ts,react-alias.ts,
sidecar/bundle-island.ts) now use the standardRegExp.escape, which is
specified for exactly the ECMAScriptRegExpposition they feed. The fourth,
emit-host-config.ts, emits ApacheRewriteRulepatterns — PCRE, a different
dialect — so it gets a purpose-namedescapePcrewhose contract matches its
output language and which keeps a deployed.htaccessreadable
(^app/.*$, not^\x61pp/.*$). -
Generated Apache config now has a validation net rather than only literal-string
greps:emit-host-config.test.tsruns each emittedRewriteRulepattern through
a real Perl-compatible regex engine and asserts it matches the URLs it should and
rejects the near-misses an unescaped.would have swallowed.buildAllowgained
the metacharacter-escaping test it never had. -
Eleven of the fourteen test scripts
tests/meta/unrun-scripts.txtinventoried as knowingly
unrun now run in CI: the non-browserexamples/tsx-site/test/*.sh— island SSR and the
bundle/import-map wiring, SSR↔CSR parity, byte-parity against a rawbun build, depfile
incrementality, the props-check gate in both directions,migrate --doctor, and the four SPA
prerender scripts (routing manifest, nginx/zigbase host config, code splitting, baked flag
defaults, guarded routes, nested layouts) — plus the live-server smoke test.They are a step in the existing
e2e-dev-loopjob rather than atests/<area>/shim, and the
distinction is the whole point. Every one of them runszig buildinsideexamples/tsx-site,
i.e. a full consumer build of zigapagos-as-a-dependency, ande2e-dev-loopis the only job
that already pays for one — itstests/serve/dev.shstep drives that project's own
zig build dev. A shim would have put them ine2e-rest, which builds the repo and not the
example, buying a cold ~265s consumer build and making that job the run's critical path.
Measured against the warm tree the job already has, the eleven cost 49s in CI (29s
locally) against the 468s thedev.shstep above them takes.serve.shalone was
inventoried at 76.1s; behinddev.shit is ~5s, which is the placement argument in one
number.The list is literal, not a glob, for the opposite reason
e2e-restuses a glob: a new sibling
in that directory should NOT be adopted onto the PR path automatically — it might be the next
one that needs a browser or four minutes. Being unnamed there is exactly what makes
script-coverage.shstop and ask. -
tests/meta/script-coverage.shno longer counts a script as CI-run because a workflow
comment names it. Its rule (b) was a plaingit grepover.github/workflows/, so prose
saying "these two are deliberately not run here" would have vouched for precisely the scripts
it was disclaiming — and, since both are also inventoried, would have failed the gate with
"run by CI but also listed". Rule (b) now applies the same non-comment filter rule (c) already
had. The two Playwright paths are spelled in full in that comment on purpose: they pin the
filter, because removing it turns the gate red by name.(
site/test/build.shand the two Playwright scripts were the three still inventoried at this
point; all three were wired up before this release shipped — see the entry below for where
each ended up and why.) -
The
typescriptdevDependency moves 5.9.3 → 6.0.3, the final JavaScript-based TypeScript
line. The compiler API thatruntime/scripts/slice-host.tsand
runtime/sidecar/hot-transform.tsparse with is fully present, so neither needed
re-platforming, and the runtime suite is unchanged at 617 passing.site/bun.lockand
examples/tsx-site/bun.lockare regenerated in step: each embeds its own copy of
@z/runtime's resolved dependencies and bun does not refresh them for a linked package on a
plain install, so left alone they would have kept the props-check gate running 5.9.3 while the
runtime was tested on 6.0.3. -
TypeScript 7.x is capped out via a Dependabot
ignoreon>=7.0.0. 7.0 is the Go rewrite and
its npm package no longer ships the JavaScript compiler API —import ts from "typescript"
resolves tolib/version.cjsand yields only{version, versionMajorMinor}, taking the
runtime suite to 566 passing / 51 failing. The cap is deliberately a version bound rather than
a major-block, which is what let 6.x through. It lifts when a 7.x ships a usable programmatic
API (7.1 at the earliest). -
The
tsconfig.jsonfiles were audited against TypeScript 6.0's deprecation list and needed no
changes: none usesbaseUrl,outFile,downlevelIteration,target: es5,
moduleResolution: node|node10|classic,module: amd|umd|system|none, or an explicitly false
esModuleInterop/allowSyntheticDefaultImports/alwaysStrict. No source file uses the
modulenamespace keyword or importassertsyntax.ignoreDeprecationsis therefore not
needed, and the config surface is already clean for whatever 7.x removes. -
Dependabot no longer groups major version bumps with routine ones. The
bungroups for
runtime/are restricted to minor and patch, and a newruntime-majorsgroup collects every
major into its own pull request, so a breaking major can no longer block unrelated updates
from merging.github-actionsdeliberately keeps its single group: everyuses:is pinned to
a bare major tag, so majors are the only update it can produce and splitting would reintroduce
per-action pull-request spam. -
happy-domand@happy-dom/global-registratormove to 20.11.0. -
tests/meta/unrun-scripts.txtis empty. All 36 tracked test scripts are now run by CI;
the inventory that started at 14 rows and was cut to 3 is at 0. The file stays because the
gate reading it is the point, not the list.site/test/build.shmoved intopages.yml, betweenBuild siteandUpload artifact. That
makes it a deploy gate: a failed assertion fails the build job, the artifact is never
uploaded, anddeploy(whichneeds: build) never runs, so the previous good deployment
stays live. It is nearly free there — the workflow has already builtsite/, so the script's
own install and build are warm no-ops and the five greps measured 1.7s — against ~120s in any
ci.ymljob, becausesite/is a third consumer project with its own.zig-cachethat
nothing else warms. The residual gap is stated rather than glossed:pages.ymltriggers on
push tomainand manual dispatch only, so these assertions gate the deploy and not the PR.examples/tsx-site/test/{hydrate,spa_slice}.shmoved into a newbrowser-e2e.ymlon a
nightlyschedule:plusworkflow_dispatch:. Scheduled rather than PR-gating because each is
~125s on top of a ~265s cold consumer build plus a ~150MB browser install, and because what
they catch — a real-browser hydration or runtime-slicing regression — arrives with a
runtime/srcchange or a dependency bump, unattended. Each script gets its own matrix runner
(fail-fast: false):spa_slice.shopens byrm -rfing.zig-cacheandzig-out, so the
two cannot share a build, and separate runners make that hazard structurally impossible rather
than merely avoided.One correction to the plan the inventory carried: the install step is
playwright install --with-deps chrome, notchromium. All ten*_playwright.pyhelpers
launch withchannel="chrome", which on Linux resolves to/opt/google/chrome/chrome— the
bundled Chromium build satisfies none of them, and the run would have died at browser launch
after paying for the whole consumer build. -
tests/meta/script-coverage.shgained a self-test,tests/meta/script-coverage.test.sh,
in the shape ofscripts/check-allocator-contracts.test.sh: seven cases against throwaway git
repos in$TMPDIR. That gate has shipped three defects already — a self-vouching inventory, a
pipefail+grep -qSIGPIPE race, and a comment filter applied to one rule but not the
other — and every one made it pass when it should have failed.Two of the cases exist because emptying the inventory silently removed the only thing pinning
the comment filter. That filter is what stops a script a workflow merely mentions in prose
from counting as run, and it was pinned by accident: while the two Playwright scripts were
inventoried, dropping the filter made the gate see them as both CI-run and listed and fail by
name. With the inventory empty, removing the filter now breaks nothing in the tree —
confirmed by deleting the filter line and watching the real gate still pass 36/36. Case 5
makes the pin deliberate; case 6 is its guard rail, that comment-awareness has not become
"never believe a workflow". -
contract/test/drift.sh— the test that proves the cross-tier codegen gate is not vacuous —
was itself vacuous, and now runs in CI. Its Case B asserted only thattsc --noEmitexited
non-zero, which a compiler that fails to launch also does:contract/has no
node_modules, sobun x tscresolvedtscoffPATH, hit mise's shim, and died with
No version is set for shim: tsc— exit 1, nothing type-checked,PASS Case Bprinted. Cases
A and B now assert on the diagnostic text (theexperiments→variantshunk in api-check's
staged diff; both assignability directions of the_assert.tstripwire, and no diagnostic
from anywhere else), and a new Case D feeds those assertions canned "the tool never ran"
output to prove they reject it. -
The same script now runs the repo's pinned TypeScript rather than whatever
bun xresolves.
bun x tscwith no local install can fetch from npm, wherelatestis 7.x — the line this
repo deliberately caps out — so the gate could have silently type-checked with a compiler the
manifest pins away from. It now invokesruntime/node_modules/typescriptthrough bun and
fails if the installed version does not match the one locked inruntime/bun.lock. -
drift.sh's restore no longer discards more than it mutates. It used to
git checkout HEAD -- contract/, which coverscontract/test/drift.shitself — editing the
script and running it reverted the edit mid-run. The restore set is now exactly the two paths
a case writes to, a pre-flight refuses to start when either is already dirty, and the EXIT
trap both restores and fails the run if anything is left behind. -
A
tests/contract/drift.shshim puts the gate in CI'stests/*/*.shglob, alongside the
existingtests/changelog/assemble.shandtests/release/scripts.shhooks. It costs ~1.5s
and spawns no server, so it runs in thee2e-restshard rather than a job of its own. Being
outside that glob, and unnamed inci.yml, is why the rot above went unseen. -
examples/tsx-site/test/spa.shhad rotted the same way, and is fixed. Its two nginx
assertions expected the unquotedtry_files $uri $uri/ /app/index.html;, but
emit-host-config.tshas run every interpolated route value throughnginxQuote()since
that helper landed, so both greps had matched nothing for as long as they had existed — and
the script sits outside thetests/*/*.shglob, so nothing ran it. They now match the quoted
form byte-for-byte, with a third assertion covering the dynamiclocation'stry_files
order. The emitter itself was never at risk:runtime/scripts/emit-host-config.test.tspins
the same strings and does run in CI. What the e2e assertions add is that those bytes actually
reachzig-out/site/app/nginx.nginx.confin a real build. -
A new gate,
tests/meta/script-coverage.sh, makes that class of rot impossible to introduce
silently: every test script must be either run by CI or listed intests/meta/unrun-scripts.txt
with a written reason, and the gate fails on one that is neither — as well as on a stale row
for a script that has since been wired up or deleted. This is the same shape as
scripts/allocator-allowlist.txtand its checker. It isgit ls-filesplusgrepover
tracked text, so it costs no toolchain and runs in well under a second.The inventory it enforces records the audit behind it: all 34 tracked test scripts were run by
hand, 20 are covered by CI, and the 14 that are not (site/test/build.shplus the 13 under
examples/tsx-site/test/) all currently pass. Each row carries its measured wall-clock and
what adopting it would cost, so the trade-off can be re-checked rather than re-derived.