From 58bd3d1cfb01a14f39a73b7df9cac63c5eb4d035 Mon Sep 17 00:00:00 2001 From: gabroberge Date: Sun, 17 May 2026 01:53:28 -0400 Subject: [PATCH 1/3] fix(repo): avoid --experimental-transform-types in NODE_OPTIONS on Node 26+ --- ark/repo/nodeOptions.js | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ark/repo/nodeOptions.js b/ark/repo/nodeOptions.js index c629a0fdba..c5dfc8f281 100644 --- a/ark/repo/nodeOptions.js +++ b/ark/repo/nodeOptions.js @@ -1,14 +1,33 @@ // @ts-check +/** @param {number} major @returns {string} */ +function getFallbackReason(major) { + if (major >= 26) { + return "Node.js >= 26 does not allow --experimental-transform-types in NODE_OPTIONS; falling back to tsx..." + } + + return "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." +} + +/** + * @param {number} major + * @param {number} minor + * @returns {string} + */ +function versionedFlagsFor(major, minor) { + const useExperimentalTransformTypes = + (major > 22 && major < 26) || (major === 22 && minor >= 7) + if (useExperimentalTransformTypes) { + return "--experimental-transform-types --no-warnings" + } + + console.log(getFallbackReason(major)) + return "--import tsx" +} + const [major, minor] = process.version.replace("v", "").split(".").map(Number) -const versionedFlags = - major > 22 || (major === 22 && minor >= 7) ? - "--experimental-transform-types --no-warnings" - : (console.log( - "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." - ), - "--import tsx") +const versionedFlags = versionedFlagsFor(major, minor) export const nodeDevOptions = `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}` From c48649007e307430f43fc254cca0c2b155b0d1e1 Mon Sep 17 00:00:00 2001 From: David Blass Date: Tue, 23 Jun 2026 09:39:04 -0400 Subject: [PATCH 2/3] fix(repo): rely on native type stripping instead of --experimental-transform-types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node >= 26 rejects `--experimental-transform-types` in NODE_OPTIONS. Since this repo contains no syntax that requires type *transformation* (only erasable `declare`/type-level forms — the regex `enum` is inside a `declare namespace`), native type *stripping* is sufficient: - pass no flag where Node strips by default (>= 22.18 / 23.6 / 24+, incl. 26) - use `--experimental-strip-types` on the flagged range (22.6–23.5) - fall back to tsx below 22.6 Also de-sugar the one parameter property in realWorld.test.ts so the entire codebase is strip-compatible. Co-Authored-By: Claude Opus 4.8 --- ark/repo/nodeOptions.js | 41 ++++++++++++++++++---------- ark/type/__tests__/realWorld.test.ts | 5 +++- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/ark/repo/nodeOptions.js b/ark/repo/nodeOptions.js index c5dfc8f281..8c00ca6372 100644 --- a/ark/repo/nodeOptions.js +++ b/ark/repo/nodeOptions.js @@ -1,12 +1,23 @@ // @ts-check -/** @param {number} major @returns {string} */ -function getFallbackReason(major) { - if (major >= 26) { - return "Node.js >= 26 does not allow --experimental-transform-types in NODE_OPTIONS; falling back to tsx..." - } +// This repo contains no TypeScript syntax that requires *transformation* +// (no runtime enums, parameter properties, or value-bearing namespaces — only +// erasable `declare`/type-level forms), so native type *stripping* is +// sufficient. That lets us avoid `--experimental-transform-types`, which Node +// >= 26 rejects in NODE_OPTIONS. + +/** @param {number} major @param {number} minor @returns {boolean} */ +function stripsTypesByDefault(major, minor) { + // type stripping became the default (no flag) in 23.6.0, backported to 22.18.0 + return ( + major >= 24 || (major === 23 && minor >= 6) || (major === 22 && minor >= 18) + ) +} - return "--experimental-transform-types requires Node >= 22.7.0, falling back to tsx..." +/** @param {number} major @param {number} minor @returns {boolean} */ +function stripsTypesBehindFlag(major, minor) { + // 22.6–22.17 and 23.0–23.5 can strip types, but only behind a flag + return (major === 22 && minor >= 6) || (major === 23 && minor < 6) } /** @@ -15,13 +26,14 @@ function getFallbackReason(major) { * @returns {string} */ function versionedFlagsFor(major, minor) { - const useExperimentalTransformTypes = - (major > 22 && major < 26) || (major === 22 && minor >= 7) - if (useExperimentalTransformTypes) { - return "--experimental-transform-types --no-warnings" - } + if (stripsTypesByDefault(major, minor)) return "" + + if (stripsTypesBehindFlag(major, minor)) + return "--experimental-strip-types --no-warnings" - console.log(getFallbackReason(major)) + console.log( + "native TypeScript support requires Node >= 22.6.0, falling back to tsx..." + ) return "--import tsx" } @@ -29,7 +41,8 @@ const [major, minor] = process.version.replace("v", "").split(".").map(Number) const versionedFlags = versionedFlagsFor(major, minor) -export const nodeDevOptions = `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}` +export const nodeDevOptions = + `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}`.trimEnd() export const addNodeDevOptions = extraOpts => - (process.env.NODE_OPTIONS = `${nodeDevOptions} ${extraOpts ?? ""}`) + (process.env.NODE_OPTIONS = `${nodeDevOptions} ${extraOpts ?? ""}`.trimEnd()) diff --git a/ark/type/__tests__/realWorld.test.ts b/ark/type/__tests__/realWorld.test.ts index 33539ddc9c..acdd6d08c2 100644 --- a/ark/type/__tests__/realWorld.test.ts +++ b/ark/type/__tests__/realWorld.test.ts @@ -1302,10 +1302,13 @@ Right: { x: number, y: number, + (undeclared): delete }`) it("described input of morph", () => { class ValidatedUserID { + readonly data: string static fromString(value: string): ValidatedUserID { return new ValidatedUserID(value) } - private constructor(readonly data: string) {} + private constructor(data: string) { + this.data = data + } } const UserID = type("string") From d1440ac19d0fa3161a2b40fe8134a348560fa5a4 Mon Sep 17 00:00:00 2001 From: David Blass Date: Tue, 23 Jun 2026 10:06:18 -0400 Subject: [PATCH 3/3] refactor(repo): drop tsx, rely solely on native type stripping Every Node version we support strips TypeScript types by default (engines floor bumped 18 -> 22.18.0, where unflagged stripping landed in the 22.x line; also 23.6+/24+). The codebase has no transform-only syntax, so no loader or flag is needed anywhere: - nodeOptions.js: collapse the version ladder; pass only --conditions ark-ts - remove `import=tsx` from all three synced mocha configs (root package.json, ark/repo/mocha.package.jsonc, .vscode/settings.json) - fast-check test + attest self-test spawn run via plain `node` - drop the `tsx` devDependency Verified tsx-free under native stripping: testTyped (1703 passing), fast-check integration (56), attest snapPopulation/self-tests (63). Co-Authored-By: Claude Opus 4.8 --- .vscode/settings.json | 2 +- ark/attest/__tests__/utils.ts | 2 +- ark/fast-check/package.json | 2 +- ark/repo/mocha.package.jsonc | 2 +- ark/repo/nodeOptions.js | 49 ++++++----------------------------- package.json | 6 ++--- pnpm-lock.yaml | 8 +++--- 7 files changed, 18 insertions(+), 53 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8823d9ef0b..3d6eca3019 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,7 +28,7 @@ "typescript.tsserver.experimental.enableProjectDiagnostics": true, "typescript.tsdk": "./node_modules/typescript/lib", // IF YOU UPDATE THE MOCHA CONFIG HERE, PLEASE ALSO UPDATE package.json/mocha AND ark/repo/mocha.jsonc - "mochaExplorer.nodeArgv": ["--conditions", "ark-ts", "--import", "tsx"], + "mochaExplorer.nodeArgv": ["--conditions", "ark-ts"], // ignore attest since it requires type information "mochaExplorer.ignore": ["ark/attest/**/*"], "mochaExplorer.require": "ark/repo/mocha.globalSetup.ts", diff --git a/ark/attest/__tests__/utils.ts b/ark/attest/__tests__/utils.ts index 5daf26e8ef..703d74d732 100644 --- a/ark/attest/__tests__/utils.ts +++ b/ark/attest/__tests__/utils.ts @@ -7,7 +7,7 @@ export const runThenGetContents = (templatePath: string): string => { const tempPath = templatePath + ".temp.ts" copyFileSync(templatePath, tempPath) try { - shell(`node --import=tsx ${tempPath}`, { + shell(`node ${tempPath}`, { cwd: dirName(), env: { ATTEST_failOnMissingSnapshots: "0" diff --git a/ark/fast-check/package.json b/ark/fast-check/package.json index dd844bbaf2..acd8e7d649 100644 --- a/ark/fast-check/package.json +++ b/ark/fast-check/package.json @@ -34,7 +34,7 @@ ], "scripts": { "build": "ts ../repo/build.ts", - "test": "tsx ../repo/testPackage.ts" + "test": "node ../repo/testPackage.ts" }, "dependencies": { "arktype": "workspace:*", diff --git a/ark/repo/mocha.package.jsonc b/ark/repo/mocha.package.jsonc index dfde0ace2a..3c5e01c1e6 100644 --- a/ark/repo/mocha.package.jsonc +++ b/ark/repo/mocha.package.jsonc @@ -8,6 +8,6 @@ { "spec": ["__tests__/*.test.*"], "ignore": "node_modules/**/*", - "node-option": ["conditions=ark-ts", "import=tsx"], + "node-option": ["conditions=ark-ts"], "require": "../repo/mocha.globalSetup.ts" } diff --git a/ark/repo/nodeOptions.js b/ark/repo/nodeOptions.js index 8c00ca6372..9cc86532f5 100644 --- a/ark/repo/nodeOptions.js +++ b/ark/repo/nodeOptions.js @@ -1,48 +1,15 @@ // @ts-check -// This repo contains no TypeScript syntax that requires *transformation* -// (no runtime enums, parameter properties, or value-bearing namespaces — only -// erasable `declare`/type-level forms), so native type *stripping* is -// sufficient. That lets us avoid `--experimental-transform-types`, which Node -// >= 26 rejects in NODE_OPTIONS. - -/** @param {number} major @param {number} minor @returns {boolean} */ -function stripsTypesByDefault(major, minor) { - // type stripping became the default (no flag) in 23.6.0, backported to 22.18.0 - return ( - major >= 24 || (major === 23 && minor >= 6) || (major === 22 && minor >= 18) - ) -} - -/** @param {number} major @param {number} minor @returns {boolean} */ -function stripsTypesBehindFlag(major, minor) { - // 22.6–22.17 and 23.0–23.5 can strip types, but only behind a flag - return (major === 22 && minor >= 6) || (major === 23 && minor < 6) -} - -/** - * @param {number} major - * @param {number} minor - * @returns {string} - */ -function versionedFlagsFor(major, minor) { - if (stripsTypesByDefault(major, minor)) return "" - - if (stripsTypesBehindFlag(major, minor)) - return "--experimental-strip-types --no-warnings" - - console.log( - "native TypeScript support requires Node >= 22.6.0, falling back to tsx..." - ) - return "--import tsx" -} - -const [major, minor] = process.version.replace("v", "").split(".").map(Number) - -const versionedFlags = versionedFlagsFor(major, minor) +// Every Node version we support (see `engines.node`) strips TypeScript types +// natively by default — no flag required. This repo also contains no syntax +// that needs type *transformation* (no runtime enums, parameter properties, or +// value-bearing namespaces — only erasable `declare`/type-level forms), so +// stripping alone is sufficient and we pass no TS-related flags. Notably this +// avoids `--experimental-transform-types`, which Node >= 26 rejects in +// NODE_OPTIONS. export const nodeDevOptions = - `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts ${versionedFlags}`.trimEnd() + `${process.env.NODE_OPTIONS ?? ""} --conditions ark-ts`.trimEnd() export const addNodeDevOptions = extraOpts => (process.env.NODE_OPTIONS = `${nodeDevOptions} ${extraOpts ?? ""}`.trimEnd()) diff --git a/package.json b/package.json index 8d618bf1db..58130e7c76 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "mocha": "11.7.4", "prettier": "3.6.2", "tsup": "8.5.0", - "tsx": "4.20.6", "typescript": "catalog:", "typescript-eslint": "8.46.1" }, @@ -85,8 +84,7 @@ ], "ignore": "**/node_modules/**/*", "node-option": [ - "conditions=ark-ts", - "import=tsx" + "conditions=ark-ts" ], "require": "./ark/repo/mocha.globalSetup.ts", "timeout": 10000 @@ -109,7 +107,7 @@ "arrowParens": "avoid" }, "engines": { - "node": ">=18", + "node": ">=22.18.0", "pnpm": ">=10" }, "packageManager": "pnpm@10.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cab8f6e63..5d4b898043 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,9 +89,6 @@ importers: tsup: specifier: 8.5.0 version: 8.5.0(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3) - tsx: - specifier: 4.20.6 - version: 4.20.6 typescript: specifier: 'catalog:' version: 5.9.3 @@ -8263,6 +8260,7 @@ snapshots: get-tsconfig@4.12.0: dependencies: resolve-pkg-maps: 1.0.0 + optional: true github-from-package@0.0.0: optional: true @@ -10099,7 +10097,8 @@ snapshots: resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} + resolve-pkg-maps@1.0.0: + optional: true resolve@1.22.10: dependencies: @@ -10668,6 +10667,7 @@ snapshots: get-tsconfig: 4.12.0 optionalDependencies: fsevents: 2.3.3 + optional: true tunnel-agent@0.6.0: dependencies: