diff --git a/merge-check-typescript.sh b/merge-check-typescript.sh new file mode 100644 index 00000000..4a66f9da --- /dev/null +++ b/merge-check-typescript.sh @@ -0,0 +1,96 @@ +#!/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 is said NOT CHECKED, by name; +# 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. +# +# 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. +set -eu + +deps="${TOOLCHAIN_MODULES:-/app/node_modules}" +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) + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +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 "NOT CHECKED: $d depends on $missing, which the toolchain does not hold and the check cannot fetch" + 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 diff --git a/merge-check.sh b/merge-check.sh index 2b07c81f..2d87f1a3 100755 --- a/merge-check.sh +++ b/merge-check.sh @@ -1,5 +1,6 @@ #!/bin/sh # mesh-check-toolchain: go +# mesh-check-also: typescript merge-check-typescript.sh # # The catalogue's own check (novox/hq ADR 0237 as amended): the second layer of a pull request's merge # check, `mesh/repo-check`, run by the build seat in the mesh's Go toolchain. @@ -13,8 +14,9 @@ # it shares a rule with refused (MESH_GATE is the controller the mesh runs) — and the count of # long-running resources without `health` held to the number in health-undeclared, which only goes down; # 2. the Go tests of every module the change touches that has them, under the race detector when the -# toolchain has a C compiler — and a module whose dependencies cannot be fetched here, or that is -# written in TypeScript, is said as not tested, never passed silently; +# toolchain has a C compiler — and a module whose dependencies cannot be fetched here is said as not +# tested, never passed silently. A module written in TypeScript is type-checked and tested by +# merge-check-typescript.sh, declared above, in the TypeScript toolchain (novox/hq issues 302, 311); # 3. the words (novox/hq ADR 0244): no word the glossary retired for the tools, as copied in # retired-words, in any text a module's tools can show an agent — every string in its own code that is # not a test, and every description in its manifest. Run over every module, not only the touched ones, @@ -73,7 +75,7 @@ fi for m in $touched; do [ -d "modules/$m" ] || continue if [ ! -f "modules/$m/go.mod" ]; then - [ -f "modules/$m/package.json" ] && echo "NOT TESTED HERE: modules/$m is TypeScript; the gate judges its manifest" + [ -f "modules/$m/package.json" ] && echo "modules/$m is TypeScript: merge-check-typescript.sh type-checks and tests it" continue fi if ! (cd "modules/$m" && GOPRIVATE=git.novox.be go mod download >/dev/null 2>&1); then