@nx/js 23.2.0-beta.2 → 23.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ export { walkTsconfigExtendsChain, type RawTsconfigJsonCache, } from './src/util
5
5
  export { isEsmProject } from './src/utils/module-format/is-esm-project';
6
6
  export { TS_SOLUTION_SETUP_TSCONFIG_INPUT, addProjectToTsSolutionWorkspace, assertNotUsingTsSolutionSetup, findRuntimeTsConfigName, getDefinedCustomConditionName, getProjectSourceRoot, getProjectType, isUsingTsSolutionSetup, shouldConfigureTsSolutionSetup, updateTsconfigFiles, } from './src/utils/typescript/ts-solution-setup';
7
7
  export { getTsConfigBaseOptions } from './src/utils/typescript/create-ts-config';
8
+ export { createTreeParseConfigHost, type TreeParseConfigHost, } from './src/utils/typescript/ts-config';
8
9
  export { ensureTypescript } from './src/utils/typescript/ensure-typescript';
9
10
  export { getRangeMinimum, getTsConfigModuleResolution, isTypescriptVersionAtLeast, } from './src/utils/is-typescript-version-at-least';
10
11
  export { getNeededCompilerOptionOverrides } from './src/utils/typescript/configuration';
package/dist/internal.js CHANGED
@@ -5,8 +5,8 @@
5
5
  // internal consumers and may change without semver protection. Consider it
6
6
  // the @nx/js equivalent of `@nx/devkit/internal`.
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.addReleaseConfigForNonTsSolution = exports.addLocalRegistryScripts = exports.stripGlobToBaseDir = exports.getImportPath = exports.createGlobPatternsForDependencies = exports.normalizeUnitTestRunnerOption = exports.normalizeLinterOption = exports.isValidPackageJsonBuildConfig = exports.addBuildAndWatchDepsTargets = exports.findNpmDependencies = exports.getProjectPackageManagerWorkspaceStateWarningTask = exports.getProjectPackageManagerWorkspaceState = exports.getNpmScope = exports.sortPackageJsonFields = exports.CopyAssetsHandler = exports.addSwcRegisterDependencies = exports.addSwcDependencies = exports.addSwcTestConfig = exports.addSwcConfig = exports.createTmpTsConfig = exports.computeCompilerOptionsPaths = exports.calculateProjectDependencies = exports.calculateProjectBuildableDependencies = exports.compileTypeScript = exports.getNeededCompilerOptionOverrides = exports.isTypescriptVersionAtLeast = exports.getTsConfigModuleResolution = exports.getRangeMinimum = exports.ensureTypescript = exports.getTsConfigBaseOptions = exports.updateTsconfigFiles = exports.shouldConfigureTsSolutionSetup = exports.isUsingTsSolutionSetup = exports.getProjectType = exports.getProjectSourceRoot = exports.getDefinedCustomConditionName = exports.findRuntimeTsConfigName = exports.assertNotUsingTsSolutionSetup = exports.addProjectToTsSolutionWorkspace = exports.TS_SOLUTION_SETUP_TSCONFIG_INPUT = exports.isEsmProject = exports.walkTsconfigExtendsChain = exports.findProjectsNpmDependencies = exports.isBuiltinModuleImport = exports.TargetProjectLocator = exports.requireWithTsconfigFallback = exports.registerTsConfigPaths = exports.registerTsProject = exports.loadTsFile = exports.forceRegisterEsmLoader = void 0;
9
- exports.typesNodeVersion = exports.tsLibVersion = exports.swcNodeVersion = exports.swcHelpersVersion = exports.swcCoreVersion = exports.swcCliVersion = exports.prettierVersion = exports.esbuildVersion = exports.nxVersion = exports.releaseTasks = exports.addReleaseConfigForTsSolution = void 0;
8
+ exports.addLocalRegistryScripts = exports.stripGlobToBaseDir = exports.getImportPath = exports.createGlobPatternsForDependencies = exports.normalizeUnitTestRunnerOption = exports.normalizeLinterOption = exports.isValidPackageJsonBuildConfig = exports.addBuildAndWatchDepsTargets = exports.findNpmDependencies = exports.getProjectPackageManagerWorkspaceStateWarningTask = exports.getProjectPackageManagerWorkspaceState = exports.getNpmScope = exports.sortPackageJsonFields = exports.CopyAssetsHandler = exports.addSwcRegisterDependencies = exports.addSwcDependencies = exports.addSwcTestConfig = exports.addSwcConfig = exports.createTmpTsConfig = exports.computeCompilerOptionsPaths = exports.calculateProjectDependencies = exports.calculateProjectBuildableDependencies = exports.compileTypeScript = exports.getNeededCompilerOptionOverrides = exports.isTypescriptVersionAtLeast = exports.getTsConfigModuleResolution = exports.getRangeMinimum = exports.ensureTypescript = exports.createTreeParseConfigHost = exports.getTsConfigBaseOptions = exports.updateTsconfigFiles = exports.shouldConfigureTsSolutionSetup = exports.isUsingTsSolutionSetup = exports.getProjectType = exports.getProjectSourceRoot = exports.getDefinedCustomConditionName = exports.findRuntimeTsConfigName = exports.assertNotUsingTsSolutionSetup = exports.addProjectToTsSolutionWorkspace = exports.TS_SOLUTION_SETUP_TSCONFIG_INPUT = exports.isEsmProject = exports.walkTsconfigExtendsChain = exports.findProjectsNpmDependencies = exports.isBuiltinModuleImport = exports.TargetProjectLocator = exports.requireWithTsconfigFallback = exports.registerTsConfigPaths = exports.registerTsProject = exports.loadTsFile = exports.forceRegisterEsmLoader = void 0;
9
+ exports.typesNodeVersion = exports.tsLibVersion = exports.swcNodeVersion = exports.swcHelpersVersion = exports.swcCoreVersion = exports.swcCliVersion = exports.prettierVersion = exports.esbuildVersion = exports.nxVersion = exports.releaseTasks = exports.addReleaseConfigForTsSolution = exports.addReleaseConfigForNonTsSolution = void 0;
10
10
  // Re-exports of nx-source internals (need `no-restricted-imports` overrides).
