@nx/js 24.0.0-beta.2 → 24.0.0-beta.3

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.
Files changed (30) hide show
  1. package/dist/internal.d.ts +2 -0
  2. package/dist/internal.js +8 -2
  3. package/dist/src/generators/init/init.js +3 -33
  4. package/dist/src/generators/init/schema.d.ts +0 -1
  5. package/dist/src/generators/library/library.d.ts +1 -1
  6. package/dist/src/generators/library/library.js +3 -11
  7. package/dist/src/generators/library/schema.d.ts +0 -1
  8. package/dist/src/generators/setup-build/generator.js +0 -5
  9. package/dist/src/plugins/scripts/create-script-nodes.d.ts +6 -2
  10. package/dist/src/plugins/scripts/create-script-nodes.js +18 -71
  11. package/dist/src/plugins/scripts/node-plugin.d.ts +2 -1
  12. package/dist/src/plugins/scripts/shebang.d.ts +7 -2
  13. package/dist/src/plugins/scripts/shebang.js +27 -10
  14. package/dist/src/plugins/scripts/target-name.d.ts +9 -0
  15. package/dist/src/plugins/scripts/target-name.js +19 -0
  16. package/dist/src/plugins/scripts/tsx-plugin.d.ts +3 -2
  17. package/dist/src/plugins/typescript/build-typechecks.d.ts +8 -0
  18. package/dist/src/plugins/typescript/build-typechecks.js +45 -0
  19. package/dist/src/plugins/typescript/plugin.js +107 -38
  20. package/dist/src/plugins/typescript/typecheck-target.d.ts +54 -0
  21. package/dist/src/plugins/typescript/typecheck-target.js +81 -0
  22. package/dist/src/utils/add-linting-to-project.d.ts +0 -2
  23. package/dist/src/utils/add-linting-to-project.js +0 -3
  24. package/dist/src/utils/typescript/add-typescript-plugin.d.ts +22 -0
  25. package/dist/src/utils/typescript/add-typescript-plugin.js +145 -0
  26. package/dist/src/utils/typescript/ts-solution-setup.d.ts +1 -1
  27. package/dist/src/utils/typescript/ts-solution-setup.js +1 -10
  28. package/package.json +11 -11
  29. package/dist/src/plugins/scripts/frontmatter.d.ts +0 -6
  30. package/dist/src/plugins/scripts/frontmatter.js +0 -52
@@ -7,6 +7,9 @@ const node_fs_1 = require("node:fs");
7
7
  const node_path_1 = require("node:path");
8
8
  const picomatch = require("picomatch");
9
9
  const util_1 = require("./util");
10
+ const typecheck_target_1 = require("./typecheck-target");
11
+ const build_typechecks_1 = require("./build-typechecks");
12
+ const ts_solution_setup_1 = require("../../utils/typescript/ts-solution-setup");
10
13
  let ts;
11
14
  const resolvedTypescriptPaths = {};
12
15
  function resolveTypescriptPath(projectRoot, workspaceRoot) {
@@ -119,14 +122,16 @@ exports.createNodesV2 = [
119
122
  projectContexts: new Map(),
120
123
  configContexts: new Map(),
121
124
  referenceExpansionCache: new Map(),
125
+ siblingFiles: new Map(),
122
126
  });
123
127
  const cache = cacheStore.get(cacheKey);
124
128
  initializeTsConfigCache(configFilePaths, context.workspaceRoot, cache);
125
129
  const normalizedOptions = normalizePluginOptions(options);
130
+ const isUsingTsSolutionSetup = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(new internal_1.FsTree(context.workspaceRoot, false));
126
131
  const packageManager = (0, devkit_1.detectPackageManager)(context.workspaceRoot);
127
132
  const pmc = (0, devkit_1.getPackageManagerCommand)(packageManager);
128
133
  const lockFileName = (0, internal_1.getLockFileName)(packageManager);
