From bb29acd429257cc7bf280204b1b28240cc340a22 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 09:52:04 +0000 Subject: [PATCH 1/8] feat(js): one package.json, three runtimes (bun, pnpm, deno) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the owner's direction of 2026-10-14: Bun is the main runtime, and a generator should keep Bun, Deno and pnpm outputs working and current, with every .json in I-JSON + JCS (D306, ijson-jcs). package.json is the only manifest; Bun, pnpm and Deno 2 all read it natively, so nothing is translated and no second manifest can drift. Each runtime's tool writes its own lock (bun.lock, pnpm-lock.yaml, deno.lock). No deno.json: Deno stays banned as an authoring target. - scripts/js-runtimes.sh regen: the only writer (ijson-jcs fix, then the three locks). check : JCS, lock present, frozen install, test script under that runtime's runner. - scripts/tests/js-runtimes-test.sh: 38 cases proving each failure under each runtime, and exit 2 when a tool is missing. - .github/workflows/js-runtimes-reusable.yml: one job per runtime; runtimes installed from npm at pinned versions, no new actions. - self-test.yml installs the three runtimes for the test. - LANGUAGE-POLICY §1.4 (PROPOSED), JSON-POLICY §6, the CLAUDE.md Deno row, and a decision record. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Y6FD63UrriRBHqmhhEca9t --- .claude/CLAUDE.md | 2 +- .github/workflows/actions.lock | 2 + .github/workflows/js-runtimes-reusable.yml | 93 ++++++++++++++ .github/workflows/self-test.yml | 5 + 3-practice/JSON-POLICY.adoc | 7 +- 3-practice/LANGUAGE-POLICY.adoc | 35 +++++- ...026-10-14-one-manifest-three-runtimes.adoc | 95 +++++++++++++++ scripts/js-runtimes.sh | 115 ++++++++++++++++++ scripts/tests/js-runtimes-test.sh | 90 ++++++++++++++ 9 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/js-runtimes-reusable.yml create mode 100644 docs/decisions/2026-10-14-one-manifest-three-runtimes.adoc create mode 100755 scripts/js-runtimes.sh create mode 100755 scripts/tests/js-runtimes-test.sh diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 772a4901e..5c7a4a44c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -177,7 +177,7 @@ for the canonical statement. | TypeScript | AffineScript | RS/TS/JS → AffineScript → typed-wasm. | | ReScript | AffineScript | RS/TS/JS → AffineScript → typed-wasm. | | **ReScript** | AffineScript | Banned in new code as of 2026-04-30. Existing `.res` files migrate to `.affine` directly (do not pass through ReScript). | -| **Deno** | Bun | **Banned 2026-09-22.** Owner ruling: *"deno is over, we're prioritising bun, and using bunx."* `deno.json` task definitions must be ported to `package.json` scripts. Shrink-only ledger: `.machine_readable/deno-allow.txt`. | +| **Deno** | Bun | **Banned 2026-09-22.** Owner ruling: *"deno is over, we're prioritising bun, and using bunx."* `deno.json` task definitions must be ported to `package.json` scripts. Shrink-only ledger: `.machine_readable/deno-allow.txt`. Banned as an *authoring* target only: a generated `deno.lock` beside a `package.json` (one manifest, three runtimes — `3-practice/LANGUAGE-POLICY.adoc` §1.4, PROPOSED 2026-10-14) is a compatibility output, not a Deno project. | | Node.js | Bun | Bun is Node-compatible; run the code, drop the runtime. | | npm | Bun | npm is tier 3 — *permitted, never preferred*, not banned. `package-lock.json` must still not be tracked (standards#67). | | yarn | Bun | yarn is not in the tier list at all. | diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index d41887b41..dbbd79f56 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -82,6 +82,8 @@ workflows: - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/instant-sync.yml': - 'peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697' + '.github/workflows/js-runtimes-reusable.yml': + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/k9-contractile.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' '.github/workflows/label-triage.yml': [] diff --git a/.github/workflows/js-runtimes-reusable.yml b/.github/workflows/js-runtimes-reusable.yml new file mode 100644 index 000000000..88f692931 --- /dev/null +++ b/.github/workflows/js-runtimes-reusable.yml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# One package.json, three runtimes (3-practice/LANGUAGE-POLICY.adoc §1.4). +# +# For a caller with a package.json, one job per runtime proves that: +# * package.json is I-JSON + JCS (owner ruling D306; ijson-jcs); +# * that runtime's lockfile exists and installs FROZEN, so a lock that has +# fallen behind package.json goes red; +# * the `test` script, if any, passes under that runtime's own runner. +# The checks are scripts/js-runtimes.sh at this workflow's own commit; the +# fix for any finding is `scripts/js-runtimes.sh regen`. +# +# Usage (caller): +# jobs: +# js-runtimes: +# uses: hyperpolymath/standards/.github/workflows/js-runtimes-reusable.yml@ +# # with: +# # working-directory: web # default "." +# # runtimes: '["bun","pnpm"]' # default all three +# +# Runtimes come from the npm registry at pinned versions (npm checks each +# tarball's integrity), so this workflow adds no third-party action. +name: JS runtimes (reusable) + +on: + workflow_call: + inputs: + working-directory: + description: Directory holding package.json + type: string + default: "." + runtimes: + description: JSON array, a subset of ["bun","pnpm","deno"] + type: string + default: '["bun","pnpm","deno"]' + +permissions: + contents: read + +jobs: + check: + name: ${{ matrix.runtime }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + runtime: ${{ fromJSON(inputs.runtimes) }} + env: + BUN_VERSION: "1.4.2" + PNPM_VERSION: "10.28.0" + DENO_VERSION: "2.9.6" + IJSON_JCS_REV: 0fdeea716fd558e9807362acf6f8202d56d8d88f # same pin as ijson-jcs.yml (JSON-POLICY §3) + steps: + - name: Checkout caller repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Checkout standards for the checker + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + # This reusable's own commit, so the checker is exactly what the + # caller pinned (the pattern governance-reusable uses). + ref: ${{ job.workflow_sha }} + path: .standards-js + persist-credentials: false + sparse-checkout: | + scripts/js-runtimes.sh + sparse-checkout-cone-mode: false + - name: Install ${{ matrix.runtime }} and ijson-jcs + env: + RUNTIME: ${{ matrix.runtime }} + run: | + set -euo pipefail + case "$RUNTIME" in + bun) npm install -g "bun@${BUN_VERSION}" ;; + pnpm) npm install -g "pnpm@${PNPM_VERSION}" ;; + deno) npm install -g "deno@${DENO_VERSION}" ;; + *) echo "::error::unknown runtime '$RUNTIME'"; exit 1 ;; + esac + "$RUNTIME" --version + cargo install --git https://github.com/hyperpolymath/ijson-jcs --rev "$IJSON_JCS_REV" --locked ijson-jcs + - name: One manifest, ${{ matrix.runtime }} + env: + RUNTIME: ${{ matrix.runtime }} + WORKDIR: ${{ inputs.working-directory }} + run: | + set -uo pipefail + cp .standards-js/scripts/js-runtimes.sh "$RUNNER_TEMP/js-runtimes.sh" + rm -rf .standards-js + bash "$RUNNER_TEMP/js-runtimes.sh" check "$RUNTIME" "$WORKDIR" diff --git a/.github/workflows/self-test.yml b/.github/workflows/self-test.yml index ea4288cdc..fda49fb8a 100644 --- a/.github/workflows/self-test.yml +++ b/.github/workflows/self-test.yml @@ -55,6 +55,11 @@ jobs: - name: Install ijson-jcs run: cargo install --git https://github.com/hyperpolymath/ijson-jcs --rev 0fdeea716fd558e9807362acf6f8202d56d8d88f --locked ijson-jcs + # scripts/tests/js-runtimes-test.sh needs all three runtimes; it exits 2 + # without them. Same versions as js-runtimes-reusable.yml (keep in step). + - name: Install bun, pnpm and deno + run: npm install -g bun@1.4.2 pnpm@10.28.0 deno@2.9.6 + - name: Run tests/*.sh and scripts/tests/*.sh run: bash scripts/run-shell-test-suite.sh diff --git a/3-practice/JSON-POLICY.adoc b/3-practice/JSON-POLICY.adoc index 588100e2a..1d0509f06 100644 --- a/3-practice/JSON-POLICY.adoc +++ b/3-practice/JSON-POLICY.adoc @@ -113,8 +113,11 @@ would differ from another JCS implementation's. * *Bot-written files.* Some JSON is written by tools that rewrite it in their own layout: `package.json` (`bun add`), `.hypatia-baseline.json`, scanner - output. Converting those is undone on the next tool run. Whether they are - carved out, or the tools are changed, is open. + output. Converting those is undone on the next tool run. For `package.json` + the answer is PROPOSED in LANGUAGE-POLICY §1.4: `scripts/js-runtimes.sh regen` + re-canonicalises it after the tool, and `js-runtimes-reusable.yml` fails a + forgotten run. For the rest, whether they are carved out or the tools are + changed is open. * *Mass reformatting.* Convert a repository's JSON in a PR scoped to that repository, after checking each consumer reads it by parsing, not by line. Do not reformat estate-wide outside a PR that adds the gate. diff --git a/3-practice/LANGUAGE-POLICY.adoc b/3-practice/LANGUAGE-POLICY.adoc index 2d8131b2f..5613fd55a 100644 --- a/3-practice/LANGUAGE-POLICY.adoc +++ b/3-practice/LANGUAGE-POLICY.adoc @@ -28,13 +28,17 @@ Ordered preference. Reach for the first one that can do the job. | 2 | *pnpm* | Second. Only where an upstream toolchain requires a node_modules layout. + Also a generated compatibility target of every Bun package (§1.4). | 3 | *npm* | Last resort. Permitted, never preferred. Reaching for npm should be a deliberate, noted decision — not a default. | — | *Deno* -| *BANNED.* Removed from the ordering 2026-09-22 — see §1.3. +| *BANNED as an authoring target* (no `deno.json`). Removed from the + ordering 2026-09-22 — see §1.3. A generated `deno.lock` next to a + `package.json` is a compatibility output, not a Deno project (§1.4, + PROPOSED). |=== === 1.1 What this replaced @@ -141,6 +145,35 @@ Measured 2026-09-22 across the 23 tracked config files in the 11 ledgered repositories: *95* task definitions to port. That is the substantive work, and the reason this is a ledger rather than a flag day. +=== 1.4 One manifest, three runtimes + +PROPOSED 2026-10-14, on the owner's direction ("bun is the main one from now +on … a form of generative thing that always produces bun, deno, and pnpm +outputs … the problem of more than one was making sure it was always working +and up to date"; decision record +`docs/decisions/2026-10-14-one-manifest-three-runtimes.adoc`). + +`package.json` is the only manifest. Bun, pnpm and Deno 2 all read it +natively, so nothing is translated and no second manifest can drift. Each +runtime's own tool writes its own lockfile: `bun.lock` (tier 1, required), +`pnpm-lock.yaml` and `deno.lock` (generated compatibility locks, never +hand-edited). + +Writer:: `scripts/js-runtimes.sh regen` is the only writer. It makes +`package.json` I-JSON + JCS (D306, `ijson-jcs fix`) and regenerates all three +locks. Run it after every `bun add` / `pnpm add`. +Gate:: `.github/workflows/js-runtimes-reusable.yml` runs one job per runtime, +each running `scripts/js-runtimes.sh check `. Each job fails on a +non-canonical `package.json`, a missing lock, a stale lock (the frozen install +refuses it), or a failing `test` script under that runtime's runner. +`scripts/tests/js-runtimes-test.sh` proves each failure. +Scope of §1.3:: Unchanged for authoring: no `deno.json` / `deno.jsonc`, and no +Deno-only APIs in the package. Changed for outputs: a generated `deno.lock` +is not a Deno project. + +Test scripts must be runtime-neutral. A `test` script that calls one runtime +by name passes on every leg while testing only that runtime. + == 2. Package management RULED 2026-05-18. Two tiers, in order: diff --git a/docs/decisions/2026-10-14-one-manifest-three-runtimes.adoc b/docs/decisions/2026-10-14-one-manifest-three-runtimes.adoc new file mode 100644 index 000000000..542190d85 --- /dev/null +++ b/docs/decisions/2026-10-14-one-manifest-three-runtimes.adoc @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MPL-2.0 += Decision – One package.json, three JavaScript runtimes +:date: 2026-10-14 +:author: Jonathan D.A. Jewell (hyperpolymath) +:status: PROPOSED (owner direction 2026-10-14; this PR is the mechanism for review) +:scope: estate + +== Context + +The owner, 2026-10-14, verbatim: + +[quote] +____ +bun is the main one from now on. but I wonder, is there a way of estate-wide +making it so that there is a form of generative thing that always produces +bun, deno, and pnpm outputs for these things - the problem of more than one +was making sure it was always working and up to date - to do that opens up +all three options I would be happy with rather than forcing one +____ + +and, asked whether to build it, "yes but ensure this is ijson-jcs" (D306: +every `.json` is I-JSON, serialised by JCS). + +The problem the owner names is drift. When the estate had several runtimes, +each with its own manifest, keeping them in step and keeping each one +working was a manual job. That cost is part of why Deno was banned +(LANGUAGE-POLICY §1.3). + +== Decision + +*One manifest, three runtimes.* `package.json` is the only manifest. Bun +(tier 1), pnpm and Deno 2 all read it natively: dependencies through their +installers, and `scripts` through `bun run`, `pnpm run` and `deno task`. +Nothing is translated, so there is no second manifest to drift. + +What differs per runtime is the lockfile, and each runtime's own tool writes +its own: + +[cols="1,2,2",options="header"] +|=== +| Runtime | Lockfile | Role +| Bun | `bun.lock` | Tier 1. The lock governance requires (`governance-reusable.yml`, npm/bun step). +| pnpm | `pnpm-lock.yaml` | Generated compatibility lock. Tool-owned (YAML-POLICY §6). +| Deno | `deno.lock` | Generated compatibility lock. No `deno.json`: Deno stays banned as an authoring target (§1.3). +|=== + +*The generator* is `scripts/js-runtimes.sh regen`. It is the only writer of +`package.json` formatting and of the three locks. It: + +* runs `ijson-jcs fix package.json`; +* regenerates `bun.lock` (`bun install --lockfile-only`), `pnpm-lock.yaml` +(`pnpm install --lockfile-only`) and `deno.lock` (`deno install`); +* re-canonicalises `package.json` in case a tool rewrote it. + +*The gate* is `.github/workflows/js-runtimes-reusable.yml`. It runs one job +per runtime, each running `scripts/js-runtimes.sh check `, which +fails when: + +* `package.json` is not I-JSON + JCS; +* the runtime's lockfile is missing; +* a frozen install refuses the lockfile (it is stale against `package.json`); +* the `test` script fails under that runtime's own runner. + +So "always working and up to date" is checked on every push, under each +runtime. + +== Consequences + +* JSON-POLICY §6 left `package.json` open as a bot-written file: `bun add` +rewrites it in its own layout, undoing JCS. Running `regen` after +`bun add` / `pnpm add` puts it back, and the gate catches a forgotten run. +That closes `package.json`'s part of §6. +* Deno's ban changes in scope, not in force. A `deno.json` / `deno.jsonc` +is still refused (ci-pipeline `deno` job, `.githooks`, ledger). A generated +`deno.lock` next to a `package.json` is a compatibility output, not a Deno +project. +* Test scripts must not name one runtime. A `"test": "bun test"` passes on +the pnpm and Deno legs only because it shells out to Bun. Use a +runtime-neutral command, or restrict `runtimes:` for that repository. +* Deno does not record `file:` dependencies in `deno.lock`, so a stale +`file:` dependency is caught by the Bun and pnpm legs only. This was measured +2026-10-14 with Deno 2.9.6. Registry dependencies are caught on all three +legs (`scripts/tests/js-runtimes-test.sh`). +* Pinned tool versions: Bun 1.4.2, pnpm 10.28.0, Deno 2.9.6, ijson-jcs +`0fdeea71`. They are installed from the npm registry (and cargo, for +ijson-jcs), so the gate adds no third-party action. They are pinned in two +places, `js-runtimes-reusable.yml` and `self-test.yml`, which must be bumped +together. + +== Not decided here + +* Whether the reusable joins `governance-reusable.yml`, or is opt-in per +repository. As written it is opt-in. +* Whether a repository may drop a runtime leg (the `runtimes:` input allows +it) without an exemption record. diff --git a/scripts/js-runtimes.sh b/scripts/js-runtimes.sh new file mode 100755 index 000000000..fced63a71 --- /dev/null +++ b/scripts/js-runtimes.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# js-runtimes.sh — one package.json, three runtimes (LANGUAGE-POLICY §1.4). +# +# js-runtimes.sh regen [DIR] the only writer: canonicalise +# DIR/package.json (ijson-jcs fix) and +# regenerate bun.lock, pnpm-lock.yaml +# and deno.lock, each with its own tool +# js-runtimes.sh check RUNTIME [DIR] the gate, for one of bun|pnpm|deno: +# package.json is I-JSON + JCS (D306), +# RUNTIME's lockfile exists and installs +# FROZEN (a stale lock fails), and the +# `test` script, if any, passes under +# RUNTIME's own runner +# +# Bun, pnpm and Deno 2 all read package.json natively, so there is no second +# manifest to keep in step: no deno.json (Deno stays banned as an authoring +# target, §1.3) and no translation layer. What differs per runtime is the +# lockfile, and each runtime writes its own. Bun is tier 1 and its lock is +# the one governance requires; pnpm-lock.yaml and deno.lock are generated +# compatibility locks, never hand-edited. +# +# Exit: 0 pass; 1 a finding (not canonical, missing or stale lock, failing +# test); 2 cannot run (missing tool or manifest). A gate that cannot look +# does not report a pass. +# +# Tools: ijson-jcs (on PATH or $IJSON_JCS) and the runtime(s) concerned. +set -uo pipefail + +IJSON_JCS="${IJSON_JCS:-ijson-jcs}" +RUNTIMES=(bun pnpm deno) + +die() { printf 'js-runtimes: %s\n' "$1" >&2; exit "${2:-2}"; } +note() { printf 'js-runtimes: %s\n' "$1"; } + +lockfile_of() { + case "$1" in + bun) echo bun.lock ;; + pnpm) echo pnpm-lock.yaml ;; + deno) echo deno.lock ;; + *) die "unknown runtime '$1' (want one of: ${RUNTIMES[*]})" ;; + esac +} + +need() { command -v "$1" >/dev/null 2>&1 || die "$1 is not on PATH (cannot run, not a pass)"; } + +has_test_script() { # has_test_script RUNTIME — read package.json with that runtime's engine + case "$1" in + deno) deno eval --quiet 'const p=JSON.parse(Deno.readTextFileSync("package.json"));Deno.exit(p.scripts&&typeof p.scripts.test==="string"?0:1)' ;; + # pnpm runs on Node, so Node is already present on a pnpm leg. + pnpm) node -e 'const p=JSON.parse(require("fs").readFileSync("package.json","utf8"));process.exit(p.scripts&&typeof p.scripts.test==="string"?0:1)' ;; + bun) bun -e 'const p=JSON.parse(require("fs").readFileSync("package.json","utf8"));process.exit(p.scripts&&typeof p.scripts.test==="string"?0:1)' ;; + esac +} + +regen() { + local dir="${1:-.}" + cd -- "$dir" || die "no directory $dir" + [ -f package.json ] || die "no package.json in $dir" + need "$IJSON_JCS"; need bun; need pnpm; need deno + [ ! -e bun.lockb ] || die "bun.lockb (binary) present: remove it; bun.lock is the text lock" 1 + + "$IJSON_JCS" fix package.json >/dev/null || die "package.json is not I-JSON (ijson-jcs fix refused it)" 1 + bun install --lockfile-only >/dev/null || die "bun could not resolve package.json" 1 + pnpm install --lockfile-only >/dev/null || die "pnpm could not resolve package.json" 1 + # deno has no lockfile-only mode; it also populates node_modules. + deno install >/dev/null || die "deno could not resolve package.json" 1 + # A tool that rewrote package.json while resolving is put back in canon. + "$IJSON_JCS" fix package.json >/dev/null || die "package.json stopped being I-JSON during regen" 1 + note "regenerated in $(pwd): package.json (JCS), bun.lock, pnpm-lock.yaml, deno.lock" +} + +check() { + local rt="$1" dir="${2:-.}" lock rc + # Validate here: a die inside $(...) would only end the subshell. + case " ${RUNTIMES[*]} " in *" $rt "*) ;; *) die "unknown runtime '$rt' (want one of: ${RUNTIMES[*]})" ;; esac + lock="$(lockfile_of "$rt")" + cd -- "$dir" || die "no directory $dir" + [ -f package.json ] || die "no package.json in $dir" + need "$IJSON_JCS"; need "$rt" + + rc=0; "$IJSON_JCS" check package.json >/dev/null 2>&1 || rc=$? + case "$rc" in + 0) note "package.json is I-JSON + JCS" ;; + 1) die "package.json is not JCS-canonical: run scripts/js-runtimes.sh regen" 1 ;; + *) die "package.json is not I-JSON (ijson-jcs exit $rc)" 1 ;; + esac + + [ -f "$lock" ] || die "$lock is missing: run scripts/js-runtimes.sh regen" 1 + case "$rt" in + bun) bun install --frozen-lockfile ;; + pnpm) pnpm install --frozen-lockfile ;; + deno) deno install --frozen ;; + esac >/dev/null 2>&1 || die "$rt: $lock is stale against package.json (frozen install refused): run scripts/js-runtimes.sh regen" 1 + note "$rt installs frozen from $lock" + + if has_test_script "$rt"; then + case "$rt" in + bun) bun run test ;; + pnpm) pnpm run test ;; + deno) deno task test ;; + esac || die "$rt: the test script failed" 1 + note "$rt ran the test script" + else + note "$rt: no test script (install-only check)" + fi +} + +case "${1:-}" in + regen) shift; regen "$@" ;; + check) shift; [ $# -ge 1 ] || die "usage: js-runtimes.sh check bun|pnpm|deno [DIR]"; check "$@" ;; + *) die "usage: js-runtimes.sh regen [DIR] | check bun|pnpm|deno [DIR]" ;; +esac diff --git a/scripts/tests/js-runtimes-test.sh b/scripts/tests/js-runtimes-test.sh new file mode 100755 index 000000000..45e4a67ee --- /dev/null +++ b/scripts/tests/js-runtimes-test.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Proves scripts/js-runtimes.sh CAN FAIL, for the right reason, under each of +# bun, pnpm and deno: a regenerated package passes; a non-canonical +# package.json, a missing lock, a stale lock and a failing test script each +# fail; a missing tool is "cannot run" (exit 2), never a pass. +# +# Needs bun, pnpm, deno and ijson-jcs (PATH or $IJSON_JCS), and the npm +# registry: the fixture depends on two tiny zero-dependency packages, because +# deno.lock does not record file: dependencies, so an offline fixture could +# never show a stale deno.lock. +set -uo pipefail +SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/js-runtimes.sh" +IJSON_JCS="${IJSON_JCS:-ijson-jcs}" +for t in bun pnpm deno "$IJSON_JCS"; do + command -v "$t" >/dev/null 2>&1 || { echo "js-runtimes-test: $t is not on PATH (cannot run, not a pass)" >&2; exit 2; } +done +WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT + +pass=0; fail=0 +expect() { # expect