Hold the shipped source to ECMAScript alone, and run the suite on Deno and Bun
CI / gate (push) Successful in 18s

This commit is contained in:
2026-09-01 16:44:32 +02:00
parent ece3828122
commit 1b7f66546e
7 changed files with 55 additions and 8 deletions
+10
View File
@@ -60,6 +60,12 @@ they never reach a consumer.
## 6. The package contract ## 6. The package contract
- Runs on every runtime, not only Node — a browser as readily as a server. The shipped source is
ECMAScript and nothing else, at an ES2022 baseline: no host import, no host global, no DOM.
`tsconfig.build.json` is that gate, typechecking the shipped files alone with `types: []` and
`lib: ES2022`, so `node:fs`, `process` and an ES2024 method are compile errors here rather than a
consumer's crash there. Node's test runner, the corpus reads and the build are the repo's own,
never the library's.
- ESM only — no CommonJS build, no dual-package hazard. - ESM only — no CommonJS build, no dual-package hazard.
- One entrypoint: built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint — - One entrypoint: built JavaScript, `.d.ts` beside it. Do not add a TypeScript-source entrypoint —
Node refuses to type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), Node refuses to type-strip under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`),
@@ -113,6 +119,10 @@ Test for the behaviour wanted first, then implement until green. `node --test`,
Node, tsc and npm never run on the host — only via the pinned images (§9). Tests are independent, Node, tsc and npm never run on the host — only via the pinned images (§9). Tests are independent,
coverage does not decline, containers are torn down after a run. coverage does not decline, containers are torn down after a run.
The gate runs that same suite under Deno and Bun as well as Node, the three images pinned alike:
§6's compile gate proves no host API is named, and only running proves those engines execute what
is written. Both refuse a run that matches no test, so Node's is the only vacuous-green guard.
The floors live in the `test` script, so `npm test` and the gate are one path: 100% of lines and The floors live in the `test` script, so `npm test` and the gate are one path: 100% of lines and
functions, and a branch floor that only ever moves upward. It sits below 100 because the guards functions, and a branch floor that only ever moves upward. It sits below 100 because the guards
`noUncheckedIndexedAccess` and ADF's optional keys force — `?? []`, `?? {}`, `?.`, an index `noUncheckedIndexedAccess` and ADF's optional keys force — `?? []`, `?? {}`, `?.`, an index
+2 -1
View File
@@ -64,4 +64,5 @@ Personas, never named consumers (AGENTS.md §7):
## The package ## The package
ESM only, no runtime dependencies, public npmjs. Built JavaScript with `.d.ts` beside it. ESM only, no runtime dependencies, public npmjs. Built JavaScript with `.d.ts` beside it.
Contract: `AGENTS.md` §56. Pure ECMAScript at an ES2022 baseline, reaching for no host API, so a browser runs it as readily
as a server; the test suite runs under Node, Deno and Bun. Contract: `AGENTS.md` §56.
+10 -5
View File
@@ -2,13 +2,15 @@
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
image=node:24.19.0-alpine3.24 bun_image=oven/bun:1.4.0-alpine
in_node() { docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/app" -w /app "$image" "$@"; } deno_image=denoland/deno:2.9.6
node_image=node:24.19.0-alpine3.24
in_image() { docker run --rm -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/app" -w /app --entrypoint "$2" "$1" "${@:3}"; }
in_node npm ci in_image "$node_image" npm ci
in_node npm run typecheck in_image "$node_image" npm run typecheck
if ! test_output=$(in_node npm test 2>&1); then if ! test_output=$(in_image "$node_image" npm test 2>&1); then
printf '%s\n' "$test_output" printf '%s\n' "$test_output"
exit 1 exit 1
fi fi
@@ -17,3 +19,6 @@ if printf '%s' "$test_output" | grep -q ' tests 0'; then
echo 'the gate ran zero tests — failing instead of a vacuous green' echo 'the gate ran zero tests — failing instead of a vacuous green'
exit 1 exit 1
fi fi
in_image "$deno_image" deno test --allow-read --no-check src/
in_image "$bun_image" bun test
+1 -1
View File
@@ -14,7 +14,7 @@
}, },
"scripts": { "scripts": {
"test": "node --test --experimental-test-coverage --test-coverage-exclude=\"src/**/*.test.ts\" --test-coverage-branches=97 --test-coverage-functions=100 --test-coverage-lines=100 \"src/**/*.test.ts\"", "test": "node --test --experimental-test-coverage --test-coverage-exclude=\"src/**/*.test.ts\" --test-coverage-branches=97 --test-coverage-functions=100 --test-coverage-lines=100 \"src/**/*.test.ts\"",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.build.json"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "24.13.3", "@types/node": "24.13.3",
+18
View File
@@ -2,6 +2,15 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"automerge": true, "automerge": true,
"customManagers": [ "customManagers": [
{
"customType": "regex",
"datasourceTemplate": "docker",
"depNameTemplate": "denoland/deno",
"description": "Pin the Deno image ci.sh runs",
"managerFilePatterns": ["ci.sh"],
"matchStrings": ["denoland/deno:(?<currentValue>[0-9][^\\s\"']*)"],
"versioningTemplate": "docker"
},
{ {
"customType": "regex", "customType": "regex",
"datasourceTemplate": "docker", "datasourceTemplate": "docker",
@@ -11,6 +20,15 @@
"matchStrings": ["node:(?<currentValue>[0-9][^\\s\"']*)"], "matchStrings": ["node:(?<currentValue>[0-9][^\\s\"']*)"],
"versioningTemplate": "docker" "versioningTemplate": "docker"
}, },
{
"customType": "regex",
"datasourceTemplate": "docker",
"depNameTemplate": "oven/bun",
"description": "Pin the Bun image ci.sh runs",
"managerFilePatterns": ["ci.sh"],
"matchStrings": ["oven/bun:(?<currentValue>[0-9][^\\s\"']*)"],
"versioningTemplate": "docker"
},
{ {
"customType": "regex", "customType": "regex",
"datasourceTemplate": "docker", "datasourceTemplate": "docker",
+5 -1
View File
@@ -139,10 +139,14 @@ numbering is the order the work was planned in, not the order it ships.
additions are read here as one list before that freeze — nine sessions mint them additions are read here as one list before that freeze — nine sessions mint them
independently, and one cause wearing two codes is breaking to undo after `0.1.0`. `0.1.0` independently, and one cause wearing two codes is breaking to undo after `0.1.0`. `0.1.0`
is the markdown round-trip: both markdown directions, the types, `isAdfDocument`. The build is the markdown round-trip: both markdown directions, the types, `isAdfDocument`. The build
lands here: a build tsconfig emitting JS and `.d.ts` to `dist/` (the dev config's lands here: `tsconfig.build.json` gains emit of JS and `.d.ts` to `dist/` (the dev config's
`allowImportingTsExtensions` forces `noEmit`, so the build config needs `allowImportingTsExtensions` forces `noEmit`, so the build config needs
`rewriteRelativeImportExtensions`), plus `exports`/`files` in `package.json`. The `rewriteRelativeImportExtensions`), plus `exports`/`files` in `package.json`. The
maintainer's bump PR also removes `private: true`, the guard against any earlier publish. maintainer's bump PR also removes `private: true`, the guard against any earlier publish.
§6's browser half is first checkable here, on the emitted `dist/index.js` a browser can
load — the compile gate names no host API, and a real page converting a document is the
other half. `engines` is settled here too: `>=24` is the toolchain's floor, not the ES2022
one the shipped JavaScript needs, and it reads to a consumer as the latter.
**Settled** (the maintainer, 2026-09-01): the round-trip proved over the checked-in corpus **Settled** (the maintainer, 2026-09-01): the round-trip proved over the checked-in corpus
is what `0.1.0` ships on, and the open-ended proof work follows it rather than gating it — is what `0.1.0` ships on, and the open-ended proof work follows it rather than gating it —
3k's spec suite and 4's generators and maintainer-supplied payloads are `0.2.0`, 4b's retry 3k's spec suite and 4's generators and maintainer-supplied payloads are `0.2.0`, 4b's retry
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"lib": ["ES2022"],
"target": "ES2022",
"types": []
},
"exclude": ["src/**/*.test.ts"]
}