From 3e16c055be8ec4744da084b459ff54aba401a78a Mon Sep 17 00:00:00 2001 From: mesh-admin Date: Wed, 14 Oct 2026 21:03:53 +0000 Subject: [PATCH] Let an agent's session run the TypeScript check outside the toolchain image (issue 742) 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 --- merge-check-typescript.sh | 44 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/merge-check-typescript.sh b/merge-check-typescript.sh index 3b0ed856..b32bb08f 100644 --- a/merge-check-typescript.sh +++ b/merge-check-typescript.sh @@ -23,9 +23,51 @@ # # 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" @@ -39,8 +81,6 @@ touched=$({ 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"