mesh/merge-gate head 3e16c055be8e merged into trunk 0361c44a03a0 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 3e16c055be8e merged into trunk 0361c44a03a0 — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check-typescript.sh): main's version judged…
mesh/delivery delivered
Outside the image, assemble the same node_modules in the check's own workspace from mesh-tools' lock on the forge, so a module's TypeScript is checked locally before a push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
7.7 KiB
Bash
141 lines
7.7 KiB
Bash
#!/bin/sh
|
|
# The catalogue's TypeScript part of its own check (novox/hq issue 302's `mesh-check-also`), declared in
|
|
# merge-check.sh's header and run by the build seat in the TypeScript toolchain, after the Go part. The media
|
|
# catalogue's twin is issue 311. Before this, a TypeScript module's change ran nothing of its own before it
|
|
# merged: the Go part said "NOT TESTED HERE", and a type error in the forge module would first have shown in
|
|
# its walk.
|
|
#
|
|
# For every touched module that has a package.json, in a copy of it outside the clone:
|
|
#
|
|
# 1. its dependencies are the toolchain's own (/app/node_modules: the mesh's SDK, Node's types and what the
|
|
# runtime carries), copied beside it, never fetched: the check holds no credential for the package
|
|
# registry. A module that depends on a package the toolchain does not hold **fails the check**, by name:
|
|
# it could not be type-checked or tested, and what could not run is a failure, never a note (novox/hq
|
|
# issue 385);
|
|
# 2. every source that is not a test is type-checked with the toolchain's compiler and the flags a build
|
|
# compiles with — the same check the build makes, before the merge rather than in the walk;
|
|
# 3. its tests (test/*.test.ts) run under Node's own runner with types stripped, as its own `npm test` does
|
|
# after its build: the sources are compiled to dist/ and beside themselves first, so a test that imports
|
|
# the compiled module, or a source that imports its sibling by its .js name, finds it.
|
|
#
|
|
# Said, never passed silently: a module with no tests is said NOT TESTED on every run that touches it —
|
|
# nothing of it could fail. A module whose tests exist and could not run fails (novox/hq issue 385).
|
|
#
|
|
# By hand: MESH_CHECK_MODULES=gitea sh merge-check-typescript.sh, in the toolchain image (it reads
|
|
# /app/node_modules), or with TOOLCHAIN_MODULES pointing at a node_modules that holds the same.
|
|
#
|
|
# **In an agent's session, outside the toolchain image** (novox/hq issue 742), the same node_modules is
|
|
# assembled first, in a throwaway directory under the check's own workspace, the way the image's `deps` stage
|
|
# assembles it: the toolchain's own package.json and lock (mesh-tools' node-tools/, read from the forge this
|
|
# clone came from, so the list is never copied here) installed with `npm ci`, then the SDK the mesh last
|
|
# published (`@novox/mesh-sdk@latest`), the @novox scope from the forge's package registry, read without a
|
|
# credential. Node and npm are the dev-tools module's; nothing is installed on the machine, no npm
|
|
# configuration or cache of the account's is read or written. Only when neither TOOLCHAIN_MODULES nor
|
|
# /app/node_modules is there: the forge's check, in the image, fetches nothing, as before. The forge is
|
|
# MESH_FORGE (default: the origin remote's), the owner MESH_FORGE_OWNER (default: the origin's owner).
|
|
set -eu
|
|
|
|
work=$(mktemp -d)
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
deps="${TOOLCHAIN_MODULES:-/app/node_modules}"
|
|
if [ -z "${TOOLCHAIN_MODULES:-}" ] && [ ! -f "$deps/typescript/bin/tsc" ]; then
|
|
origin=$(git remote get-url origin 2>/dev/null || true)
|
|
forge="${MESH_FORGE:-$(printf '%s\n' "$origin" | sed -n 's#^\(https\{0,1\}://[^/]*\)/.*#\1#p')}"
|
|
owner="${MESH_FORGE_OWNER:-$(printf '%s\n' "$origin" | sed -n 's#^https\{0,1\}://[^/]*/\([^/]*\)/.*#\1#p')}"
|
|
if [ -z "$forge" ] || [ -z "$owner" ]; then
|
|
echo "NOT CHECKED: no TypeScript toolchain at $deps, and no forge to assemble one from (origin is '$origin'; set MESH_FORGE and MESH_FORGE_OWNER, or TOOLCHAIN_MODULES)"
|
|
exit 1
|
|
fi
|
|
echo "no TypeScript toolchain at $deps: assembling one from $forge/$owner/mesh-tools node-tools/ and $owner's package registry (novox/hq issue 742)"
|
|
tc="$work/toolchain"
|
|
mkdir -p "$tc"
|
|
printf '@%s:registry=%s/api/packages/%s/npm/\n' "$owner" "$forge" "$owner" >"$work/npmrc"
|
|
# Read with Node's own fetch (dev-tools declares Node, not curl); a status that is not 2xx fails.
|
|
if ! { node -e '
|
|
const [base, dir] = process.argv.slice(1);
|
|
Promise.all(["package.json", "package-lock.json"].map(async (f) => {
|
|
const r = await fetch(base + f);
|
|
if (!r.ok) throw new Error(base + f + ": " + r.status);
|
|
require("fs").writeFileSync(dir + "/" + f, await r.text());
|
|
})).catch((e) => { console.error(e.message); process.exit(1); });' \
|
|
"$forge/$owner/mesh-tools/raw/branch/main/node-tools/" "$tc" &&
|
|
(cd "$tc" && export npm_config_userconfig="$work/npmrc" npm_config_cache="$work/npm-cache" &&
|
|
npm ci --ignore-scripts --no-audit --no-fund --loglevel=error &&
|
|
npm install --ignore-scripts --no-audit --no-fund --no-save --loglevel=error "@$owner/mesh-sdk@latest"); }; then
|
|
echo "NOT CHECKED: the TypeScript toolchain could not be assembled from $forge (novox/hq issue 742)"
|
|
exit 1
|
|
fi
|
|
deps="$tc/node_modules"
|
|
fi
|
|
tsc="$deps/typescript/bin/tsc"
|
|
if [ ! -f "$tsc" ]; then
|
|
echo "NOT CHECKED: no TypeScript compiler at $tsc — this part runs in the TypeScript toolchain"
|
|
exit 1
|
|
fi
|
|
|
|
# The modules the change reaches: the planner's answer (hq ADR 0238), and every module a changed file is in —
|
|
# a change to a module's tests alone moves no build (hq ADR 0267), and its tests must still run.
|
|
touched=$({
|
|
printf '%s\n' "${MESH_CHECK_MODULES:-}" | tr ',' '\n' | sed -e 's#^modules/##' -e '/\//d'
|
|
printf '%s\n' "${MESH_CHECK_CHANGED:-}" | tr ',' '\n' | sed -n 's#^modules/\([^/]*\)/.*#\1#p'
|
|
} | sed '/^$/d' | sort -u)
|
|
|
|
failed=""
|
|
for m in $touched; do
|
|
d="modules/$m"
|
|
[ -f "$d/package.json" ] || continue
|
|
copy="$work/$m"
|
|
mkdir -p "$copy"
|
|
(cd "$d" && tar --exclude=./node_modules --exclude=./dist -cf - .) | (cd "$copy" && tar -xf -)
|
|
# What it depends on, against what the toolchain holds.
|
|
missing=$(node -e '
|
|
const p = require(process.argv[1]); const have = require("fs").readdirSync(process.argv[2]);
|
|
const scoped = (n) => n.startsWith("@") ? n.split("/")[0] : n;
|
|
const names = Object.keys({ ...(p.dependencies || {}), ...(p.devDependencies || {}) });
|
|
const held = (n) => have.includes(n) || (n.startsWith("@") && have.includes(scoped(n)) &&
|
|
require("fs").existsSync(process.argv[2] + "/" + n));
|
|
console.log(names.filter((n) => n !== "typescript" && !held(n)).join(" "));' "$copy/package.json" "$deps")
|
|
if [ -n "$missing" ]; then
|
|
echo "FAILED: $d could not be checked — it depends on $missing, which the toolchain does not hold and the check cannot fetch (novox/hq issue 385)"
|
|
failed="$failed $m"
|
|
continue
|
|
fi
|
|
mkdir -p "$copy/node_modules"
|
|
for p in "$deps"/*; do
|
|
case "$(basename "$p")" in typescript | esbuild | @esbuild) continue ;; esac
|
|
cp -r "$p" "$copy/node_modules/"
|
|
done
|
|
sources=$(cd "$copy" && find . -name '*.ts' ! -name '*.d.ts' ! -path './test/*' ! -path './node_modules/*' ! -path './dist/*' | sort)
|
|
if [ -n "$sources" ]; then
|
|
echo "type-checking $d (TypeScript)"
|
|
# shellcheck disable=SC2086 # the module's sources, none with a space in its name
|
|
if ! (cd "$copy" && node "$tsc" --noEmit --module NodeNext --moduleResolution NodeNext --target ES2022 \
|
|
--types node $sources); then
|
|
failed="$failed $m"
|
|
continue
|
|
fi
|
|
else
|
|
echo "$d has no TypeScript sources of its own to type-check: its tests read its files"
|
|
fi
|
|
if ! ls "$copy"/test/*.test.ts >/dev/null 2>&1; then
|
|
echo "NOT TESTED: $d is TypeScript and has no tests of its own"
|
|
continue
|
|
fi
|
|
if [ -n "$sources" ]; then
|
|
# shellcheck disable=SC2086
|
|
(cd "$copy" && node "$tsc" --module NodeNext --moduleResolution NodeNext --target ES2022 --types node \
|
|
--rootDir . --outDir dist $sources && node "$tsc" --module NodeNext --moduleResolution NodeNext \
|
|
--target ES2022 --types node --rootDir . --outDir . $sources)
|
|
fi
|
|
echo "testing $d (TypeScript)"
|
|
if ! (cd "$copy" && node --test --test-reporter=spec --experimental-strip-types \
|
|
--no-warnings=ExperimentalWarning test/*.test.ts); then
|
|
failed="$failed $m"
|
|
fi
|
|
done
|
|
if [ -n "$failed" ]; then
|
|
echo "TypeScript modules that failed:$failed"
|
|
exit 1
|
|
fi
|