From 437c524b81c78da4092a9f85ceeb1c32a51ea10b Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 23 Oct 2026 10:18:57 +0200 Subject: [PATCH] gitea, minio, mosquitto: tests run as the Node build module runs them, tsc -p . then npm test (issue 1891) The old TypeScript check compiled every source beside itself, and into dist/, before it ran the tests; mesh-build-node runs `tsc -p .` (these tsconfigs say noEmit) and then the package's test script, so: - gitea and minio: the tests import the .ts sources, which import each other as ./x.js. A resolve hook in test/ resolves such an import, when no .js is there, to the .ts beside it and compiles it with the toolchain's TypeScript (gitea's sources use parameter properties, which type stripping refuses). 57 of gitea's tests and minio's links test failed. - mosquitto: its ctrl test imports ../dist/client.js; its test script now builds first, as gitea's does. Test scripts and a test helper only; what is built and delivered is the same. --- modules/gitea/package.json | 2 +- modules/gitea/test/resolve-ts.mjs | 37 +++++++++++++++++++++++++++++++ modules/minio/package.json | 2 +- modules/minio/test/resolve-ts.mjs | 37 +++++++++++++++++++++++++++++++ modules/mosquitto/package.json | 2 +- 5 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 modules/gitea/test/resolve-ts.mjs create mode 100644 modules/minio/test/resolve-ts.mjs diff --git a/modules/gitea/package.json b/modules/gitea/package.json index 21a6feb5..008e85a7 100644 --- a/modules/gitea/package.json +++ b/modules/gitea/package.json @@ -6,7 +6,7 @@ "private": true, "scripts": { "build": "tsc client.ts token.ts index.ts provisioner/index.ts provisioner/teams.ts tools/index.ts --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist", - "test": "npm run build && node --test --experimental-strip-types 'test/*.test.ts'" + "test": "npm run build && node --import ./test/resolve-ts.mjs --test --experimental-strip-types 'test/*.test.ts'" }, "dependencies": { "@novox/mesh-sdk": "^0.1.1" diff --git a/modules/gitea/test/resolve-ts.mjs b/modules/gitea/test/resolve-ts.mjs new file mode 100644 index 00000000..e7197721 --- /dev/null +++ b/modules/gitea/test/resolve-ts.mjs @@ -0,0 +1,37 @@ +// The tests import the module's TypeScript sources (node --experimental-strip-types), and the sources import each other +// by the .js name tsc compiles them to (NodeNext). Nothing compiles them beside themselves before the tests run: the +// Node build module runs `tsc -p .` and then `npm test` (novox/hq issue 1891). So this hook resolves a relative .js import +// from a .ts file, when no such .js is there, to the .ts beside it, and compiles that file with the toolchain's own +// TypeScript (transpileModule: no type-check, which `tsc -p .` did), since a source may use what type stripping refuses +// (a parameter property). Every other import resolves and loads as Node does. +import { readFileSync } from "node:fs"; +import { registerHooks } from "node:module"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; + +const compiled = new Set(); + +registerHooks({ + resolve(specifier, context, nextResolve) { + try { + return nextResolve(specifier, context); + } catch (err) { + const relative = specifier.startsWith("./") || specifier.startsWith("../"); + if (err?.code !== "ERR_MODULE_NOT_FOUND" || !relative || !specifier.endsWith(".js") || !context.parentURL?.endsWith(".ts")) { + throw err; + } + const resolved = nextResolve(specifier.slice(0, -".js".length) + ".ts", context); + compiled.add(resolved.url); + return resolved; + } + }, + load(url, context, nextLoad) { + if (!compiled.has(url)) return nextLoad(url, context); + const file = fileURLToPath(url); + const out = ts.transpileModule(readFileSync(file, "utf8"), { + fileName: file, + compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022, sourceMap: false }, + }); + return { format: "module", source: out.outputText, shortCircuit: true }; + }, +}); diff --git a/modules/minio/package.json b/modules/minio/package.json index 017e2af8..d3351990 100644 --- a/modules/minio/package.json +++ b/modules/minio/package.json @@ -5,7 +5,7 @@ "type": "module", "private": true, "scripts": { - "test": "node --test --experimental-strip-types 'test/*.test.ts'" + "test": "node --import ./test/resolve-ts.mjs --test --experimental-strip-types 'test/*.test.ts'" }, "dependencies": { "@novox/mesh-sdk": "^0.1.7" diff --git a/modules/minio/test/resolve-ts.mjs b/modules/minio/test/resolve-ts.mjs new file mode 100644 index 00000000..e7197721 --- /dev/null +++ b/modules/minio/test/resolve-ts.mjs @@ -0,0 +1,37 @@ +// The tests import the module's TypeScript sources (node --experimental-strip-types), and the sources import each other +// by the .js name tsc compiles them to (NodeNext). Nothing compiles them beside themselves before the tests run: the +// Node build module runs `tsc -p .` and then `npm test` (novox/hq issue 1891). So this hook resolves a relative .js import +// from a .ts file, when no such .js is there, to the .ts beside it, and compiles that file with the toolchain's own +// TypeScript (transpileModule: no type-check, which `tsc -p .` did), since a source may use what type stripping refuses +// (a parameter property). Every other import resolves and loads as Node does. +import { readFileSync } from "node:fs"; +import { registerHooks } from "node:module"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; + +const compiled = new Set(); + +registerHooks({ + resolve(specifier, context, nextResolve) { + try { + return nextResolve(specifier, context); + } catch (err) { + const relative = specifier.startsWith("./") || specifier.startsWith("../"); + if (err?.code !== "ERR_MODULE_NOT_FOUND" || !relative || !specifier.endsWith(".js") || !context.parentURL?.endsWith(".ts")) { + throw err; + } + const resolved = nextResolve(specifier.slice(0, -".js".length) + ".ts", context); + compiled.add(resolved.url); + return resolved; + } + }, + load(url, context, nextLoad) { + if (!compiled.has(url)) return nextLoad(url, context); + const file = fileURLToPath(url); + const out = ts.transpileModule(readFileSync(file, "utf8"), { + fileName: file, + compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022, sourceMap: false }, + }); + return { format: "module", source: out.outputText, shortCircuit: true }; + }, +}); diff --git a/modules/mosquitto/package.json b/modules/mosquitto/package.json index 4c74b1fe..705ca055 100644 --- a/modules/mosquitto/package.json +++ b/modules/mosquitto/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "tsc topics.ts client.ts index.ts tools/index.ts provisioner/index.ts bootstrap/index.ts --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist", "typecheck": "tsc -p tsconfig.json", - "test": "node --test --experimental-strip-types 'test/*.test.ts'" + "test": "npm run build && node --test --experimental-strip-types 'test/*.test.ts'" }, "dependencies": { "@novox/mesh-sdk": "^0.1.1"