129
- const { configFilePaths: validConfigFilePaths, hashes, projectRoots, } = await resolveValidConfigFilesAndHashes(configFilePaths, normalizedOptions, optionsHash, context, cache, lockFileName);
134
+ const { configFilePaths: validConfigFilePaths, hashes, projectRoots, } = await resolveValidConfigFilesAndHashes(configFilePaths, normalizedOptions, (0, internal_1.hashObject)({ options, isUsingTsSolutionSetup }), context, cache, lockFileName, isUsingTsSolutionSetup);
130
135
  try {
131
136
  return await (0, devkit_1.createNodesFromFiles)((configFilePath, options, context, idx) => {
132
137
  const projectRoot = projectRoots[idx];
@@ -134,7 +139,7 @@ exports.createNodesV2 = [
134
139
  const targetsCacheKey = `${hash}_${configFilePath}`;
135
140
  const absolutePath = (0, node_path_1.join)(context.workspaceRoot, configFilePath);
136
141
  const configContext = getConfigContext(absolutePath, context.workspaceRoot, cache);
137
- targetsCache[targetsCacheKey] ??= buildTscTargets(configContext, options, context, validConfigFilePaths, cache, pmc);
142
+ targetsCache[targetsCacheKey] ??= buildTscTargets(configContext, options, context, validConfigFilePaths, cache, pmc, isUsingTsSolutionSetup);
138
143
  const { targets } = targetsCache[targetsCacheKey];
139
144
  return {
140
145
  projects: {
@@ -159,7 +164,7 @@ exports.createNodesV2 = [
159
164
  },
160
165
  ];
161
166
  exports.createNodes = exports.createNodesV2;
162
- async function resolveValidConfigFilesAndHashes(configFilePaths, options, optionsHash, context, cache, lockFileName) {
167
+ async function resolveValidConfigFilesAndHashes(configFilePaths, options, optionsHash, context, cache, lockFileName, isUsingTsSolutionSetup) {
163
168
  const lockFileHash = (0, internal_1.hashFile)((0, node_path_1.join)(context.workspaceRoot, lockFileName)) ?? '';
164
169
  const validConfigFilePaths = [];
165
170
  const hashes = [];
@@ -180,7 +185,13 @@ async function resolveValidConfigFilesAndHashes(configFilePaths, options, option
180
185
  }
181
186
  projectRoots.push(projectRoot);
182
187
  validConfigFilePaths.push(configFilePath);
183
- hashes.push(await getConfigFileHash(configFilePath, context.workspaceRoot, configContext.project, optionsHash, lockFileHash, cache));
188
+ const configFileHash = await getConfigFileHash(configFilePath, context.workspaceRoot, configContext.project, optionsHash, lockFileHash, cache);
189
+ hashes.push(isUsingTsSolutionSetup
190
+ ? configFileHash
191
+ : (0, devkit_1.hashArray)([
192
+ configFileHash,
193
+ ...getNoEmitTypecheckHashInputs(configContext.project, context.workspaceRoot, cache),
194
+ ]));
184
195
  }
185
196
  return { configFilePaths: validConfigFilePaths, hashes, projectRoots };
186
197
  }
@@ -226,7 +237,7 @@ function checkIfConfigFileShouldBeProject(config, cache) {
226
237
  return false;
227
238
  }
228
239
  // Do not create a project if package.json and project.json isn't there.
229
- const siblingFiles = (0, node_fs_1.readdirSync)(config.project.absolute);
240
+ const siblingFiles = readSiblingFiles(config.project, cache);
230
241
  if (!siblingFiles.includes('package.json') &&
231
242
  !siblingFiles.includes('project.json')) {
232
243
  return false;
@@ -247,7 +258,7 @@ function checkIfConfigFileShouldBeProject(config, cache) {
247
258
  }
248
259
  return true;
249
260
  }
250
- function buildTscTargets(config, options, context, configFiles, cache, pmc) {
261
+ function buildTscTargets(config, options, context, configFiles, cache, pmc, isUsingTsSolutionSetup) {
251
262
  const targets = {};
252
263
  const namedInputs = (0, internal_1.getNamedInputs)(config.project.root, context);
253
264
  const tsConfig = retrieveTsConfigFromCache(config.absolutePath, context.workspaceRoot, cache);
@@ -259,18 +270,28 @@ function buildTscTargets(config, options, context, configFiles, cache, pmc) {
259
270
  const externalProjectReferences = resolveShallowExternalProjectReferences(tsConfig, context.workspaceRoot, config.project, cache);
260
271
  const targetName = options.typecheck.targetName;
261
272
  const compiler = options.compiler;
262
- if (!targets[targetName]) {
263
- let command = `${compiler} --build ${options.typecheck.configName} --emitDeclarationOnly${options.verboseOutput ? ' --verbose' : ''}`;
264
- if (tsConfig.options.noEmit ||
265
- Object.values(internalProjectReferences).some((ref) => ref.options.noEmit) ||
266
- Object.values(externalProjectReferences).some((ref) => ref.options.noEmit)) {
267
- // `tsc --build` does not work with `noEmit: true`
268
- command = `echo "The 'typecheck' target is disabled because one or more project references set 'noEmit: true' in their tsconfig. Remove this property to resolve this issue."`;
273
+ // `tsc --build` needs composite projects, so other setups check without
274
+ // emitting, and skip projects whose build already type-checks.
275
+ if (!isUsingTsSolutionSetup) {
276
+ const siblingFiles = readSiblingFiles(config.project, cache);
277
+ if (!(0, build_typechecks_1.buildTypechecksProject)(siblingFiles, readProjectTargets(config.project.absolute, siblingFiles))) {
278
+ targets[targetName] = (0, typecheck_target_1.createTypecheckTarget)({
279
+ mode: 'noEmit',
280
+ projectRoot: config.project.normalized,
281
+ pmc,
282
+ tsConfig: (0, typecheck_target_1.selectTypecheckTsConfig)(siblingFiles.filter((file) => /^tsconfig.*\.json$/.test(file))),
283
+ compiler,
284
+ // Like the framework targets it replaces: `{workspaceRoot}` inputs
285
+ // would mark the project affected by any root tsconfig change.
286
+ namedInputs,
287
+ });
269
288
  }
270
- const dependsOn = [`^${targetName}`];
289
+ }
290
+ else if (!targets[targetName]) {
291
+ let buildTargetName;
271
292
  if (options.build && targets[options.build.targetName]) {
272
293
  // we already processed and have a build target
273
- dependsOn.unshift(options.build.targetName);
294
+ buildTargetName = options.build.targetName;
274
295
  }
275
296
  else if (options.build) {
276
297
  // check if the project will have a build target
@@ -278,29 +299,29 @@ function buildTscTargets(config, options, context, configFiles, cache, pmc) {
278
299
  if (configFiles.some((f) => f === buildConfigPath) &&
279
300
  (options.build.skipBuildCheck ||
280
301
  (0, util_1.isValidPackageJsonBuildConfig)(retrieveTsConfigFromCache(buildConfigPath, context.workspaceRoot, cache), context.workspaceRoot, config.project.root))) {
281
- dependsOn.unshift(options.build.targetName);
302
+ buildTargetName = options.build.targetName;
282
303
  }
283
304
  }
284
- targets[targetName] = {
285
- dependsOn,
286
- command,
287
- options: { cwd: config.project.normalized },
288
- cache: true,
305
+ const target = (0, typecheck_target_1.createTypecheckTarget)({
306
+ mode: 'build',
307
+ projectRoot: config.project.normalized,
308
+ pmc,
309
+ tsConfig: options.typecheck.configName,
310
+ compiler,
311
+ targetName,
312
+ buildTargetName,
313
+ verboseOutput: options.verboseOutput,
289
314
  inputs: getInputs(namedInputs, config, tsConfig, internalProjectReferences, context.workspaceRoot, cache),
290
315
  outputs: getOutputs(config, tsConfig, internalProjectReferences, context.workspaceRoot,
291
316
  /* emitDeclarationOnly */ true),
292
- syncGenerators: ['@nx/js:typescript-sync'],
293
- metadata: {
294
- technologies: ['typescript'],
295
- description: 'Runs type-checking for the project.',
296
- help: {
297
- command: `${pmc.exec} ${compiler} --build --help`,
298
- example: {
299
- args: ['--force'],
300
- },
301
- },
302
- },
303
- };
317
+ });
318
+ if (tsConfig.options.noEmit ||
319
+ Object.values(internalProjectReferences).some((ref) => ref.options.noEmit) ||
320
+ Object.values(externalProjectReferences).some((ref) => ref.options.noEmit)) {
321
+ // `tsc --build` does not work with `noEmit: true`
322
+ target.command = `echo "The 'typecheck' target is disabled because one or more project references set 'noEmit: true' in their tsconfig. Remove this property to resolve this issue."`;
323
+ }
324
+ targets[targetName] = target;
304
325
  }
305
326
  }
306
327
  // Build target
@@ -313,7 +334,7 @@ function buildTscTargets(config, options, context, configFiles, cache, pmc) {
313
334
  const compiler = options.compiler;
314
335
  targets[targetName] = {
315
336
  dependsOn: [`^${targetName}`],
316
- command: `${compiler} --build ${options.build.configName}${options.verboseOutput ? ' --verbose' : ''}`,
337
+ command: `${compiler} --build${(0, typecheck_target_1.getTsConfigArg)(options.build.configName)}${options.verboseOutput ? ' --verbose' : ''}`,
317
338
  options: { cwd: config.project.normalized },
318
339
  cache: true,
319
340
  inputs: getInputs(namedInputs, config, tsConfig, internalProjectReferences, context.workspaceRoot, cache),
@@ -339,6 +360,48 @@ function buildTscTargets(config, options, context, configFiles, cache, pmc) {
339
360
  }
340
361
  return { targets };
341
362
  }
363
+ /**
364
+ * Files that decide the `noEmit` typecheck target: the sibling tsconfigs and
365
+ * bundler configs by name, and `project.json` by content.
366
+ */
367
+ function getNoEmitTypecheckHashInputs(project, workspaceRoot, cache) {
368
+ const siblingFiles = readSiblingFiles(project, cache);
369
+ return [
370
+ ...siblingFiles
371
+ .filter((file) => /^(tsconfig.*\.json|(webpack|rspack|rollup)\.config\..+)$/.test(file))
372
+ .sort(),
373
+ ...(siblingFiles.includes('project.json')
374
+ ? [
375
+ getFileHash((0, node_path_1.join)(project.absolute, 'project.json'), workspaceRoot, cache),
376
+ ]
377
+ : []),
378
+ ];
379
+ }
380
+ function readSiblingFiles(project, cache) {
381
+ let files = cache.siblingFiles.get(project.absolute);
382
+ if (!files) {
383
+ files = (0, node_fs_1.readdirSync)(project.absolute);
384
+ cache.siblingFiles.set(project.absolute, files);
385
+ }
386
+ return files;
387
+ }
388
+ function readProjectTargets(projectRoot, siblingFiles) {
389
+ return {
390
+ ...readTargets(projectRoot, siblingFiles, 'package.json', (json) => json.nx?.targets),
391
+ ...readTargets(projectRoot, siblingFiles, 'project.json', (json) => json.targets),
392
+ };
393
+ }
394
+ function readTargets(projectRoot, siblingFiles, file, getTargets) {
395
+ if (!siblingFiles.includes(file)) {
396
+ return undefined;
397
+ }
398
+ try {
399
+ return getTargets((0, devkit_1.readJsonFile)((0, node_path_1.join)(projectRoot, file)));
400
+ }
401
+ catch {
402
+ return undefined;
403
+ }
404
+ }
342
405
  function getInputs(namedInputs, config, tsConfig, internalProjectReferences, workspaceRoot, cache) {
343
406
  const configFiles = new Set();
344
407
  // TODO(leo): temporary disable external dependencies until we support hashing
@@ -983,15 +1046,21 @@ function getFileHash(filePath, workspaceRoot, cache) {
983
1046
  const relativePath = posixRelative(workspaceRoot, filePath);
984
1047
  if (!cache.fileHashes[relativePath]) {
985
1048
  const content = readFile(filePath, workspaceRoot, cache);
986
- cache.fileHashes[relativePath] = (0, devkit_1.hashArray)([content]);
1049
+ cache.fileHashes[relativePath] = (0, devkit_1.hashArray)([content ?? '<missing>']);
987
1050
  }
988
1051
  return cache.fileHashes[relativePath];
989
1052
  }
1053
+ /**
1054
+ * Returns `undefined` for a missing file, as `ts.sys.readFile` does, so a
1055
+ * tsconfig extending a deleted file is reported by TypeScript instead of
1056
+ * throwing.
1057
+ */
990
1058
  function readFile(filePath, workspaceRoot, cache) {
991
1059
  const relativePath = posixRelative(workspaceRoot, filePath);
992
- if (!cache.rawFiles[relativePath]) {
993
- const content = (0, node_fs_1.readFileSync)(filePath, 'utf8');
994
- cache.rawFiles[relativePath] = content;
1060
+ if (!(relativePath in cache.rawFiles)) {
1061
+ cache.rawFiles[relativePath] = (0, node_fs_1.existsSync)(filePath)
1062
+ ? (0, node_fs_1.readFileSync)(filePath, 'utf8')
1063
+ : undefined;
995
1064
  }
996
1065
  return cache.rawFiles[relativePath];
997
1066
  }
@@ -0,0 +1,54 @@
1
+ import type { getPackageManagerCommand, TargetConfiguration } from '@nx/devkit';
2
+ export interface TypecheckTargetOptions {
3
+ /**
4
+ * `build` runs `tsc --build --emitDeclarationOnly` for TS solution setups.
5
+ * `noEmit` runs `tsc -p <tsconfig> --noEmit`.
6
+ */
7
+ mode: 'build' | 'noEmit';
8
+ /**
9
+ * Project root, used as the command's cwd.
10
+ */
11
+ projectRoot: string;
12
+ pmc: ReturnType<typeof getPackageManagerCommand>;
13
+ /**
14
+ * Defaults to `tsconfig.json`, which is left out of the command.
15
+ */
16
+ tsConfig?: string;
17
+ /**
18
+ * Defaults to `tsc`.
19
+ */
20
+ compiler?: string;
21
+ /**
22
+ * Name of the typecheck target, for the dependency on referenced projects
23
+ * in `build` mode. Defaults to `typecheck`.
24
+ */
25
+ targetName?: string;
26
+ /**
27
+ * The project's build target, which `build` mode runs first.
28
+ */
29
+ buildTargetName?: string;
30
+ /**
31
+ * Defaults to the `production` (or `default`) named input plus the
32
+ * compiler package.
33
+ */
34
+ inputs?: TargetConfiguration['inputs'];
35
+ namedInputs?: Record<string, unknown>;
36
+ outputs?: string[];
37
+ technologies?: string[];
38
+ verboseOutput?: boolean;
39
+ }
40
+ /**
41
+ * Builds a `typecheck` target. Plugins that infer a `tsc` one share this
42
+ * shape so it stays compatible with the one `@nx/js/typescript` infers.
43
+ */
44
+ export declare function createTypecheckTarget(options: TypecheckTargetOptions): TargetConfiguration;
45
+ /**
46
+ * The tsconfig a `noEmit` typecheck target should check, from the files next
47
+ * to the project's config: the app, then the lib, then the root tsconfig.
48
+ */
49
+ export declare function selectTypecheckTsConfig(tsConfigFiles: string[]): string | undefined;
50
+ /**
51
+ * ` <tsconfig>` for a tsc command, or an empty string for `tsconfig.json`,
52
+ * which tsc reads by default.
53
+ */
54
+ export declare function getTsConfigArg(tsConfig: string | undefined): string;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTypecheckTarget = createTypecheckTarget;
4
+ exports.selectTypecheckTsConfig = selectTypecheckTsConfig;
5
+ exports.getTsConfigArg = getTsConfigArg;
6
+ const DEFAULT_TSCONFIG = 'tsconfig.json';
7
+ /**
8
+ * Builds a `typecheck` target. Plugins that infer a `tsc` one share this
9
+ * shape so it stays compatible with the one `@nx/js/typescript` infers.
10
+ */
11
+ function createTypecheckTarget(options) {
12
+ const compiler = options.compiler ?? 'tsc';
13
+ const tsConfigArg = getTsConfigArg(options.tsConfig);
14
+ const target = {
15
+ command: options.mode === 'build'
16
+ ? `${compiler} --build${tsConfigArg} --emitDeclarationOnly${options.verboseOutput ? ' --verbose' : ''}`
17
+ : `${compiler}${tsConfigArg ? ` -p${tsConfigArg}` : ''} --noEmit`,
18
+ options: { cwd: options.projectRoot },
19
+ cache: true,
20
+ inputs: options.inputs ??
21
+ getDefaultTypecheckInputs(options.namedInputs ?? {}, compiler),
22
+ metadata: {
23
+ technologies: options.technologies ?? ['typescript'],
24
+ description: 'Runs type-checking for the project.',
25
+ help: options.mode === 'build'
26
+ ? {
27
+ command: `${options.pmc.exec} ${compiler} --build --help`,
28
+ example: { args: ['--force'] },
29
+ }
30
+ : {
31
+ command: `${options.pmc.exec} ${compiler}${tsConfigArg ? ` -p${tsConfigArg}` : ''} --help`,
32
+ example: { options: { noEmit: true } },
33
+ },
34
+ },
35
+ };
36
+ if (options.outputs) {
37
+ target.outputs = options.outputs;
38
+ }
39
+ if (options.mode === 'build') {
40
+ target.dependsOn = [`^${options.targetName ?? 'typecheck'}`];
41
+ if (options.buildTargetName) {
42
+ target.dependsOn.unshift(options.buildTargetName);
43
+ }
44
+ target.syncGenerators = ['@nx/js:typescript-sync'];
45
+ }
46
+ return target;
47
+ }
48
+ /**
49
+ * The tsconfig a `noEmit` typecheck target should check, from the files next
50
+ * to the project's config: the app, then the lib, then the root tsconfig.
51
+ */
52
+ function selectTypecheckTsConfig(tsConfigFiles) {
53
+ return (['tsconfig.app.json', 'tsconfig.lib.json', DEFAULT_TSCONFIG].find((file) => tsConfigFiles.includes(file)) ?? tsConfigFiles.find((file) => SHELL_SAFE_TSCONFIG.test(file)));
54
+ }
55
+ // The name goes unquoted into a shell command.
56
+ const SHELL_SAFE_TSCONFIG = /^tsconfig[\w.-]*\.json$/;
57
+ /**
58
+ * ` <tsconfig>` for a tsc command, or an empty string for `tsconfig.json`,
59
+ * which tsc reads by default.
60
+ */
61
+ function getTsConfigArg(tsConfig) {
62
+ return !tsConfig || tsConfig === DEFAULT_TSCONFIG ? '' : ` ${tsConfig}`;
63
+ }
64
+ function getDefaultTypecheckInputs(namedInputs, compiler) {
65
+ return [
66
+ ...('production' in namedInputs
67
+ ? ['production', '^production']
68
+ : ['default', '^default']),
69
+ { externalDependencies: getCompilerPackages(compiler) },
70
+ ];
71
+ }
72
+ function getCompilerPackages(compiler) {
73
+ switch (compiler) {
74
+ case 'tsgo':
75
+ return ['@typescript/native-preview'];
76
+ case 'vue-tsc':
77
+ return ['vue-tsc', 'typescript'];
78
+ default:
79
+ return ['typescript'];
80
+ }
81
+ }
@@ -6,7 +6,6 @@ export interface AddLintingToProjectOptions {
6
6
  project: string;
7
7
  skipPackageJson?: boolean;
8
8
  keepExistingVersions?: boolean;
9
- addPlugin?: boolean;
10
9
  /** ESLint-only. Ignored by other linters. */
11
10
  tsConfigPaths?: string[];
12
11
  unitTestRunner?: string;
@@ -15,7 +14,6 @@ export interface AddLintingToProjectOptions {
15
14
  /** ESLint-only. Ignored by other linters. */
16
15
  enableTypedLinting?: boolean;
17
16
  /** ESLint-only. Oxlint is inference-only, so it writes no explicit target. */
18
- addExplicitTargets?: boolean;
19
17
  /**
20
18
  * ESLint-only. The `@nx/dependency-checks` rule lints `package.json`, which
21
19
  * Oxlint cannot read.
@@ -60,7 +60,6 @@ async function addLintingToProject(tree, options) {
60
60
  skipFormat: true,
61
61
  skipPackageJson: options.skipPackageJson,
62
62
  keepExistingVersions: options.keepExistingVersions,
63
- // No `addPlugin`: `@nx/oxlint` is inference-only, so it always registers.
64
63
  });
65
64
  }
66
65
  if (linter === 'eslint') {
@@ -72,12 +71,10 @@ async function addLintingToProject(tree, options) {
72
71
  unitTestRunner: options.unitTestRunner,
73
72
  rootProject: options.rootProject,
74
73
  enableTypedLinting: options.enableTypedLinting,
75
- addExplicitTargets: options.addExplicitTargets,
76
74
  addPackageJsonDependencyChecks: options.addPackageJsonDependencyChecks,
77
75
  skipFormat: true,
78
76
  skipPackageJson: options.skipPackageJson,
79
77
  keepExistingVersions: options.keepExistingVersions,
80
- addPlugin: options.addPlugin,
81
78
  });
82
79
  }
83
80
  // Spelling ESLint out as its own branch rather than letting it be the
@@ -0,0 +1,22 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export declare function addTypescriptPlugin(tree: Tree, updatePackageScripts?: boolean): Promise<void>;
3
+ /**
4
+ * Registers `@nx/js/typescript` in a workspace where no registration of it
5
+ * infers `typecheck` yet, adding a typecheck-only entry next to an existing
6
+ * unscoped one. Outside TS solution setups it only infers `typecheck`. Callers decide
7
+ * whether inference plugins are in use. Pass `frameworkPlugin` when that
8
+ * plugin still infers `typecheck` for some projects, so its target wins.
9
+ */
10
+ export declare function ensureTypescriptPlugin(tree: Tree, updatePackageScripts?: boolean, frameworkPlugin?: string): Promise<void>;
11
+ /**
12
+ * Registers `@nx/js/typescript`, with only its typecheck target, in a
13
+ * workspace where `frameworkPlugin` no longer infers `typecheck`. Reuses the
14
+ * framework plugin's `typecheckTargetName` and a `tsgo` compiler, and skips
15
+ * workspaces where `@nx/js/typescript` already infers it or where
16
+ * `typecheckTargetName` is `false`. With
17
+ * `removeTypecheckTargetName`, the option is also dropped from the framework
18
+ * plugin's registrations. Returns whether nx.json changed.
19
+ */
20
+ export declare function registerTypescriptPluginForTypecheck(tree: Tree, frameworkPlugin: string, { removeTypecheckTargetName }?: {
21
+ removeTypecheckTargetName?: boolean;
22
+ }): boolean;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addTypescriptPlugin = addTypescriptPlugin;
4
+ exports.ensureTypescriptPlugin = ensureTypescriptPlugin;
5
+ exports.registerTypescriptPluginForTypecheck = registerTypescriptPluginForTypecheck;
6
+ const internal_1 = require("@nx/devkit/internal");
7
+ const devkit_1 = require("@nx/devkit");
8
+ const plugin_1 = require("../../plugins/typescript/plugin");
9
+ const ts_solution_setup_1 = require("./ts-solution-setup");
10
+ async function addTypescriptPlugin(tree, updatePackageScripts) {
11
+ await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/js/typescript', plugin_1.createNodesV2, {
12
+ typecheck: TYPECHECK_OPTIONS,
13
+ build: [
14
+ {
15
+ targetName: 'build',
16
+ configName: 'tsconfig.lib.json',
17
+ buildDepsName: 'build-deps',
18
+ watchDepsName: 'watch-deps',
19
+ },
20
+ {
21
+ targetName: 'tsc:build',
22
+ configName: 'tsconfig.lib.json',
23
+ buildDepsName: 'tsc:build-deps',
24
+ watchDepsName: 'tsc:watch-deps',
25
+ },
26
+ {
27
+ targetName: 'tsc-build',
28
+ configName: 'tsconfig.lib.json',
29
+ buildDepsName: 'tsc-build-deps',
30
+ watchDepsName: 'tsc-watch-deps',
31
+ },
32
+ ],
33
+ }, updatePackageScripts);
34
+ }
35
+ const TYPECHECK_OPTIONS = [
36
+ { targetName: 'typecheck' },
37
+ { targetName: 'tsc:typecheck' },
38
+ { targetName: 'tsc-typecheck' },
39
+ ];
40
+ /**
41
+ * Registers `@nx/js/typescript` in a workspace where no registration of it
42
+ * infers `typecheck` yet, adding a typecheck-only entry next to an existing
43
+ * unscoped one. Outside TS solution setups it only infers `typecheck`. Callers decide
44
+ * whether inference plugins are in use. Pass `frameworkPlugin` when that
45
+ * plugin still infers `typecheck` for some projects, so its target wins.
46
+ */
47
+ async function ensureTypescriptPlugin(tree, updatePackageScripts, frameworkPlugin) {
48
+ const nxJson = (0, devkit_1.readNxJson)(tree);
49
+ if (nxJson?.plugins?.some(infersTypecheck)) {
50
+ return;
51
+ }
52
+ if (nxJson?.plugins?.some((p) => typeof p !== 'string' && p.plugin === '@nx/js/typescript' && !p.include)) {
53
+ // `addPlugin` skips workspaces that have an unscoped registration.
54
+ nxJson.plugins.push({
55
+ plugin: '@nx/js/typescript',
56
+ options: { typecheck: { targetName: 'typecheck' } },
57
+ });
58
+ (0, devkit_1.updateNxJson)(tree, nxJson);
59
+ }
60
+ else if ((0, ts_solution_setup_1.isUsingTsSolutionSetup)(tree)) {
61
+ await addTypescriptPlugin(tree, updatePackageScripts);
62
+ }
63
+ else {
64
+ await (0, internal_1.addPlugin)(tree, await (0, devkit_1.createProjectGraphAsync)(), '@nx/js/typescript', plugin_1.createNodesV2, { typecheck: TYPECHECK_OPTIONS }, updatePackageScripts);
65
+ }
66
+ if (frameworkPlugin) {
67
+ const updated = (0, devkit_1.readNxJson)(tree);
68
+ if (moveTypescriptPluginBefore(updated.plugins, frameworkPlugin)) {
69
+ (0, devkit_1.updateNxJson)(tree, updated);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Registers `@nx/js/typescript`, with only its typecheck target, in a
75
+ * workspace where `frameworkPlugin` no longer infers `typecheck`. Reuses the
76
+ * framework plugin's `typecheckTargetName` and a `tsgo` compiler, and skips
77
+ * workspaces where `@nx/js/typescript` already infers it or where
78
+ * `typecheckTargetName` is `false`. With
79
+ * `removeTypecheckTargetName`, the option is also dropped from the framework
80
+ * plugin's registrations. Returns whether nx.json changed.
81
+ */
82
+ function registerTypescriptPluginForTypecheck(tree, frameworkPlugin, { removeTypecheckTargetName = false } = {}) {
83
+ const nxJson = (0, devkit_1.readNxJson)(tree);
84
+ if (!nxJson?.plugins?.length) {
85
+ return false;
86
+ }
87
+ const frameworkRegistrations = nxJson.plugins.filter((p) => getPluginName(p) === frameworkPlugin);
88
+ const typecheckOptions = frameworkRegistrations
89
+ .map((p) => (typeof p === 'string' ? {} : (p.options ?? {})))
90
+ .find((options) => options.typecheckTargetName !== false);
91
+ const targetName = typecheckOptions
92
+ ? (typecheckOptions.typecheckTargetName ?? 'typecheck')
93
+ : undefined;
94
+ let changed = false;
95
+ if (removeTypecheckTargetName) {
96
+ for (const p of frameworkRegistrations) {
97
+ const options = typeof p === 'string'
98
+ ? undefined
99
+ : p.options;
100
+ if (options && 'typecheckTargetName' in options) {
101
+ delete options['typecheckTargetName'];
102
+ changed = true;
103
+ }
104
+ }
105
+ }
106
+ if (targetName && !nxJson.plugins.some(infersTypecheck)) {
107
+ nxJson.plugins.push({
108
+ plugin: '@nx/js/typescript',
109
+ options: {
110
+ typecheck: { targetName },
111
+ ...(typecheckOptions.compiler === 'tsgo' ? { compiler: 'tsgo' } : {}),
112
+ },
113
+ });
114
+ moveTypescriptPluginBefore(nxJson.plugins, frameworkPlugin);
115
+ changed = true;
116
+ }
117
+ if (changed) {
118
+ (0, devkit_1.updateNxJson)(tree, nxJson);
119
+ }
120
+ return changed;
121
+ }
122
+ /**
123
+ * Plugins registered later override same-named targets, so `@nx/js/typescript`
124
+ * goes before a framework plugin that still infers `typecheck`.
125
+ */
126
+ function moveTypescriptPluginBefore(plugins, frameworkPlugin) {
127
+ const tsIndex = plugins.findIndex(infersTypecheck);
128
+ const frameworkIndex = plugins.findIndex((p) => getPluginName(p) === frameworkPlugin);
129
+ if (tsIndex === -1 || frameworkIndex === -1 || tsIndex < frameworkIndex) {
130
+ return false;
131
+ }
132
+ const [tsPlugin] = plugins.splice(tsIndex, 1);
133
+ plugins.splice(frameworkIndex, 0, tsPlugin);
134
+ return true;
135
+ }
136
+ // Scoped build-only registrations, like `@nx/node`'s tsc apps, set `typecheck: false`.
137
+ function infersTypecheck(plugin) {
138
+ return (getPluginName(plugin) === '@nx/js/typescript' &&
139
+ (typeof plugin === 'string' ||
140
+ plugin.options?.typecheck !==
141
+ false));
142
+ }
143
+ function getPluginName(plugin) {
144
+ return typeof plugin === 'string' ? plugin : plugin.plugin;
145
+ }
@@ -2,7 +2,7 @@ import { type ProjectConfiguration, type Tree } from '@nx/devkit';
2
2
  import type { JsonInput } from '@nx/devkit/internal';
3
3
  export declare const TS_SOLUTION_SETUP_TSCONFIG_INPUT: JsonInput;
4
4
  export declare function isUsingTypeScriptPlugin(tree: Tree): boolean;
5
- export declare function shouldConfigureTsSolutionSetup(tree: Tree, addPlugins: boolean, addTsPlugin?: boolean): boolean;
5
+ export declare function shouldConfigureTsSolutionSetup(tree: Tree, addTsPlugin?: boolean): boolean;
6
6
  export declare function isUsingTsSolutionSetup(tree?: Tree): boolean;
7
7
  export declare function assertNotUsingTsSolutionSetup(tree: Tree, pluginName: string, generatorName: string): void;
8
8
  export declare function findRuntimeTsConfigName(projectRoot: string, tree?: Tree): string | null;
@@ -28,19 +28,10 @@ function isUsingTypeScriptPlugin(tree) {
28
28
  ? p === '@nx/js/typescript'
29
29
  : p.plugin === '@nx/js/typescript') ?? false);
30
30
  }
31
- function shouldConfigureTsSolutionSetup(tree, addPlugins, addTsPlugin) {
31
+ function shouldConfigureTsSolutionSetup(tree, addTsPlugin) {
32
32
  if (addTsPlugin !== undefined) {
33
33
  return addTsPlugin;
34
34
  }
35
- if (addPlugins === undefined) {
36
- const nxJson = (0, devkit_1.readNxJson)(tree);
37
- addPlugins =
38
- process.env.NX_ADD_PLUGINS !== 'false' &&
39
- nxJson.useInferencePlugins !== false;
40
- }
41
- if (!addPlugins) {
42
- return false;
43
- }
44
35
  if (!(0, package_manager_workspaces_1.isUsingPackageManagerWorkspaces)(tree)) {
45
36
  return false;
46
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "24.0.0-beta.2",
3
+ "version": "24.0.0-beta.3",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "files": [
@@ -154,8 +154,8 @@
154
154
  "@babel/preset-env": "^7.23.2",
155
155
  "@babel/preset-typescript": "^7.22.5",
156
156
  "@babel/runtime": "^7.22.6",
157
- "@nx/devkit": "24.0.0-beta.2",
158
- "@nx/workspace": "24.0.0-beta.2",
157
+ "@nx/devkit": "24.0.0-beta.3",
158
+ "@nx/workspace": "24.0.0-beta.3",
159
159
  "@zkochan/js-yaml": "0.0.7",
160
160
  "babel-plugin-const-enum": "^1.0.1",
161
161
  "babel-plugin-macros": "^3.1.0",
@@ -175,14 +175,14 @@
175
175
  "tslib": "^2.3.0"
176
176
  },
177
177
  "devDependencies": {
178
- "@nx/esbuild": "24.0.0-beta.2",
179
- "@nx/eslint": "24.0.0-beta.2",
180
- "@nx/jest": "24.0.0-beta.2",
181
- "@nx/oxlint": "24.0.0-beta.2",
182
- "@nx/rollup": "24.0.0-beta.2",
183
- "@nx/vite": "24.0.0-beta.2",
184
- "@nx/vitest": "24.0.0-beta.2",
185
- "nx": "24.0.0-beta.2"
178
+ "@nx/esbuild": "24.0.0-beta.3",
179
+ "@nx/eslint": "24.0.0-beta.3",
180
+ "@nx/jest": "24.0.0-beta.3",
181
+ "@nx/oxlint": "24.0.0-beta.3",
182
+ "@nx/rollup": "24.0.0-beta.3",
183
+ "@nx/vite": "24.0.0-beta.3",
184
+ "@nx/vitest": "24.0.0-beta.3",
185
+ "nx": "24.0.0-beta.3"
186
186
  },
187
187
  "peerDependencies": {
188
188
  "@swc/cli": ">=0.6.0 <0.9.0",