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"