11
11
  // eslint-disable-next-line @typescript-eslint/no-restricted-imports
12
12
  var register_1 = require("nx/src/plugins/js/utils/register");
@@ -47,6 +47,8 @@ Object.defineProperty(exports, "updateTsconfigFiles", { enumerable: true, get: f
47
47
  // re-exported here so internal consumers can import it from @nx/js/internal.
48
48
  var create_ts_config_1 = require("./src/utils/typescript/create-ts-config");
49
49
  Object.defineProperty(exports, "getTsConfigBaseOptions", { enumerable: true, get: function () { return create_ts_config_1.getTsConfigBaseOptions; } });
50
+ var ts_config_1 = require("./src/utils/typescript/ts-config");
51
+ Object.defineProperty(exports, "createTreeParseConfigHost", { enumerable: true, get: function () { return ts_config_1.createTreeParseConfigHost; } });
50
52
  var ensure_typescript_1 = require("./src/utils/typescript/ensure-typescript");
51
53
  Object.defineProperty(exports, "ensureTypescript", { enumerable: true, get: function () { return ensure_typescript_1.ensureTypescript; } });
52
54
  var is_typescript_version_at_least_1 = require("./src/utils/is-typescript-version-at-least");
@@ -7,6 +7,7 @@ const internal_1 = require("@nx/devkit/internal");
7
7
  const child_process_1 = require("child_process");
8
8
  const detect_port_1 = tslib_1.__importDefault(require("detect-port"));
9
9
  const node_fs_1 = require("node:fs");
10
+ const devkit_internals_1 = require("nx/src/devkit-internals");
10
11
  const path_1 = require("path");
11
12
  const semver_1 = require("semver");
12
13
  let childProcess;
@@ -66,12 +67,19 @@ async function verdaccioExecutor(options, context) {
66
67
  port: options.port,
67
68
  };
68
69
  }
70
+ function getVerdaccioBinPath() {
71
+ const { packageJson, path: packageJsonPath } = (0, devkit_internals_1.readModulePackageJson)('verdaccio');
72
+ const bin = typeof packageJson.bin === 'string'
73
+ ? packageJson.bin
74
+ : packageJson.bin['verdaccio'];
75
+ return (0, path_1.join)((0, path_1.dirname)(packageJsonPath), bin);
76
+ }
69
77
  /**
70
78
  * Fork the verdaccio process: https://verdaccio.org/docs/verdaccio-programmatically/#using-fork-from-child_process-module
71
79
  */
72
80
  function startVerdaccio(options, workspaceRoot) {
73
81
  return new Promise((resolve, reject) => {
74
- childProcess = (0, child_process_1.fork)(require.resolve('verdaccio/bin/verdaccio'), createVerdaccioOptions(options, workspaceRoot), {
82
+ childProcess = (0, child_process_1.fork)(getVerdaccioBinPath(), createVerdaccioOptions(options, workspaceRoot), {
75
83
  env: {
76
84
  ...process.env,
77
85
  VERDACCIO_HANDLE_KILL_SIGNALS: 'true',
@@ -2,6 +2,6 @@ import { GeneratorCallback, Tree } from '@nx/devkit';
2
2
  import type { LibraryGeneratorSchema, NormalizedLibraryGeneratorOptions } from './schema';
3
3
  export declare function libraryGenerator(tree: Tree, schema: LibraryGeneratorSchema): Promise<GeneratorCallback>;
4
4
  export declare function libraryGeneratorInternal(tree: Tree, schema: LibraryGeneratorSchema): Promise<GeneratorCallback>;
5
- export type AddLintOptions = Pick<NormalizedLibraryGeneratorOptions, 'name' | 'linter' | 'projectRoot' | 'unitTestRunner' | 'js' | 'setParserOptionsProject' | 'rootProject' | 'bundler' | 'addPlugin' | 'isUsingTsSolutionConfig'>;
5
+ export type AddLintOptions = Pick<NormalizedLibraryGeneratorOptions, 'name' | 'linter' | 'projectRoot' | 'unitTestRunner' | 'js' | 'enableTypedLinting' | 'setParserOptionsProject' | 'rootProject' | 'bundler' | 'addPlugin' | 'isUsingTsSolutionConfig'>;
6
6
  export declare function addLint(tree: Tree, options: AddLintOptions): Promise<GeneratorCallback>;
7
7
  export default libraryGenerator;
@@ -249,6 +249,9 @@ async function configureProject(tree, options) {
249
249
  }
250
250
  async function addLint(tree, options) {
251
251
  const { lintProjectGenerator } = (0, devkit_1.ensurePackage)('@nx/eslint', versions_1.nxVersion);
252
+ const { addOverrideToLintConfig, lintConfigHasOverride, isEslintConfigSupported, updateOverrideInLintConfig, addIgnoresToLintConfig, isTypedLintingEnabled,
253
+ // nx-ignore-next-line
254
+ } = require('@nx/eslint/internal');
252
255
  const projectConfiguration = (0, devkit_1.readProjectConfiguration)(tree, options.name);
253
256
  const task = await lintProjectGenerator(tree, {
254
257
  project: options.name,
@@ -258,15 +261,12 @@ async function addLint(tree, options) {
258
261
  (0, devkit_1.joinPathFragments)(options.projectRoot, 'tsconfig.lib.json'),
259
262
  ],
260
263
  unitTestRunner: options.unitTestRunner,
261
- setParserOptionsProject: options.setParserOptionsProject,
264
+ enableTypedLinting: isTypedLintingEnabled(options),
262
265
  rootProject: options.rootProject,
263
266
  addPlugin: options.addPlugin,
264
267
  // Since the build target is inferred now, we need to let the generator know to add @nx/dependency-checks regardless.
265
268
  addPackageJsonDependencyChecks: options.bundler !== 'none',
266
269
  });
267
- const { addOverrideToLintConfig, lintConfigHasOverride, isEslintConfigSupported, updateOverrideInLintConfig, addIgnoresToLintConfig,
268
- // nx-ignore-next-line
269
- } = require('@nx/eslint/internal');
270
270
  // if config is not supported, we don't need to do anything
271
271
  if (!isEslintConfigSupported(tree)) {
272
272
  return task;
@@ -23,6 +23,10 @@ export interface LibraryGeneratorSchema {
23
23
  strict?: boolean;
24
24
  publishable?: boolean;
25
25
  buildable?: boolean;
26
+ enableTypedLinting?: boolean;
27
+ /**
28
+ * @deprecated Use `enableTypedLinting` instead. This option will be removed in Nx v24.
29
+ */
26
30
  setParserOptionsProject?: boolean;
27
31
  config?: 'workspace' | 'project' | 'npm-scripts';
28
32
  compiler?: Compiler;
@@ -99,11 +99,17 @@
99
99
  "description": "Generate a buildable library.",
100
100
  "x-deprecated": "Use the `bundler` option for greater control (swc, tsc, rollup, vite, esbuild, none)."
101
101
  },
102
- "setParserOptionsProject": {
102
+ "enableTypedLinting": {
103
103
  "type": "boolean",
104
- "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
104
+ "description": "Whether to enable typed linting. For flat configs, this configures the recommended `parserOptions.projectService` and `tsconfigRootDir`. For legacy `.eslintrc` configs, this configures `parserOptions.project`. We do not enable this by default for lint performance reasons.",
105
105
  "default": false
106
106
  },
107
+ "setParserOptionsProject": {
108
+ "type": "boolean",
109
+ "description": "Deprecated alias for `enableTypedLinting`.",
110
+ "default": false,
111
+ "x-deprecated": "Use `enableTypedLinting` instead. This option will be removed in Nx v24."
112
+ },
107
113
  "config": {
108
114
  "type": "string",
109
115
  "enum": ["workspace", "project", "npm-scripts"],
@@ -181,11 +181,7 @@ function updatePackageJsonForTsc(tree, options, project) {
181
181
  if (!ts) {
182
182
  ts = (0, ensure_typescript_1.ensureTypescript)();
183
183
  }
184
- const tsconfig = (0, ts_config_1.readTsConfig)(options.tsConfig, {
185
- ...ts.sys,
186
- readFile: (p) => tree.read(p, 'utf-8'),
187
- fileExists: (p) => tree.exists(p),
188
- });
184
+ const tsconfig = (0, ts_config_1.readTsConfig)(options.tsConfig, (0, ts_config_1.createTreeParseConfigHost)(tree));
189
185
  let main;
190
186
  let rootDir;
191
187
  let outputPath;
@@ -21,8 +21,9 @@ import { type Tree } from '@nx/devkit';
21
21
  * the pre-TS6 behavior. It is itself deprecated (removed in TS7), so pass
22
22
  * 2 silences it, deferring the interop change to the eventual TS7 work.
23
23
  * - Config-load flag: set `ignoreDeprecations: "6.0"` on every file named
24
- * exactly `tsconfig.json`, the name jest/ts-node auto-resolve (walking up
25
- * from the file they compile, such as jest.config.ts). ts-node injects a
24
+ * exactly `tsconfig.json`, the name ts-node auto-resolves when jest compiles
25
+ * a config file like jest.config.ts (walking up from the working directory,
26
+ * not the config file's own directory). ts-node injects a
26
27
  * `target: es5` when the config leaves it unset, and es5 is a TS6-deprecated
27
28
  * value (TS5107); the flag (which ts-node passes through) keeps that load
28
29
  * silent. Set unconditionally, since any `tsconfig.json` is a potential
@@ -5,6 +5,7 @@ const devkit_1 = require("@nx/devkit");
5
5
  const node_path_1 = require("node:path");
6
6
  const jsonc_parser_1 = require("jsonc-parser");
7
7
  const ensure_typescript_1 = require("../../utils/typescript/ensure-typescript");
8
+ const ts_config_1 = require("../../utils/typescript/ts-config");
8
9
  const FORMATTING_OPTIONS = {
9
10
  formattingOptions: { keepLines: true, insertSpaces: true, tabSize: 2 },
10
11
  };
@@ -31,8 +32,9 @@ let tsModule;
31
32
  * the pre-TS6 behavior. It is itself deprecated (removed in TS7), so pass
32
33
  * 2 silences it, deferring the interop change to the eventual TS7 work.
33
34
  * - Config-load flag: set `ignoreDeprecations: "6.0"` on every file named
34
- * exactly `tsconfig.json`, the name jest/ts-node auto-resolve (walking up
35
- * from the file they compile, such as jest.config.ts). ts-node injects a
35
+ * exactly `tsconfig.json`, the name ts-node auto-resolves when jest compiles
36
+ * a config file like jest.config.ts (walking up from the working directory,
37
+ * not the config file's own directory). ts-node injects a
36
38
  * `target: es5` when the config leaves it unset, and es5 is a TS6-deprecated
37
39
  * value (TS5107); the flag (which ts-node passes through) keeps that load
38
40
  * silent. Set unconditionally, since any `tsconfig.json` is a potential
@@ -53,14 +55,11 @@ let tsModule;
53
55
  async function default_1(tree) {
54
56
  tsModule ??= (0, ensure_typescript_1.ensureTypescript)();
55
57
  const ts = tsModule;
56
- // Tree-backed host so TypeScript resolves `extends` against pass 1's pending
57
- // writes; the `readDirectory` no-op skips the source-file scan, since only the
58
- // merged compiler options matter here, not the file list.
59
- const parseHost = {
60
- ...ts.sys,
61
- readFile: (filePath) => tree.read(filePath, 'utf-8') ?? undefined,
62
- readDirectory: () => [],
63
- };
58
+ // Tree-faithful host so TypeScript resolves `extends` against pass 1's pending
59
+ // writes and against package-provided bases (a naive `tree.read` host misses
60
+ // the latter). The host's `readDirectory` no-op skips the source-file scan,
61
+ // since only the merged compiler options matter here, not the file list.
62
+ const parseHost = (0, ts_config_1.createTreeParseConfigHost)(tree);
64
63
  const tsconfigPaths = await (0, devkit_1.globAsync)(tree, ['**/tsconfig*.json']);
65
64
  // Pass 1: pins and config-load flags, so pass 2's inheritance check sees them.
66
65
  let defaultsPinCount = 0;
@@ -178,7 +177,14 @@ function silenceDeprecations(tree, ts, parseHost, tsconfigPath) {
178
177
  // The `extends`-merged main options, normalized by TypeScript. Passing the
179
178
  // config object (not the path) keeps TypeScript from re-reading this file; it
180
179
  // reads only the ancestors.
181
- const mainOptions = ts.parseJsonConfigFileContent({ extends: own.extends, compilerOptions: mainIsObject ? mainBlock : {} }, parseHost, (0, node_path_1.dirname)(tsconfigPath)).options;
180
+ const mainParsed = ts.parseJsonConfigFileContent({ extends: own.extends, compilerOptions: mainIsObject ? mainBlock : {} }, parseHost, (0, node_path_1.dirname)(tsconfigPath));
181
+ // A base that neither the tree nor `tsc` can read leaves the merged options
182
+ // incomplete, so a value it deprecates would be missed. The config does not
183
+ // compile in that state anyway; surface it rather than silently guessing.
184
+ if ((0, ts_config_1.extendsResolutionFailed)(mainParsed.errors)) {
185
+ devkit_1.logger.warn(`Could not resolve the "extends" chain of "${tsconfigPath}"; a TypeScript 6 deprecated option inherited from the unresolved base may have been missed. Make sure the config compiles.`);
186
+ }
187
+ const mainOptions = mainParsed.options;
182
188
  const mainDeprecated = hasDeprecatedOption(ts, mainOptions);
183
189
  let contents = original;
184
190
  let changed = false;
@@ -207,12 +207,14 @@ class JsVersionActions extends release_1.VersionActions {
207
207
  const depText = numDependenciesToUpdate === 1 ? 'dependency' : 'dependencies';
208
208
  logMessages.push(`✍️ Updated ${numDependenciesToUpdate} ${depText} in manifest: ${manifestToUpdate.manifestPath}`);
209
209
  }
210
- // Update catalog definitions in pnpm-workspace.yaml
210
+ // Update catalog definitions in the package manager's catalog file
211
211
  if (catalogUpdates.length > 0) {
212
212
  // catalogManager is guaranteed to be defined when there are catalog updates
213
213
  catalogManager.updateCatalogVersions(tree, catalogUpdates);
214
214
  const catalogText = catalogUpdates.length === 1 ? 'entry' : 'entries';
215
- logMessages.push(`✍️ Updated ${catalogUpdates.length} catalog ${catalogText} in pnpm-workspace.yaml`);
215
+ logMessages.push(`✍️ Updated ${catalogUpdates.length} catalog ${catalogText} in ${catalogManager
216
+ .getCatalogDefinitionFilePaths()
217
+ .join(', ')}`);
216
218
  }
217
219
  return logMessages;
218
220
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getNeededCompilerOptionOverrides = getNeededCompilerOptionOverrides;
4
4
  const path_1 = require("path");
5
5
  const ensure_typescript_1 = require("./ensure-typescript");
6
+ const ts_config_1 = require("./ts-config");
6
7
  const optionEnumTypeMap = {
7
8
  importsNotUsedAsValues: 'ImportsNotUsedAsValues',
8
9
  jsx: 'JsxEmit',
@@ -21,11 +22,8 @@ function getNeededCompilerOptionOverrides(tree, compilerOptions, tsConfigPath) {
21
22
  if (!ts) {
22
23
  ts = (0, ensure_typescript_1.ensureTypescript)();
23
24
  }
24
- const tsSysFromTree = {
25
- ...ts.sys,
26
- readFile: (path) => tree.read(path, 'utf-8'),
27
- };
28
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(tsConfigPath, tsSysFromTree.readFile).config, tsSysFromTree, (0, path_1.dirname)(tsConfigPath));
25
+ const host = (0, ts_config_1.createTreeParseConfigHost)(tree);
26
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(tsConfigPath, host.readFile).config, host, (0, path_1.dirname)(tsConfigPath));
29
27
  // ModuleKind: { CommonJS: 'commonjs', ... } => ModuleKind: { commonjs: 'CommonJS', ... }
30
28
  const reversedCompilerOptionsEnumValues = {
31
29
  JsxEmit: reverseEnum(ts.server.protocol.JsxEmit),
@@ -1,7 +1,57 @@
1
1
  import { Tree } from '@nx/devkit';
2
2
  import type * as ts from 'typescript';
3
- export declare function readTsConfig(tsConfigPath: string, sys?: ts.System): ts.ParsedCommandLine;
3
+ export declare function readTsConfig(tsConfigPath: string, sys?: ts.ParseConfigHost): ts.ParsedCommandLine;
4
4
  export declare function readTsConfigFromTree(tree: Tree, tsConfigPath: string): ts.ParsedCommandLine;
5
+ /**
6
+ * The members `createTreeParseConfigHost` overrides: the file system ones are
7
+ * backed by the `Tree`, `realpath`/`getCurrentDirectory` are anchored to
8
+ * `tree.root`, and `useCaseSensitiveFileNames` is platform-derived from
9
+ * `ts.sys`. The returned object still spreads `ts.sys` (TypeScript duck-types
10
+ * the host and reads members this type does not name), so the narrow type is a
11
+ * compile-time guard on Nx callers: everything outside it is disk-backed and
12
+ * escapes the tree.
13
+ */
14
+ export type TreeParseConfigHost = Required<Pick<ts.ParseConfigHost, 'useCaseSensitiveFileNames' | 'readDirectory' | 'readFile' | 'fileExists' | 'directoryExists' | 'realpath' | 'getCurrentDirectory'>>;
15
+ /**
16
+ * A TypeScript host, backed by the devkit `Tree`, that resolves `extends` the
17
+ * way real `tsc` does. The naive `{ ...ts.sys, readFile: (p) => tree.read(p) }`
18
+ * host resolves less than `tsc`:
19
+ * - A package-form `extends` (e.g. `@tsconfig/node14/tsconfig.json`) is
20
+ * realpath-absolutized by TypeScript; `Tree.read`/`Tree.exists` re-root an
21
+ * absolute path under the workspace, so the base reads as nothing and its
22
+ * options silently vanish from the merged result (the failure surfaces only
23
+ * as a TS5083/TS6053 in `errors`, which callers discard).
24
+ * - An extension-less `extends` resolves against `process.cwd()` when
25
+ * existence comes from `ts.sys`; Nx never chdirs to the workspace root, so it
26
+ * resolves only when the command runs from the root.
27
+ *
28
+ * This host maps absolute paths under `tree.root` back to tree-relative for the
29
+ * tree lookups and falls through to `fs` for paths that resolve outside the
30
+ * workspace (a pnpm store or a `link:`/`file:` target). Resolution is anchored
31
+ * to `tree.root` rather than the working directory: TypeScript resolves a
32
+ * package-form base as a relative path and hands it to `realpath`, and
33
+ * `ts.sys.realpath` would resolve it against `process.cwd()`, so a workspace
34
+ * whose root is not an ancestor of the working directory would read a
35
+ * same-named package from the wrong tree.
36
+ *
37
+ * `readDirectory` is a no-op: the source-file scan is skipped because callers
38
+ * read only the merged `options`, never the file list. A config that resolves
39
+ * its inputs through `include` (or the default glob) then reports a TS18003
40
+ * unless it also carries `files` or `references`, so `errors.length > 0` is not
41
+ * a usable signal; match specific codes (TS5083/TS6053) instead.
42
+ */
43
+ export declare function createTreeParseConfigHost(tree: Tree): TreeParseConfigHost;
44
+ /**
45
+ * Whether a `ParsedCommandLine` produced with `createTreeParseConfigHost` failed
46
+ * to resolve an `extends` target: TS5083 (the base resolved but could not be
47
+ * read) or TS6053 (the base could not be resolved at all). Because the host
48
+ * resolves what real `tsc` resolves, this only fires for a config whose extends
49
+ * chain `tsc` cannot read either. The no-op `readDirectory` also adds a TS18003
50
+ * whenever the config resolves inputs through `include` and carries neither
51
+ * `files` nor `references`, so `errors.length > 0` is not a usable signal; match
52
+ * these two codes.
53
+ */
54
+ export declare function extendsResolutionFailed(errors: readonly ts.Diagnostic[]): boolean;
5
55
  export declare function getRootTsConfigPathInTree(tree: Tree): string | null;
6
56
  export declare function getRelativePathToRootTsConfig(tree: Tree, targetPath: string): string;
7
57
  export declare function getRootTsConfigPath(): string | null;
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readTsConfig = readTsConfig;
4
4
  exports.readTsConfigFromTree = readTsConfigFromTree;
5
+ exports.createTreeParseConfigHost = createTreeParseConfigHost;
6
+ exports.extendsResolutionFailed = extendsResolutionFailed;
5
7
  exports.getRootTsConfigPathInTree = getRootTsConfigPathInTree;
6
8
  exports.getRelativePathToRootTsConfig = getRelativePathToRootTsConfig;
7
9
  exports.getRootTsConfigPath = getRootTsConfigPath;
@@ -32,6 +34,118 @@ function readTsConfigFromTree(tree, tsConfigPath) {
32
34
  };
33
35
  return readTsConfig(tsConfigPath, tsSysFromTree);
34
36
  }
37
+ /**
38
+ * A TypeScript host, backed by the devkit `Tree`, that resolves `extends` the
39
+ * way real `tsc` does. The naive `{ ...ts.sys, readFile: (p) => tree.read(p) }`
40
+ * host resolves less than `tsc`:
41
+ * - A package-form `extends` (e.g. `@tsconfig/node14/tsconfig.json`) is
42
+ * realpath-absolutized by TypeScript; `Tree.read`/`Tree.exists` re-root an
43
+ * absolute path under the workspace, so the base reads as nothing and its
44
+ * options silently vanish from the merged result (the failure surfaces only
45
+ * as a TS5083/TS6053 in `errors`, which callers discard).
46
+ * - An extension-less `extends` resolves against `process.cwd()` when
47
+ * existence comes from `ts.sys`; Nx never chdirs to the workspace root, so it
48
+ * resolves only when the command runs from the root.
49
+ *
50
+ * This host maps absolute paths under `tree.root` back to tree-relative for the
51
+ * tree lookups and falls through to `fs` for paths that resolve outside the
52
+ * workspace (a pnpm store or a `link:`/`file:` target). Resolution is anchored
53
+ * to `tree.root` rather than the working directory: TypeScript resolves a
54
+ * package-form base as a relative path and hands it to `realpath`, and
55
+ * `ts.sys.realpath` would resolve it against `process.cwd()`, so a workspace
56
+ * whose root is not an ancestor of the working directory would read a
57
+ * same-named package from the wrong tree.
58
+ *
59
+ * `readDirectory` is a no-op: the source-file scan is skipped because callers
60
+ * read only the merged `options`, never the file list. A config that resolves
61
+ * its inputs through `include` (or the default glob) then reports a TS18003
62
+ * unless it also carries `files` or `references`, so `errors.length > 0` is not
63
+ * a usable signal; match specific codes (TS5083/TS6053) instead.
64
+ */
65
+ function createTreeParseConfigHost(tree) {
66
+ if (!tsModule) {
67
+ tsModule = (0, ensure_typescript_1.ensureTypescript)();
68
+ }
69
+ // Classify with `relative` rather than a string prefix: it normalizes
70
+ // separators, so an absolute path TypeScript hands over with forward slashes
71
+ // still matches a `tree.root` carrying platform separators, and it respects
72
+ // the segment boundary, so a sibling of the root (`/repo/core-utils` for
73
+ // `/repo/core`) is not taken for a path inside it.
74
+ const isOutsideRoot = (path) => {
75
+ if (!(0, path_1.isAbsolute)(path)) {
76
+ return false;
77
+ }
78
+ const rel = (0, path_1.relative)(tree.root, path);
79
+ return rel === '..' || rel.startsWith(`..${path_1.sep}`) || (0, path_1.isAbsolute)(rel);
80
+ };
81
+ const toTreePath = (path) => (0, path_1.isAbsolute)(path) ? (0, path_1.relative)(tree.root, path) || '.' : path;
82
+ // `ts.sys` gates `readFile`/`fileExists` on `isFile`, so a directory is never
83
+ // read as a config file. Both branches mirror that, which lets an
84
+ // extension-less `extends` ("./config") that collides with a same-named
85
+ // directory fall through to its `.json` sibling. Out-of-root, a bare
86
+ // `existsSync` answers true for a directory and `readFileSync` on it throws
87
+ // EISDIR (TS5012, which the extends-failure guard does not catch); in-root,
88
+ // `tree.exists` also answers true for a directory, so `fileExists` gates on
89
+ // `tree.isFile`.
90
+ const isFileOnDisk = (path) => {
91
+ try {
92
+ return (0, fs_1.statSync)(path).isFile();
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ };
98
+ const isDirectoryOnDisk = (path) => {
99
+ try {
100
+ return (0, fs_1.statSync)(path).isDirectory();
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ };
106
+ return {
107
+ ...tsModule.sys,
108
+ readDirectory: () => [],
109
+ getCurrentDirectory: () => tree.root,
110
+ // A relative path anchored to the workspace instead of `process.cwd()`; an
111
+ // absolute one still goes through `ts.sys` so a symlinked in-root
112
+ // `node_modules` entry resolves to its real (out-of-root) target.
113
+ realpath: (path) => tsModule.sys.realpath((0, path_1.isAbsolute)(path) ? path : (0, path_1.join)(tree.root, path)),
114
+ readFile: (path) => isOutsideRoot(path)
115
+ ? isFileOnDisk(path)
116
+ ? (0, fs_1.readFileSync)(path, 'utf-8')
117
+ : undefined
118
+ : (tree.read(toTreePath(path), 'utf-8') ?? undefined),
119
+ fileExists: (path) => {
120
+ if (isOutsideRoot(path)) {
121
+ return isFileOnDisk(path);
122
+ }
123
+ const treePath = toTreePath(path);
124
+ return tree.exists(treePath) && tree.isFile(treePath);
125
+ },
126
+ directoryExists: (path) => {
127
+ if (isOutsideRoot(path)) {
128
+ return isDirectoryOnDisk(path);
129
+ }
130
+ const treePath = toTreePath(path);
131
+ return tree.exists(treePath) && !tree.isFile(treePath);
132
+ },
133
+ };
134
+ }
135
+ /**
136
+ * Whether a `ParsedCommandLine` produced with `createTreeParseConfigHost` failed
137
+ * to resolve an `extends` target: TS5083 (the base resolved but could not be
138
+ * read) or TS6053 (the base could not be resolved at all). Because the host
139
+ * resolves what real `tsc` resolves, this only fires for a config whose extends
140
+ * chain `tsc` cannot read either. The no-op `readDirectory` also adds a TS18003
141
+ * whenever the config resolves inputs through `include` and carries neither
142
+ * `files` nor `references`, so `errors.length > 0` is not a usable signal; match
143
+ * these two codes.
144
+ */
145
+ function extendsResolutionFailed(errors) {
146
+ // 5083: Cannot read file '{0}'. 6053: File '{0}' not found.
147
+ return errors.some((error) => error.code === 5083 || error.code === 6053);
148
+ }
35
149
  function getRootTsConfigPathInTree(tree) {
36
150
  for (const path of ['tsconfig.base.json', 'tsconfig.json']) {
37
151
  if (tree.exists(path)) {
package/migrations.json CHANGED
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "23-1-0-add-ignore-deprecations-for-ts6": {
33
33
  "version": "23.1.0-beta.8",
34
- "description": "Adds `\"ignoreDeprecations\": \"6.0\"` to tsconfig files whose compilerOptions (or ts-node.compilerOptions) carry a TypeScript 6 deprecated option value, set directly or inherited through `extends` including from a base this migration does not edit (e.g. moduleResolution node/node10/classic, baseUrl, target es5, esModuleInterop false, outFile, module amd/umd/system/none, alwaysStrict false, allowSyntheticDefaultImports false, downlevelIteration set). Also pins `\"strict\": false`, `\"noUncheckedSideEffectImports\": false`, `\"types\": [\"*\"]`, and `\"esModuleInterop\": false` in chain-root tsconfigs (no \"extends\") that lack each key, preserving pre-TS6 behavior where these defaults changed; `esModuleInterop` false is itself deprecated (removed in TS7) so it also receives `ignoreDeprecations`, deferring the interop change to the TS7 migration. Also adds `ignoreDeprecations` to every `tsconfig.json` (the exact name jest/ts-node auto-resolve when compiling a config file; ts-node injects a deprecated default `target: es5` there, so the load hits a TS6 error even on a clean config), even one with no deprecated value of its own; a stale local `ignoreDeprecations` (e.g. `5.0`) that would otherwise override the inherited flag is upgraded.",
34
+ "description": "Adds `\"ignoreDeprecations\": \"6.0\"` to tsconfig files whose compilerOptions (or ts-node.compilerOptions) carry a TypeScript 6 deprecated option value, set directly or inherited through `extends` including from a base this migration does not edit (e.g. moduleResolution node/node10/classic, baseUrl, target es5, esModuleInterop false, outFile, module amd/umd/system/none, alwaysStrict false, allowSyntheticDefaultImports false, downlevelIteration set). Also pins `\"strict\": false`, `\"noUncheckedSideEffectImports\": false`, `\"types\": [\"*\"]`, and `\"esModuleInterop\": false` in chain-root tsconfigs (no \"extends\") that lack each key, preserving pre-TS6 behavior where these defaults changed; `esModuleInterop` false is itself deprecated (removed in TS7) so it also receives `ignoreDeprecations`, deferring the interop change to the TS7 migration. Also adds `ignoreDeprecations` to every `tsconfig.json` (the exact name jest/ts-node auto-resolve when compiling a config file; ts-node injects a deprecated default `target: es5` there, so the load can hit a TS6 error even on a clean config), even one with no deprecated value of its own; a stale local `ignoreDeprecations` (e.g. `5.0`) that would otherwise override the inherited flag is upgraded.",
35
35
  "requires": {
36
36
  "typescript": ">=6.0.0"
37
37
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "23.2.0-beta.2",
3
+ "version": "23.2.0-beta.4",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -147,11 +147,11 @@
147
147
  "source-map-support": "0.5.19",
148
148
  "tinyglobby": "^0.2.12",
149
149
  "tslib": "^2.3.0",
150
- "@nx/workspace": "23.2.0-beta.2",
151
- "@nx/devkit": "23.2.0-beta.2"
150
+ "@nx/devkit": "23.2.0-beta.4",
151
+ "@nx/workspace": "23.2.0-beta.4"
152
152
  },
153
153
  "devDependencies": {
154
- "nx": "23.2.0-beta.2"
154
+ "nx": "23.2.0-beta.4"
155
155
  },
156
156
  "peerDependencies": {
157
157
  "@swc/cli": ">=0.6.0 <0.9.0",