@@ -5,3 +5,2 @@ "use strict"; | ||
| const register_1 = require("../../../plugins/js/utils/register"); | ||
| const typescript_1 = require("../../../plugins/js/utils/typescript"); | ||
| const utils_1 = require("../../../tasks-runner/utils"); | ||
@@ -17,20 +16,9 @@ const workspace_root_1 = require("../../../utils/workspace-root"); | ||
| }); | ||
| // Try and load the provided (or default) changelog renderer | ||
| let ChangelogRendererClass; | ||
| let cleanupTranspiler = () => { }; | ||
| try { | ||
| const rootTsconfigPath = (0, typescript_1.getRootTsConfigPath)(); | ||
| if (rootTsconfigPath) { | ||
| cleanupTranspiler = (0, register_1.registerTsProject)(rootTsconfigPath); | ||
| } | ||
| const r = require(interpolatedChangelogRendererPath); | ||
| ChangelogRendererClass = r.default || r; | ||
| } | ||
| catch (err) { | ||
| throw err; | ||
| } | ||
| finally { | ||
| cleanupTranspiler(); | ||
| } | ||
| return ChangelogRendererClass; | ||
| // TS renderers go through loadTsFile (native-strip -> swc/ts-node + paths). | ||
| // JS renderers use require() with a lazy tsconfig-paths fallback so workspace | ||
| // alias imports still resolve, without paying registration cost up front. | ||
| const r = /\.[cm]?ts$/.test(interpolatedChangelogRendererPath) | ||
| ? (0, register_1.loadTsFile)(interpolatedChangelogRendererPath) | ||
| : (0, register_1.requireWithTsconfigFallback)(interpolatedChangelogRendererPath); | ||
| return r.default || r; | ||
| } |
@@ -7,3 +7,2 @@ "use strict"; | ||
| const register_1 = require("../../../plugins/js/utils/register"); | ||
| const typescript_1 = require("../../../plugins/js/utils/typescript"); | ||
| const utils_1 = require("../../../tasks-runner/utils"); | ||
@@ -56,8 +55,5 @@ const workspace_root_1 = require("../../../utils/workspace-root"); | ||
| else { | ||
| let cleanupTranspiler; | ||
| if (versionActionsPath.endsWith('.ts')) { | ||
| cleanupTranspiler = (0, register_1.registerTsProject)((0, typescript_1.getRootTsConfigPath)()); | ||
| } | ||
| const loaded = require(versionActionsPath); | ||
| cleanupTranspiler?.(); | ||
| const loaded = /\.[cm]?ts$/.test(versionActionsPath) | ||
| ? (0, register_1.loadTsFile)(versionActionsPath) | ||
| : (0, register_1.requireWithTsconfigFallback)(versionActionsPath); | ||
| VersionActionsClass = loaded.default ?? loaded; | ||
@@ -64,0 +60,0 @@ if (!VersionActionsClass) { |
@@ -9,4 +9,4 @@ "use strict"; | ||
| const resolve_exports_1 = require("resolve.exports"); | ||
| const register_1 = require("../plugins/js/utils/register"); | ||
| const packages_1 = require("../plugins/js/utils/packages"); | ||
| const plugins_1 = require("../project-graph/plugins"); | ||
| const path_2 = require("../utils/path"); | ||
@@ -23,6 +23,10 @@ /** | ||
| const modulePath = resolveImplementation(implementationModulePath, directory, packageName, projects); | ||
| if ((0, path_1.extname)(modulePath) === '.ts') { | ||
| (0, plugins_1.registerPluginTSTranspiler)(); | ||
| } | ||
| const module = require(modulePath); | ||
| // Route .ts entrypoints through loadTsFile so the native-strip -> | ||
| // swc/ts-node fallback chain runs. Plain require() bypasses the matcher | ||
| // set and bubbles errors like extensionless `./schema` imports (strict | ||
| // ESM resolution failures) straight to the CLI. JS entrypoints use | ||
| // requireWithTsconfigFallback so workspace-alias imports still resolve. | ||
| const module = /\.[cm]?ts$/.test(modulePath) | ||
| ? (0, register_1.loadTsFile)(modulePath) | ||
| : (0, register_1.requireWithTsconfigFallback)(modulePath); | ||
| return implementationExportName | ||
@@ -29,0 +33,0 @@ ? module[implementationExportName] |
@@ -28,3 +28,3 @@ /** | ||
| export * from './project-graph/error-types'; | ||
| export { registerTsProject } from './plugins/js/utils/register'; | ||
| export { registerTsProject, loadTsFile, forceRegisterEsmLoader, requireWithTsconfigFallback, } from './plugins/js/utils/register'; | ||
| export { interpolate } from './tasks-runner/utils'; | ||
@@ -31,0 +31,0 @@ export { isCI } from './utils/is-ci'; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.handleImport = exports.signalToCode = exports.globalSpinner = exports.readYamlFile = exports.isUsingPrettierInTree = exports.isCI = exports.interpolate = exports.registerTsProject = exports.LoadedNxPlugin = exports.retrieveProjectConfigurations = exports.findProjectForPath = exports.createProjectRootMappings = exports.createProjectRootMappingsFromProjectConfigurations = exports.hashMultiGlobWithWorkspaceContext = exports.hashWithWorkspaceContext = exports.hashObject = exports.splitByColons = exports.installPackageToTmpAsync = exports.installPackageToTmp = exports.readModulePackageJson = exports.stripIndent = exports.sortObjectByKeys = exports.combineOptionsForExecutor = exports.splitTarget = exports.getIgnoreObjectForTree = exports.normalizeTargetDefaultsAgainstRootMaps = exports.readTargetDefaultsForTarget = exports.findMatchingConfigFiles = exports.readProjectConfigurationsFromRootMap = exports.mergeTargetConfigurations = exports.retrieveProjectConfigurationsWithAngularProjects = exports.calculateDefaultProjectName = exports.readNxJsonFromDisk = exports.parseExecutor = exports.getExecutorInformation = exports.createTempNpmDirectory = void 0; | ||
| exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.handleImport = exports.signalToCode = exports.globalSpinner = exports.readYamlFile = exports.isUsingPrettierInTree = exports.isCI = exports.interpolate = exports.requireWithTsconfigFallback = exports.forceRegisterEsmLoader = exports.loadTsFile = exports.registerTsProject = exports.LoadedNxPlugin = exports.retrieveProjectConfigurations = exports.findProjectForPath = exports.createProjectRootMappings = exports.createProjectRootMappingsFromProjectConfigurations = exports.hashMultiGlobWithWorkspaceContext = exports.hashWithWorkspaceContext = exports.hashObject = exports.splitByColons = exports.installPackageToTmpAsync = exports.installPackageToTmp = exports.readModulePackageJson = exports.stripIndent = exports.sortObjectByKeys = exports.combineOptionsForExecutor = exports.splitTarget = exports.getIgnoreObjectForTree = exports.normalizeTargetDefaultsAgainstRootMaps = exports.readTargetDefaultsForTarget = exports.findMatchingConfigFiles = exports.readProjectConfigurationsFromRootMap = exports.mergeTargetConfigurations = exports.retrieveProjectConfigurationsWithAngularProjects = exports.calculateDefaultProjectName = exports.readNxJsonFromDisk = exports.parseExecutor = exports.getExecutorInformation = exports.createTempNpmDirectory = void 0; | ||
| const tslib_1 = require("tslib"); | ||
@@ -62,2 +62,5 @@ /** | ||
| Object.defineProperty(exports, "registerTsProject", { enumerable: true, get: function () { return register_1.registerTsProject; } }); | ||
| Object.defineProperty(exports, "loadTsFile", { enumerable: true, get: function () { return register_1.loadTsFile; } }); | ||
| Object.defineProperty(exports, "forceRegisterEsmLoader", { enumerable: true, get: function () { return register_1.forceRegisterEsmLoader; } }); | ||
| Object.defineProperty(exports, "requireWithTsconfigFallback", { enumerable: true, get: function () { return register_1.requireWithTsconfigFallback; } }); | ||
| var utils_1 = require("./tasks-runner/utils"); | ||
@@ -64,0 +67,0 @@ Object.defineProperty(exports, "interpolate", { enumerable: true, get: function () { return utils_1.interpolate; } }); |
| import type { TsConfigOptions } from 'ts-node'; | ||
| import type { CompilerOptions } from 'typescript'; | ||
| /** | ||
| * Force-register an ESM loader (`@swc-node/register/esm` if available, else | ||
| * `ts-node/esm`) via `Module.register` so dynamic `import()` of TS files | ||
| * goes through a transpiler. | ||
| * | ||
| * **IMPORTANT — global side effect:** `Module.register` is one-shot per | ||
| * process and applies to *every* subsequent ESM resolution in the process. | ||
| * Calling this trades native Node.js TypeScript stripping for transpiled | ||
| * loading on the dynamic-import path for the rest of the run. CJS | ||
| * `require()` is unaffected (different hook), so `.cts` files via require | ||
| * keep using native strip + swc-node's `Module._extensions` hook. | ||
| * | ||
| * Required for the niche case where an ESM config (`.mts` or `.ts` resolved | ||
| * as ESM) combines top-level await with TypeScript syntax that native strip | ||
| * can't handle (`enum`, runtime `namespace`, etc.). TLA forces dynamic | ||
| * `import()`, which bypasses the CJS hook chain - the only way to intercept | ||
| * is `Module.register`. | ||
| * | ||
| * Idempotent: subsequent calls are no-ops. | ||
| * | ||
| * Throws if neither `@swc-node/register` nor `ts-node` is installed. | ||
| */ | ||
| export declare function forceRegisterEsmLoader(): void; | ||
| /** | ||
| * Whether Nx will defer to Node's native TypeScript stripping for the next | ||
| * `.ts` load. Mirrors the gate used by `loadTsFile`/`registerTsProject` so | ||
| * other registration sites (e.g. plugin transpiler) can stay aligned. | ||
| */ | ||
| export declare function isNativeStripPreferred(): boolean; | ||
| /** | ||
| * Optionally, if swc-node and tsconfig-paths are available in the current workspace, apply the require | ||
@@ -11,2 +40,13 @@ * register hooks so that .ts files can be used for writing custom workspace projects. | ||
| * | ||
| * Behavior change in v23: when the runtime exposes native TypeScript type | ||
| * stripping (`process.features.typescript`), this function skips the | ||
| * transpiler (Node handles `.ts` directly) but still registers | ||
| * `tsconfig-paths`. Path mapping is orthogonal to transpilation - callers | ||
| * relying on tsconfig `paths` for workspace alias resolution still get them. | ||
| * For loading a `.ts` file whose syntax native strip can't handle (`enum`, | ||
| * runtime `namespace`, legacy decorators, etc.), use `loadTsFile`, which | ||
| * registers swc/ts-node + tsconfig-paths on demand. To restore the legacy | ||
| * behavior (always register swc/ts-node + tsconfig-paths up front), set | ||
| * `NX_PREFER_NODE_STRIP_TYPES=false`. | ||
| * | ||
| * @returns cleanup function | ||
@@ -31,2 +71,75 @@ */ | ||
| /** | ||
| * Node.js throws this code when native type stripping hits an unsupported | ||
| * construct (enum, runtime namespace, legacy decorators, import = require, | ||
| * parameter properties on older Node, etc.). | ||
| * | ||
| * Exported for tests. | ||
| */ | ||
| export declare function isNativeTypeStripError(err: unknown): boolean; | ||
| /** | ||
| * A SyntaxError thrown while parsing a forced-CJS file (`.cts`/`.cjs`) as | ||
| * CommonJS - typically ESM syntax in a CJS file (e.g. `export default` in | ||
| * `.cts`). Pre-v23 this worked because swc-node's CJS hook compiled away the | ||
| * ESM syntax; under native strip swc-node isn't registered, so the file | ||
| * reaches Node's strict CJS parser. swc-node tolerates ESM syntax in `.cts` | ||
| * (`register()` forces `module: commonjs` regardless of extension), so | ||
| * escalating to the swc/ts-node fallback recovers the legacy behavior. | ||
| */ | ||
| export declare function isCjsSyntaxError(err: unknown, filePath: string): boolean; | ||
| /** | ||
| * A ReferenceError from Node treating a `.ts`/`.mts` file as ESM and the file | ||
| * relying on a CJS-only global: `require`, `__dirname`, or `__filename`. | ||
| * Pre-v23 swc-node compiled `.ts` to CJS where these globals exist; under | ||
| * native strip Node detects ESM via `import`/`export` syntax and these globals | ||
| * are undefined. Registering swc/ts-node compiles ESM->CJS and restores the | ||
| * legacy globals. | ||
| */ | ||
| export declare function isRequireInEsmScopeError(err: unknown, filePath: string): boolean; | ||
| export declare function isTsEsmSyntaxError(err: unknown, filePath: string): boolean; | ||
| export declare function isTsEsmNamedExportLinkageError(err: unknown, filePath: string): boolean; | ||
| /** | ||
| * Load a TypeScript file via `require()`. | ||
| * | ||
| * When the runtime exposes native TypeScript stripping | ||
| * (`process.features.typescript`) and the user hasn't opted out via | ||
| * `NX_PREFER_NODE_STRIP_TYPES=false`, the file loads directly with no | ||
| * swc/ts-node and no tsconfig-paths registration. If Node throws on an | ||
| * unsupported construct (enum, runtime namespace, legacy decorators, etc.), | ||
| * this registers swc/ts-node + tsconfig-paths and retries - matching the | ||
| * pre-v23 registration. Set `NX_DISABLE_TSCONFIG_PATHS=true` to skip | ||
| * tsconfig-paths even on fallback (useful when relying on package manager | ||
| * workspaces). Set `NX_VERBOSE_LOGGING=true` to log when fallback triggers. | ||
| * | ||
| * When native strip is opted out (`NX_PREFER_NODE_STRIP_TYPES=false` or | ||
| * unsupported Node), uses the legacy `registerTsProject` path. | ||
| * | ||
| * `tsConfigPath` is only consulted on the swc/ts-node fallback path (for | ||
| * compilerOptions) and for tsconfig-paths registration. Native strip ignores | ||
| * it. When omitted, defaults to the workspace root tsconfig. | ||
| * | ||
| * Note on ESM: Node 22.12+ supports `require()` of synchronous ESM by default, | ||
| * so most ESM `.ts` configs load via this function without issue. Modules | ||
| * that use top-level await throw `ERR_REQUIRE_ASYNC_MODULE` and must be | ||
| * loaded with dynamic `import()` instead. `ERR_REQUIRE_ESM` (legacy code) | ||
| * bubbles unchanged for the rare case it still fires, so async-aware callers | ||
| * can dispatch to `import()`. | ||
| * | ||
| * @returns the loaded module | ||
| */ | ||
| export declare function loadTsFile<T = any>(filePath: string, tsConfigPath?: string): T; | ||
| /** | ||
| * Plain `require()` with a lazy `tsconfig-paths` fallback. Use for files that | ||
| * are NOT TypeScript (no transpilation needed) but may still import workspace | ||
| * packages through TS path aliases (e.g. a `.js` changelog renderer that | ||
| * `require`s `@my-org/lib`). | ||
| * | ||
| * `tsconfig-paths` is only registered after the first `require()` fails with | ||
| * a module-resolution error, so workspaces that resolve aliases through | ||
| * package-manager symlinks pay nothing. Set `NX_DISABLE_TSCONFIG_PATHS=true` | ||
| * to skip the fallback entirely. | ||
| * | ||
| * @returns the loaded module | ||
| */ | ||
| export declare function requireWithTsconfigFallback<T = any>(filePath: string, tsConfigPath?: string): T; | ||
| /** | ||
| * Register ts-node or swc-node given a set of compiler options. | ||
@@ -33,0 +146,0 @@ * |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.forceRegisterEsmLoader = forceRegisterEsmLoader; | ||
| exports.isNativeStripPreferred = isNativeStripPreferred; | ||
| exports.registerTsProject = registerTsProject; | ||
@@ -7,2 +9,9 @@ exports.getSwcTranspiler = getSwcTranspiler; | ||
| exports.getTranspiler = getTranspiler; | ||
| exports.isNativeTypeStripError = isNativeTypeStripError; | ||
| exports.isCjsSyntaxError = isCjsSyntaxError; | ||
| exports.isRequireInEsmScopeError = isRequireInEsmScopeError; | ||
| exports.isTsEsmSyntaxError = isTsEsmSyntaxError; | ||
| exports.isTsEsmNamedExportLinkageError = isTsEsmNamedExportLinkageError; | ||
| exports.loadTsFile = loadTsFile; | ||
| exports.requireWithTsconfigFallback = requireWithTsconfigFallback; | ||
| exports.registerTranspiler = registerTranspiler; | ||
@@ -21,2 +30,70 @@ exports.registerTsConfigPaths = registerTsConfigPaths; | ||
| /** | ||
| * Force-register an ESM loader (`@swc-node/register/esm` if available, else | ||
| * `ts-node/esm`) via `Module.register` so dynamic `import()` of TS files | ||
| * goes through a transpiler. | ||
| * | ||
| * **IMPORTANT — global side effect:** `Module.register` is one-shot per | ||
| * process and applies to *every* subsequent ESM resolution in the process. | ||
| * Calling this trades native Node.js TypeScript stripping for transpiled | ||
| * loading on the dynamic-import path for the rest of the run. CJS | ||
| * `require()` is unaffected (different hook), so `.cts` files via require | ||
| * keep using native strip + swc-node's `Module._extensions` hook. | ||
| * | ||
| * Required for the niche case where an ESM config (`.mts` or `.ts` resolved | ||
| * as ESM) combines top-level await with TypeScript syntax that native strip | ||
| * can't handle (`enum`, runtime `namespace`, etc.). TLA forces dynamic | ||
| * `import()`, which bypasses the CJS hook chain - the only way to intercept | ||
| * is `Module.register`. | ||
| * | ||
| * Idempotent: subsequent calls are no-ops. | ||
| * | ||
| * Throws if neither `@swc-node/register` nor `ts-node` is installed. | ||
| */ | ||
| function forceRegisterEsmLoader() { | ||
| ensureEsmLoaderRegistered({ required: true }); | ||
| } | ||
| function ensureEsmLoaderRegistered(opts) { | ||
| if (isTsEsmLoaderRegistered) | ||
| return; | ||
| const module = require('node:module'); | ||
| if (typeof module.register !== 'function') { | ||
| if (opts.required) { | ||
| throw new Error(`${logger_1.NX_PREFIX} Module.register is not available in this Node.js version - cannot register an ESM loader for the TypeScript fallback. ${STRIP_TYPES_OPT_OUT_HINT}`); | ||
| } | ||
| return; | ||
| } | ||
| // ts-node reads compilerOptions from this env var. Setting nodenext | ||
| // module/resolution avoids surprises when ts-node is the chosen loader. | ||
| process.env.TS_NODE_COMPILER_OPTIONS ??= JSON.stringify({ | ||
| moduleResolution: 'nodenext', | ||
| module: 'nodenext', | ||
| }); | ||
| // Prefer @swc-node/register/esm (faster) over ts-node/esm. | ||
| const swcEsm = tryResolveLoader('@swc-node/register/esm'); | ||
| const tsNodeEsm = tryResolveLoader('ts-node/esm'); | ||
| const loaderPath = swcEsm ?? tsNodeEsm; | ||
| if (!loaderPath) { | ||
| if (opts.required) { | ||
| throw new Error(`${logger_1.NX_PREFIX} Cannot register an ESM TypeScript loader to fall back from native stripping. Install @swc-node/register or ts-node, or ${STRIP_TYPES_OPT_OUT_HINT}`); | ||
| } | ||
| isTsEsmLoaderRegistered = true; | ||
| return; | ||
| } | ||
| if (process.env.NX_VERBOSE_LOGGING === 'true') { | ||
| const loaderName = swcEsm ? '@swc-node/register/esm' : 'ts-node/esm'; | ||
| logger_1.logger.warn((0, logger_1.stripIndent)(`${logger_1.NX_PREFIX} Registering ESM TypeScript loader ${loaderName}. All subsequent ESM imports in this process will go through it - native Node.js TypeScript stripping is forfeited for the dynamic-import path.`)); | ||
| } | ||
| const url = require('node:url'); | ||
| module.register(url.pathToFileURL(loaderPath)); | ||
| isTsEsmLoaderRegistered = true; | ||
| } | ||
| function tryResolveLoader(specifier) { | ||
| try { | ||
| return require.resolve(specifier); | ||
| } | ||
| catch { | ||
| return null; | ||
| } | ||
| } | ||
| /** | ||
| * tsx is a utility to run TypeScript files in node which is growing in popularity: | ||
@@ -56,6 +133,10 @@ * https://tsx.is | ||
| /** | ||
| * Whether the current Node.js version supports native TypeScript execution | ||
| * via type stripping (Node 22.6+). | ||
| * Whether the current Node.js runtime exposes native TypeScript type | ||
| * stripping. This is the authoritative gate - it correctly handles every | ||
| * way Node ships TS support: | ||
| * - Node 23.6+: unflagged, on by default | ||
| * - Node 22.18+ LTS: backported unflagged | ||
| * - Node 22.6-22.17 + `--experimental-strip-types` (or `--experimental-transform-types`) | ||
| * | ||
| * process.features.typescript is 'strip' | 'transform' | false in Node 22.6+ | ||
| * `process.features.typescript` is `'strip' | 'transform' | false`. | ||
| */ | ||
@@ -65,17 +146,33 @@ const nodeSupportsNativeTypescript = !!process.features | ||
| /** | ||
| * When process.features.typescript is truthy and the user has opted in via | ||
| * NX_PREFER_NODE_STRIP_TYPES=true, we can skip registering swc-node or ts-node | ||
| * transpilers since Node.js will handle TypeScript natively. | ||
| * When process.features.typescript is truthy, default to letting Node.js | ||
| * handle TypeScript natively via type stripping and skip registering swc-node | ||
| * or ts-node. Users can opt out by setting NX_PREFER_NODE_STRIP_TYPES=false. | ||
| * | ||
| * This can significantly improve performance when loading TypeScript config files, but there are some things | ||
| * that won't work. See: https://nodejs.org/api/typescript.html#full-typescript-support | ||
| * Some constructs (enum, runtime namespace, legacy decorators, | ||
| * import = require, parameter properties, etc.) aren't supported by native | ||
| * type stripping. `loadTsFile` catches these failures and falls back to | ||
| * registering swc/ts-node + tsconfig-paths automatically. | ||
| * | ||
| * TODO(v23): We should turn this on by default, but look at if need to fallback to SWC/ts-node if it fails. | ||
| * See: https://nodejs.org/api/typescript.html#full-typescript-support | ||
| */ | ||
| const preferNodeStripTypes = (() => { | ||
| if (process.env.NX_PREFER_NODE_STRIP_TYPES !== 'true') { | ||
| if (!nodeSupportsNativeTypescript) { | ||
| return false; | ||
| } | ||
| return nodeSupportsNativeTypescript; | ||
| return process.env.NX_PREFER_NODE_STRIP_TYPES !== 'false'; | ||
| })(); | ||
| /** | ||
| * Skip tsconfig-paths registration on the swc/ts-node fallback path. Useful | ||
| * for workspaces relying on package manager workspaces (pnpm, yarn, npm) for | ||
| * project linking, where tsconfig path aliases aren't needed. | ||
| */ | ||
| const disableTsConfigPaths = process.env.NX_DISABLE_TSCONFIG_PATHS === 'true'; | ||
| /** | ||
| * Whether Nx will defer to Node's native TypeScript stripping for the next | ||
| * `.ts` load. Mirrors the gate used by `loadTsFile`/`registerTsProject` so | ||
| * other registration sites (e.g. plugin transpiler) can stay aligned. | ||
| */ | ||
| function isNativeStripPreferred() { | ||
| return preferNodeStripTypes; | ||
| } | ||
| function registerTsProject(path, configFilename) { | ||
@@ -87,8 +184,19 @@ // See explanation alongside isInvokedByTsx declaration | ||
| const tsConfigPath = configFilename ? (0, path_1.join)(path, configFilename) : path; | ||
| // See explanation alongside preferNodeStripTypes declaration | ||
| // When using Node.js native type stripping, skip transpiler registration | ||
| // but still register tsconfig-paths for path mapping support | ||
| // Under native strip we skip the transpiler (Node handles `.ts` directly) | ||
| // but still register tsconfig-paths. Path mapping is orthogonal to | ||
| // transpilation: code calling `registerTsProject` for path aliases | ||
| // (e.g. test setup files requiring `@my-org/lib`) gets nothing back if | ||
| // both are skipped. Package-manager-workspace symlinks aren't a | ||
| // universal substitute - explicit tsconfig `paths` configs still need | ||
| // runtime alias resolution. Callers needing the transpiler for | ||
| // unsupported syntax (enum, runtime namespace, legacy decorators, etc.) | ||
| // should use `loadTsFile` instead, which registers swc/ts-node + | ||
| // tsconfig-paths on demand. | ||
| if (preferNodeStripTypes) { | ||
| return registerTsConfigPaths(tsConfigPath); | ||
| } | ||
| // Legacy path: prior to v23, Nx always registered swc-node/ts-node and | ||
| // tsconfig-paths to load .ts config files. v23+ prefers Node's built-in | ||
| // type stripping; this branch only runs when the user opted out via | ||
| // NX_PREFER_NODE_STRIP_TYPES=false or when strip is unavailable. | ||
| const { compilerOptions, tsConfigRaw } = readCompilerOptions(tsConfigPath); | ||
@@ -99,21 +207,6 @@ const cleanupFunctions = [ | ||
| ]; | ||
| // Add ESM support for `.ts` files. | ||
| // NOTE: There is no cleanup function for this, as it's not possible to unregister the loader. | ||
| // Based on limited testing, it doesn't seem to matter if we register it multiple times, but just in | ||
| // case let's keep a flag to prevent it. | ||
| if (!isTsEsmLoaderRegistered) { | ||
| // We need a way to ensure that `.ts` files are treated as ESM not CJS. | ||
| // Since there is no way to pass compilerOptions like we do with the programmatic API, we should default | ||
| // the environment variable that ts-node checks. | ||
| process.env.TS_NODE_COMPILER_OPTIONS ??= JSON.stringify({ | ||
| moduleResolution: 'nodenext', | ||
| module: 'nodenext', | ||
| }); | ||
| const module = require('node:module'); | ||
| if (module.register && packageIsInstalled('ts-node/esm')) { | ||
| const url = require('node:url'); | ||
| module.register(url.pathToFileURL(require.resolve('ts-node/esm'))); | ||
| } | ||
| isTsEsmLoaderRegistered = true; | ||
| } | ||
| // Best-effort ESM loader registration so dynamic import() of .ts/.mts | ||
| // files goes through a transpiler. No-op if no ESM loader package is | ||
| // installed. | ||
| ensureEsmLoaderRegistered({ required: false }); | ||
| return () => { | ||
@@ -263,2 +356,303 @@ for (const fn of cleanupFunctions) { | ||
| /** | ||
| * Node.js throws this code when native type stripping hits an unsupported | ||
| * construct (enum, runtime namespace, legacy decorators, import = require, | ||
| * parameter properties on older Node, etc.). | ||
| * | ||
| * Exported for tests. | ||
| */ | ||
| function isNativeTypeStripError(err) { | ||
| if (!err || typeof err !== 'object') | ||
| return false; | ||
| return (err.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'); | ||
| } | ||
| /** | ||
| * Module resolution failures - typically a tsconfig path alias that hasn't | ||
| * been registered yet, or a workspace lib not surfaced via package manager | ||
| * symlinks. CJS uses `MODULE_NOT_FOUND`, ESM uses `ERR_MODULE_NOT_FOUND`. | ||
| */ | ||
| function isModuleNotFoundError(err) { | ||
| if (!err || typeof err !== 'object') | ||
| return false; | ||
| const code = err.code; | ||
| return code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND'; | ||
| } | ||
| /** | ||
| * A SyntaxError thrown while parsing a forced-CJS file (`.cts`/`.cjs`) as | ||
| * CommonJS - typically ESM syntax in a CJS file (e.g. `export default` in | ||
| * `.cts`). Pre-v23 this worked because swc-node's CJS hook compiled away the | ||
| * ESM syntax; under native strip swc-node isn't registered, so the file | ||
| * reaches Node's strict CJS parser. swc-node tolerates ESM syntax in `.cts` | ||
| * (`register()` forces `module: commonjs` regardless of extension), so | ||
| * escalating to the swc/ts-node fallback recovers the legacy behavior. | ||
| */ | ||
| function isCjsSyntaxError(err, filePath) { | ||
| if (!(err instanceof SyntaxError)) | ||
| return false; | ||
| return filePath.endsWith('.cts') || filePath.endsWith('.cjs'); | ||
| } | ||
| /** | ||
| * A ReferenceError from Node treating a `.ts`/`.mts` file as ESM and the file | ||
| * relying on a CJS-only global: `require`, `__dirname`, or `__filename`. | ||
| * Pre-v23 swc-node compiled `.ts` to CJS where these globals exist; under | ||
| * native strip Node detects ESM via `import`/`export` syntax and these globals | ||
| * are undefined. Registering swc/ts-node compiles ESM->CJS and restores the | ||
| * legacy globals. | ||
| */ | ||
| function isRequireInEsmScopeError(err, filePath) { | ||
| if (!(err instanceof ReferenceError)) | ||
| return false; | ||
| if (!(filePath.endsWith('.ts') || filePath.endsWith('.mts'))) | ||
| return false; | ||
| // Node's exact phrasing varies across versions / strip modes. Match the | ||
| // bare-name form too (e.g. `__dirname is not defined`) so the fallback | ||
| // still triggers when the trailing "in ES module scope" is absent. | ||
| const msg = err.message; | ||
| return /(require|__dirname|__filename) is not defined/.test(msg); | ||
| } | ||
| function isTsEsmSyntaxError(err, filePath) { | ||
| if (!(err instanceof SyntaxError)) | ||
| return false; | ||
| if (!filePath.endsWith('.ts')) | ||
| return false; | ||
| // Node has multiple phrasings for ESM-in-CJS-scope syntax errors depending | ||
| // on whether the offending token is `import` or `export` and which parser | ||
| // path triggered: "Cannot use import statement outside a module" or | ||
| // "Unexpected token 'export'" / "Unexpected token 'import'". swc-node's | ||
| // CJS hook compiles ESM->CJS regardless of the surface error, so all of | ||
| // these should escalate to the same fallback. | ||
| const msg = err.message; | ||
| return (msg.includes('Cannot use import statement outside a module') || | ||
| /Unexpected token ['"](export|import)['"]/.test(msg)); | ||
| } | ||
| function isTsEsmNamedExportLinkageError(err, filePath) { | ||
| if (!(err instanceof SyntaxError)) | ||
| return false; | ||
| return ((filePath.endsWith('.ts') || filePath.endsWith('.mts')) && | ||
| err.message.includes('does not provide an export named')); | ||
| } | ||
| /** | ||
| * Hint appended to errors that the lazy fallback couldn't recover from. | ||
| * Points users at the env opt-out for cases native strip can't reach (e.g. | ||
| * ESM with top-level await + unsupported TS syntax, where swc-node's CJS | ||
| * Module._extensions hook can't intercept dynamic `import()`). | ||
| */ | ||
| const NX_PREFER_NODE_STRIP_TYPES_DOCS_URL = 'https://nx.dev/docs/reference/environment-variables#nx-prefer-node-strip-types'; | ||
| const STRIP_TYPES_OPT_OUT_HINT = `Set NX_PREFER_NODE_STRIP_TYPES=false to opt out of Node's native TypeScript stripping and use swc/ts-node instead. See ${NX_PREFER_NODE_STRIP_TYPES_DOCS_URL}`; | ||
| /** | ||
| * Load a TypeScript file via `require()`. | ||
| * | ||
| * When the runtime exposes native TypeScript stripping | ||
| * (`process.features.typescript`) and the user hasn't opted out via | ||
| * `NX_PREFER_NODE_STRIP_TYPES=false`, the file loads directly with no | ||
| * swc/ts-node and no tsconfig-paths registration. If Node throws on an | ||
| * unsupported construct (enum, runtime namespace, legacy decorators, etc.), | ||
| * this registers swc/ts-node + tsconfig-paths and retries - matching the | ||
| * pre-v23 registration. Set `NX_DISABLE_TSCONFIG_PATHS=true` to skip | ||
| * tsconfig-paths even on fallback (useful when relying on package manager | ||
| * workspaces). Set `NX_VERBOSE_LOGGING=true` to log when fallback triggers. | ||
| * | ||
| * When native strip is opted out (`NX_PREFER_NODE_STRIP_TYPES=false` or | ||
| * unsupported Node), uses the legacy `registerTsProject` path. | ||
| * | ||
| * `tsConfigPath` is only consulted on the swc/ts-node fallback path (for | ||
| * compilerOptions) and for tsconfig-paths registration. Native strip ignores | ||
| * it. When omitted, defaults to the workspace root tsconfig. | ||
| * | ||
| * Note on ESM: Node 22.12+ supports `require()` of synchronous ESM by default, | ||
| * so most ESM `.ts` configs load via this function without issue. Modules | ||
| * that use top-level await throw `ERR_REQUIRE_ASYNC_MODULE` and must be | ||
| * loaded with dynamic `import()` instead. `ERR_REQUIRE_ESM` (legacy code) | ||
| * bubbles unchanged for the rare case it still fires, so async-aware callers | ||
| * can dispatch to `import()`. | ||
| * | ||
| * @returns the loaded module | ||
| */ | ||
| function loadTsFile(filePath, tsConfigPath) { | ||
| const resolvedTsConfigPath = tsConfigPath ?? (0, typescript_1.getRootTsConfigPath)(); | ||
| if (isInvokedByTsx) { | ||
| return require(filePath); | ||
| } | ||
| if (!preferNodeStripTypes) { | ||
| if (!resolvedTsConfigPath) { | ||
| throw new Error(`${logger_1.NX_PREFIX} loadTsFile could not find a workspace tsconfig while loading ${filePath} on the swc/ts-node path. Pass an explicit tsConfigPath or add a tsconfig.base.json/tsconfig.json at the workspace root.`); | ||
| } | ||
| const cleanup = registerTsProject(resolvedTsConfigPath); | ||
| try { | ||
| return require(filePath); | ||
| } | ||
| finally { | ||
| cleanup(); | ||
| } | ||
| } | ||
| // Native strip path: no registration up front. pnpm/npm/yarn workspaces | ||
| // resolve aliases without tsconfig-paths. On failure, lazy-register what | ||
| // the specific error code indicates is needed and retry: | ||
| // - MODULE_NOT_FOUND -> first try tsconfig-paths (alias resolution). | ||
| // If that still fails (e.g. extensionless `import './foo'` when | ||
| // `foo.ts` is adjacent - Node's resolver doesn't add `.ts`), escalate | ||
| // to swc/ts-node which handles `.ts` extension resolution. | ||
| // - ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX -> register swc/ts-node + tsconfig-paths | ||
| // Each registration kind runs at most once, so a file recovers in at | ||
| // most three attempts. | ||
| let pathsRegistered = false; | ||
| let transpilerRegistered = false; | ||
| const cleanups = []; | ||
| const registerTranspilerFallback = (err) => { | ||
| if (!swcNodeInstalled && !tsNodeInstalled) { | ||
| const original = err instanceof Error ? err.message : String(err); | ||
| throw new Error(`${logger_1.NX_PREFIX} ${filePath} could not be loaded under Node's native TypeScript stripping (${original}). Install @swc-node/register and @swc/core (or ts-node) to enable the swc/ts-node fallback, or ${STRIP_TYPES_OPT_OUT_HINT}`); | ||
| } | ||
| if (!resolvedTsConfigPath) { | ||
| throw new Error(`${logger_1.NX_PREFIX} ${filePath} requires the swc/ts-node fallback but no workspace tsconfig was found. Pass an explicit tsConfigPath or add a tsconfig.base.json/tsconfig.json at the workspace root. ${STRIP_TYPES_OPT_OUT_HINT}`); | ||
| } | ||
| if (!pathsRegistered && !disableTsConfigPaths) { | ||
| cleanups.push(registerTsConfigPaths(resolvedTsConfigPath)); | ||
| pathsRegistered = true; | ||
| } | ||
| const { compilerOptions, tsConfigRaw } = readCompilerOptions(resolvedTsConfigPath); | ||
| cleanups.push(registerTranspiler(compilerOptions, tsConfigRaw)); | ||
| transpilerRegistered = true; | ||
| }; | ||
| try { | ||
| for (let attempt = 1;; attempt++) { | ||
| try { | ||
| if (attempt > 1) { | ||
| try { | ||
| delete require.cache[require.resolve(filePath)]; | ||
| } | ||
| catch { | ||
| // require.resolve may throw if the failed load never reached cache | ||
| } | ||
| } | ||
| return require(filePath); | ||
| } | ||
| catch (err) { | ||
| // Cheap fallback first: register tsconfig-paths and retry. | ||
| if (isModuleNotFoundError(err) && | ||
| !pathsRegistered && | ||
| !disableTsConfigPaths && | ||
| resolvedTsConfigPath) { | ||
| logFallback(filePath, err, 'Module not found; registering tsconfig-paths and retrying.'); | ||
| cleanups.push(registerTsConfigPaths(resolvedTsConfigPath)); | ||
| pathsRegistered = true; | ||
| continue; | ||
| } | ||
| // Heavy fallback: register swc/ts-node (+ paths) and retry. Triggered | ||
| // by: | ||
| // - strip-types failure (`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`) | ||
| // - module resolution failure that tsconfig-paths alone can't fix | ||
| // (extensionless `./foo` -> `./foo.ts`) | ||
| // - SyntaxError in a `.cts`/`.cjs` file (ESM syntax in a forced-CJS | ||
| // file). swc-node compiles ESM->CJS regardless of extension. | ||
| // - SyntaxError from Node parsing a `.ts` config with ESM syntax as | ||
| // CJS. swc/ts-node preserves the pre-v23 behavior for these files. | ||
| // - ReferenceError from Node treating a `.ts`/`.mts` config as ESM | ||
| // when it contains legacy CJS `require`. | ||
| // - SyntaxError from native ESM linkage when generated TS config | ||
| // imports a type-only symbol as a runtime named export. | ||
| if ((isNativeTypeStripError(err) || | ||
| isModuleNotFoundError(err) || | ||
| isCjsSyntaxError(err, filePath) || | ||
| isTsEsmSyntaxError(err, filePath) || | ||
| isRequireInEsmScopeError(err, filePath) || | ||
| isTsEsmNamedExportLinkageError(err, filePath)) && | ||
| !transpilerRegistered) { | ||
| logFallback(filePath, err, isNativeTypeStripError(err) | ||
| ? 'Native Node.js TypeScript stripping failed; falling back to swc/ts-node + tsconfig-paths.' | ||
| : isCjsSyntaxError(err, filePath) | ||
| ? 'ESM syntax in forced-CJS file; falling back to swc/ts-node + tsconfig-paths.' | ||
| : isTsEsmSyntaxError(err, filePath) | ||
| ? 'ESM syntax in TypeScript file parsed as CommonJS; falling back to swc/ts-node + tsconfig-paths.' | ||
| : isRequireInEsmScopeError(err, filePath) | ||
| ? 'CommonJS require in native ESM TypeScript file; falling back to swc/ts-node + tsconfig-paths.' | ||
| : isTsEsmNamedExportLinkageError(err, filePath) | ||
| ? 'Native ESM named export linkage failed; falling back to swc/ts-node + tsconfig-paths.' | ||
| : 'Module not found after tsconfig-paths; falling back to swc/ts-node + tsconfig-paths.'); | ||
| registerTranspilerFallback(err); | ||
| continue; | ||
| } | ||
| throw augmentLoadFailure(filePath, err); | ||
| } | ||
| } | ||
| } | ||
| finally { | ||
| for (const fn of cleanups) | ||
| fn(); | ||
| } | ||
| } | ||
| /** | ||
| * Plain `require()` with a lazy `tsconfig-paths` fallback. Use for files that | ||
| * are NOT TypeScript (no transpilation needed) but may still import workspace | ||
| * packages through TS path aliases (e.g. a `.js` changelog renderer that | ||
| * `require`s `@my-org/lib`). | ||
| * | ||
| * `tsconfig-paths` is only registered after the first `require()` fails with | ||
| * a module-resolution error, so workspaces that resolve aliases through | ||
| * package-manager symlinks pay nothing. Set `NX_DISABLE_TSCONFIG_PATHS=true` | ||
| * to skip the fallback entirely. | ||
| * | ||
| * @returns the loaded module | ||
| */ | ||
| function requireWithTsconfigFallback(filePath, tsConfigPath) { | ||
| try { | ||
| return require(filePath); | ||
| } | ||
| catch (err) { | ||
| if (!isModuleNotFoundError(err) || disableTsConfigPaths) { | ||
| throw err; | ||
| } | ||
| const resolvedTsConfigPath = tsConfigPath ?? (0, typescript_1.getRootTsConfigPath)(); | ||
| if (!resolvedTsConfigPath) { | ||
| throw err; | ||
| } | ||
| const cleanup = registerTsConfigPaths(resolvedTsConfigPath); | ||
| try { | ||
| delete require.cache[require.resolve(filePath)]; | ||
| } | ||
| catch { | ||
| // require.resolve may throw if the failed load never reached cache | ||
| } | ||
| try { | ||
| return require(filePath); | ||
| } | ||
| finally { | ||
| cleanup(); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Append the `NX_PREFER_NODE_STRIP_TYPES=false` opt-out hint so users know | ||
| * there's an escape hatch for cases native strip can't reach (e.g. ESM with | ||
| * top-level await + unsupported TS syntax). Skipped for: | ||
| * - ESM-redispatch signals callers expect to handle | ||
| * (`ERR_REQUIRE_ESM`, `ERR_REQUIRE_ASYNC_MODULE`) | ||
| * - plain module-resolution failures (`MODULE_NOT_FOUND`, | ||
| * `ERR_MODULE_NOT_FOUND`) - disabling strip-types doesn't fix a missing | ||
| * module, the hint just misleads. | ||
| */ | ||
| function augmentLoadFailure(filePath, err) { | ||
| if (!(err instanceof Error)) | ||
| return err; | ||
| const code = err.code; | ||
| if (code === 'ERR_REQUIRE_ESM' || | ||
| code === 'ERR_REQUIRE_ASYNC_MODULE' || | ||
| code === 'MODULE_NOT_FOUND' || | ||
| code === 'ERR_MODULE_NOT_FOUND') { | ||
| return err; | ||
| } | ||
| if (err.message.includes(NX_PREFER_NODE_STRIP_TYPES_DOCS_URL)) { | ||
| return err; | ||
| } | ||
| err.message = `${err.message}\n\n${logger_1.NX_PREFIX} Failed to load ${filePath} under Node's native TypeScript stripping. ${STRIP_TYPES_OPT_OUT_HINT}`; | ||
| return err; | ||
| } | ||
| function logFallback(filePath, err, summary) { | ||
| if (process.env.NX_VERBOSE_LOGGING !== 'true') { | ||
| return; | ||
| } | ||
| const message = err instanceof Error ? err.message : String(err); | ||
| logger_1.logger.warn((0, logger_1.stripIndent)(`${logger_1.NX_PREFIX} ${summary} (${filePath}) | ||
| ${message}`)); | ||
| } | ||
| /** | ||
| * Register ts-node or swc-node given a set of compiler options. | ||
@@ -276,3 +670,3 @@ * | ||
| // If Node.js natively supports TypeScript (22.6+), no transpiler is needed. | ||
| // Don't warn — Node will handle .ts files via type stripping. | ||
| // Don't warn - Node will handle .ts files via type stripping. | ||
| if (!nodeSupportsNativeTypescript) { | ||
@@ -301,2 +695,12 @@ warnNoTranspiler(); | ||
| if (tsConfigResult.resultType === 'success') { | ||
| // Short-circuit when the tsconfig has no `paths` entries. Installing | ||
| // tsconfig-paths' resolver hook adds a per-require cost on every | ||
| // module load; in package-manager-workspace setups (which resolve via | ||
| // symlinks instead of TS path mappings), the hook never has anything | ||
| // to do. Avoid paying that overhead on workspaces that don't use | ||
| // `paths`. | ||
| if (!tsConfigResult.paths || | ||
| Object.keys(tsConfigResult.paths).length === 0) { | ||
| return () => { }; | ||
| } | ||
| return tsconfigPaths.register({ | ||
@@ -303,0 +707,0 @@ baseUrl: resolvePathsBaseUrl(tsConfigPath), |
@@ -5,5 +5,17 @@ export declare let unregisterPluginTSTranspiler: (() => void) | null; | ||
| * with some default settings which work well for Nx plugins. | ||
| * | ||
| * When the runtime supports native TypeScript stripping and the user hasn't | ||
| * opted out, this is a noop - Node loads plugin `.ts` files directly. The | ||
| * lazy fallback in `handleImport` will call `forceRegisterPluginTSTranspiler` | ||
| * if a plugin uses unsupported syntax (enum, runtime namespace, etc.). | ||
| */ | ||
| export declare function registerPluginTSTranspiler(): void; | ||
| /** | ||
| * Always register swc-node or ts-node + tsconfig-paths, ignoring the native | ||
| * strip preference. Called from the fallback path in `handleImport` when a | ||
| * plugin `.ts` file throws `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX` under native | ||
| * stripping. | ||
| */ | ||
| export declare function forceRegisterPluginTSTranspiler(): void; | ||
| export declare function pluginTranspilerIsRegistered(): boolean; | ||
| export declare function cleanupPluginTSTranspiler(): void; |
@@ -5,2 +5,3 @@ "use strict"; | ||
| exports.registerPluginTSTranspiler = registerPluginTSTranspiler; | ||
| exports.forceRegisterPluginTSTranspiler = forceRegisterPluginTSTranspiler; | ||
| exports.pluginTranspilerIsRegistered = pluginTranspilerIsRegistered; | ||
@@ -14,7 +15,43 @@ exports.cleanupPluginTSTranspiler = cleanupPluginTSTranspiler; | ||
| exports.unregisterPluginTSTranspiler = null; | ||
| const NOOP = () => { }; | ||
| /** | ||
| * Register swc-node or ts-node if they are not currently registered | ||
| * with some default settings which work well for Nx plugins. | ||
| * | ||
| * When the runtime supports native TypeScript stripping and the user hasn't | ||
| * opted out, this is a noop - Node loads plugin `.ts` files directly. The | ||
| * lazy fallback in `handleImport` will call `forceRegisterPluginTSTranspiler` | ||
| * if a plugin uses unsupported syntax (enum, runtime namespace, etc.). | ||
| */ | ||
| function registerPluginTSTranspiler() { | ||
| if (exports.unregisterPluginTSTranspiler !== null) { | ||
| return; | ||
| } | ||
| if ((0, register_1.isNativeStripPreferred)()) { | ||
| // Sentinel so pluginTranspilerIsRegistered() reports true and callers | ||
| // don't keep retrying. The actual transpiler stays unregistered until | ||
| // a fallback forces it. | ||
| exports.unregisterPluginTSTranspiler = NOOP; | ||
| return; | ||
| } | ||
| doRegisterPluginTSTranspiler(); | ||
| } | ||
| /** | ||
| * Always register swc-node or ts-node + tsconfig-paths, ignoring the native | ||
| * strip preference. Called from the fallback path in `handleImport` when a | ||
| * plugin `.ts` file throws `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX` under native | ||
| * stripping. | ||
| */ | ||
| function forceRegisterPluginTSTranspiler() { | ||
| // If the previous registration was the native-strip sentinel, throw it | ||
| // out so we actually wire up swc/ts-node. | ||
| if (exports.unregisterPluginTSTranspiler === NOOP) { | ||
| exports.unregisterPluginTSTranspiler = null; | ||
| } | ||
| if (exports.unregisterPluginTSTranspiler !== null) { | ||
| return; | ||
| } | ||
| doRegisterPluginTSTranspiler(); | ||
| } | ||
| function doRegisterPluginTSTranspiler() { | ||
| // Get the first tsconfig that matches the allowed set | ||
@@ -21,0 +58,0 @@ const tsConfigName = [ |
@@ -6,3 +6,6 @@ /** | ||
| * Falls back to real import() for ESM-only packages that | ||
| * throw ERR_REQUIRE_ESM. | ||
| * throw ERR_REQUIRE_ESM. When loading a workspace plugin's `.ts` entry | ||
| * under native TypeScript stripping, also falls back to swc/ts-node if | ||
| * the file uses constructs Node can't strip (enum, runtime namespace, | ||
| * legacy decorators, etc.). | ||
| * | ||
@@ -9,0 +12,0 @@ * @param modulePath - The module specifier (relative, absolute, or package name) |
@@ -5,2 +5,3 @@ "use strict"; | ||
| const path_1 = require("path"); | ||
| const STRIP_TYPES_DOCS_URL = 'https://nx.dev/docs/reference/environment-variables#nx-prefer-node-strip-types'; | ||
| /** | ||
@@ -11,3 +12,6 @@ * Dynamically imports a module using CJS require(). | ||
| * Falls back to real import() for ESM-only packages that | ||
| * throw ERR_REQUIRE_ESM. | ||
| * throw ERR_REQUIRE_ESM. When loading a workspace plugin's `.ts` entry | ||
| * under native TypeScript stripping, also falls back to swc/ts-node if | ||
| * the file uses constructs Node can't strip (enum, runtime namespace, | ||
| * legacy decorators, etc.). | ||
| * | ||
@@ -29,7 +33,57 @@ * @param modulePath - The module specifier (relative, absolute, or package name) | ||
| catch (e) { | ||
| if (e.code === 'ERR_REQUIRE_ESM') { | ||
| // ERR_REQUIRE_ESM (legacy) and ERR_REQUIRE_ASYNC_MODULE (Node 22.12+, | ||
| // ESM with top-level await) both indicate the module must be loaded via | ||
| // dynamic import(). Native strip handles `.ts` extension resolution on | ||
| // the import() path, so this recovers TLA plugin entry points without | ||
| // requiring swc-node or ts-node to be installed. | ||
| if (e.code === 'ERR_REQUIRE_ESM' || e.code === 'ERR_REQUIRE_ASYNC_MODULE') { | ||
| return import(resolvedPath); | ||
| } | ||
| // Mirror `loadTsFile`'s fallback set (register.ts). Plugin loads hit a | ||
| // wider failure surface than .ts config loads because plugin sources are | ||
| // often ESM and import type-only symbols from `@nx/devkit` as runtime | ||
| // named exports. Lazy-require the matchers to keep register/transpiler | ||
| // (and their daemon/logger deps) out of module-eval-time graphs. | ||
| const matchers = require('../plugins/js/utils/register'); | ||
| if (e?.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX' || | ||
| e?.code === 'MODULE_NOT_FOUND' || | ||
| e?.code === 'ERR_MODULE_NOT_FOUND' || | ||
| matchers.isTsEsmSyntaxError(e, resolvedPath) || | ||
| matchers.isTsEsmNamedExportLinkageError(e, resolvedPath) || | ||
| matchers.isCjsSyntaxError(e, resolvedPath) || | ||
| matchers.isRequireInEsmScopeError(e, resolvedPath)) { | ||
| // Lazy-require to avoid pulling register/transpiler (and their | ||
| // daemon/logger transitive deps) into module-eval-time graphs. | ||
| const { forceRegisterPluginTSTranspiler } = require('../project-graph/plugins/transpiler'); | ||
| forceRegisterPluginTSTranspiler(); | ||
| try { | ||
| delete require.cache[require.resolve(normalizedPath)]; | ||
| } | ||
| catch { | ||
| // require.resolve may throw if the failed load never reached cache | ||
| } | ||
| try { | ||
| return require(normalizedPath); | ||
| } | ||
| catch (retryErr) { | ||
| // Mirror the initial-catch behavior: if the compiled output surfaces | ||
| // TLA or ESM-only-as-CJS, dispatch to dynamic import() instead of | ||
| // re-throwing. Without this, a plugin whose source needed the | ||
| // transpiler fallback AND emits top-level await fails here instead of | ||
| // loading. | ||
| if (retryErr?.code === 'ERR_REQUIRE_ESM' || | ||
| retryErr?.code === 'ERR_REQUIRE_ASYNC_MODULE') { | ||
| return import(resolvedPath); | ||
| } | ||
| if (retryErr instanceof Error) { | ||
| // Lazy-require NX_PREFIX so we don't pull logger -> daemon into | ||
| // module-eval-time graphs. | ||
| const { NX_PREFIX } = require('./logger'); | ||
| retryErr.message = `${retryErr.message}\n\n${NX_PREFIX} Failed to load ${normalizedPath} under Node's native TypeScript stripping. Set NX_PREFER_NODE_STRIP_TYPES=false to opt out and use swc/ts-node instead. See ${STRIP_TYPES_DOCS_URL}`; | ||
| } | ||
| throw retryErr; | ||
| } | ||
| } | ||
| throw e; | ||
| } | ||
| } |
+1
-1
@@ -165,3 +165,3 @@ { | ||
| "23-0-0-consolidate-release-tag-config": { | ||
| "version": "23.0.0-beta.0", | ||
| "version": "23.0.0-beta.16", | ||
| "description": "Consolidates any remaining legacy releaseTag* flat properties into the nested releaseTag object. The flat properties were removed in Nx 23.", | ||
@@ -168,0 +168,0 @@ "implementation": "./dist/src/migrations/update-23-0-0/consolidate-release-tag-config" |
+11
-11
| { | ||
| "name": "nx", | ||
| "version": "23.0.0-beta.15", | ||
| "version": "23.0.0-beta.16", | ||
| "private": false, | ||
@@ -173,12 +173,12 @@ "type": "commonjs", | ||
| "optionalDependencies": { | ||
| "@nx/nx-darwin-arm64": "23.0.0-beta.15", | ||
| "@nx/nx-darwin-x64": "23.0.0-beta.15", | ||
| "@nx/nx-freebsd-x64": "23.0.0-beta.15", | ||
| "@nx/nx-linux-arm-gnueabihf": "23.0.0-beta.15", | ||
| "@nx/nx-linux-arm64-gnu": "23.0.0-beta.15", | ||
| "@nx/nx-linux-arm64-musl": "23.0.0-beta.15", | ||
| "@nx/nx-linux-x64-gnu": "23.0.0-beta.15", | ||
| "@nx/nx-linux-x64-musl": "23.0.0-beta.15", | ||
| "@nx/nx-win32-arm64-msvc": "23.0.0-beta.15", | ||
| "@nx/nx-win32-x64-msvc": "23.0.0-beta.15" | ||
| "@nx/nx-darwin-arm64": "23.0.0-beta.16", | ||
| "@nx/nx-darwin-x64": "23.0.0-beta.16", | ||
| "@nx/nx-freebsd-x64": "23.0.0-beta.16", | ||
| "@nx/nx-linux-arm-gnueabihf": "23.0.0-beta.16", | ||
| "@nx/nx-linux-arm64-gnu": "23.0.0-beta.16", | ||
| "@nx/nx-linux-arm64-musl": "23.0.0-beta.16", | ||
| "@nx/nx-linux-x64-gnu": "23.0.0-beta.16", | ||
| "@nx/nx-linux-x64-musl": "23.0.0-beta.16", | ||
| "@nx/nx-win32-arm64-msvc": "23.0.0-beta.16", | ||
| "@nx/nx-win32-x64-msvc": "23.0.0-beta.16" | ||
| }, | ||
@@ -185,0 +185,0 @@ "nx-migrations": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 3 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 147 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 3 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 146 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
16355326
0.2%88035
0.7%777
3.32%