@nx/js 19.6.1 → 19.6.2

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.
package/README.md CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  # Nx: Smart Monorepos · Fast CI
24
24
 
25
- Nx is a build system with built-in tooling and advanced CI capabilities. It helps you maintain and scale monorepos, both locally and on CI.
25
+ Nx is a build system, optimized for monorepos, with plugins for popular frameworks and tools and advanced CI capabilities including caching and distribution.
26
26
 
27
27
  This package is a [JavaScript/TypeScript plugin for Nx](https://nx.dev/js/overview).
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "19.6.1",
3
+ "version": "19.6.2",
4
4
  "private": false,
5
5
  "description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
6
6
  "repository": {
@@ -34,11 +34,13 @@
34
34
  "dependencies": {
35
35
  "@babel/core": "^7.23.2",
36
36
  "@babel/plugin-proposal-decorators": "^7.22.7",
37
- "@babel/plugin-transform-runtime": "^7.23.2",
38
37
  "@babel/plugin-transform-class-properties": "^7.22.5",
38
+ "@babel/plugin-transform-runtime": "^7.23.2",
39
39
  "@babel/preset-env": "^7.23.2",
40
40
  "@babel/preset-typescript": "^7.22.5",
41
41
  "@babel/runtime": "^7.22.6",
42
+ "@nx/devkit": "19.6.2",
43
+ "@nx/workspace": "19.6.2",
42
44
  "babel-plugin-const-enum": "^1.0.1",
43
45
  "babel-plugin-macros": "^2.8.0",
44
46
  "babel-plugin-transform-typescript-metadata": "^0.3.1",
@@ -47,20 +49,19 @@
47
49
  "detect-port": "^1.5.1",
48
50
  "fast-glob": "3.2.7",
49
51
  "fs-extra": "^11.1.0",
50
- "npm-package-arg": "11.0.1",
51
- "npm-run-path": "^4.0.1",
52
- "ts-node": "10.9.1",
53
- "tsconfig-paths": "^4.1.2",
54
52
  "ignore": "^5.0.4",
55
53
  "js-tokens": "^4.0.0",
54
+ "jsonc-parser": "3.2.0",
56
55
  "minimatch": "9.0.3",
56
+ "npm-package-arg": "11.0.1",
57
+ "npm-run-path": "^4.0.1",
57
58
  "ora": "5.3.0",
58
59
  "semver": "^7.5.3",
59
60
  "source-map-support": "0.5.19",
61
+ "ts-node": "10.9.1",
62
+ "tsconfig-paths": "^4.1.2",
60
63
  "tslib": "^2.3.0",
61
- "@nx/devkit": "19.6.1",
62
- "@nx/workspace": "19.6.1",
63
- "@nrwl/js": "19.6.1"
64
+ "@nrwl/js": "19.6.2"
64
65
  },
65
66
  "peerDependencies": {
66
67
  "verdaccio": "^5.0.4"
@@ -23,6 +23,7 @@ function normalizeOptions(options, contextRoot, sourceRoot, projectRoot) {
23
23
  options.external = firstItem;
24
24
  }
25
25
  }
26
+ options.assets ??= [];
26
27
  const files = (0, assets_1.assetGlobsToFiles)(options.assets, contextRoot, outputPath);
27
28
  return {
28
29
  ...options,
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.syncGenerator = syncGenerator;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const jsonc_parser_1 = require("jsonc-parser");
5
6
  const posix_1 = require("node:path/posix");
7
+ const ts = require("typescript");
6
8
  const plugin_1 = require("../../plugins/typescript/plugin");
7
9
  const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
8
10
  'tsconfig.app.json',
@@ -27,16 +29,25 @@ async function syncGenerator(tree) {
27
29
  // Root tsconfig containing project references for the whole workspace
28
30
  const rootTsconfigPath = 'tsconfig.json';
29
31
  if (!tree.exists(rootTsconfigPath)) {
30
- throw new Error(`A "tsconfig.json" file must exist in the workspace root.`);
32
+ throw new Error(`A "tsconfig.json" file must exist in the workspace root in order to use this sync generator.`);
31
33
  }
32
- const rootTsconfig = (0, devkit_1.readJson)(tree, rootTsconfigPath);
34
+ const rawTsconfigContentsCache = new Map();
35
+ const stringifiedRootJsonContents = readRawTsconfigContents(tree, rawTsconfigContentsCache, rootTsconfigPath);
36
+ const rootTsconfig = (0, devkit_1.parseJson)(stringifiedRootJsonContents);
33
37
  const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
34
38
  const projectRoots = new Set();
39
+ const tsconfigHasCompositeEnabledCache = new Map();
35
40
  const tsconfigProjectNodeValues = Object.values(projectGraph.nodes).filter((node) => {
36
41
  projectRoots.add(node.data.root);
37
42
  const projectTsconfigPath = (0, devkit_1.joinPathFragments)(node.data.root, 'tsconfig.json');
38
- return tree.exists(projectTsconfigPath);
43
+ return tsconfigExists(tree, rawTsconfigContentsCache, projectTsconfigPath);
39
44
  });
45
+ const tsSysFromTree = {
46
+ ...ts.sys,
47
+ readFile(path) {
48
+ return readRawTsconfigContents(tree, rawTsconfigContentsCache, path);
49
+ },
50
+ };
40
51
  // Track if any changes were made to the tsconfig files. We check the changes
41
52
  // made by this generator to know if the TS config is out of sync with the
42
53
  // project graph. Therefore, we don't format the files if there were no changes
@@ -47,7 +58,7 @@ async function syncGenerator(tree) {
47
58
  for (const ref of rootTsconfig.references ?? []) {
48
59
  // reference path is relative to the tsconfig file
49
60
  const resolvedRefPath = getTsConfigPathFromReferencePath(tree, rootTsconfigPath, ref.path);
50
- if (tree.exists(resolvedRefPath)) {
61
+ if (tsconfigExists(tree, rawTsconfigContentsCache, resolvedRefPath)) {
51
62
  // we only keep the references that still exist
52
63
  referencesSet.add(normalizeReferencePath(ref.path));
53
64
  }
@@ -64,10 +75,13 @@ async function syncGenerator(tree) {
64
75
  }
65
76
  }
66
77
  if (hasChanges) {
67
- rootTsconfig.references = Array.from(referencesSet).map((ref) => ({
78
+ const updatedReferences = Array.from(referencesSet)
79
+ // Check composite is true in the internal reference before proceeding
80
+ .filter((ref) => hasCompositeEnabled(tsSysFromTree, tsconfigHasCompositeEnabledCache, (0, devkit_1.joinPathFragments)(ref, 'tsconfig.json')))
81
+ .map((ref) => ({
68
82
  path: `./${ref}`,
69
83
  }));
70
- (0, devkit_1.writeJson)(tree, rootTsconfigPath, rootTsconfig);
84
+ patchTsconfigJsonReferences(tree, rawTsconfigContentsCache, rootTsconfigPath, updatedReferences);
71
85
  }
72
86
  }
73
87
  const runtimeTsConfigFileNames = nxJson.sync?.generatorOptions?.['@nx/js:typescript-sync']
@@ -84,7 +98,7 @@ async function syncGenerator(tree) {
84
98
  const sourceProjectNode = projectGraph.nodes[name];
85
99
  // Find the relevant tsconfig file for the source project
86
100
  const sourceProjectTsconfigPath = (0, devkit_1.joinPathFragments)(sourceProjectNode.data.root, 'tsconfig.json');
87
- if (!tree.exists(sourceProjectTsconfigPath)) {
101
+ if (!tsconfigExists(tree, rawTsconfigContentsCache, sourceProjectTsconfigPath)) {
88
102
  if (process.env.NX_VERBOSE_LOGGING === 'true') {
89
103
  devkit_1.logger.warn(`Skipping project "${name}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`);
90
104
  }
@@ -97,16 +111,16 @@ async function syncGenerator(tree) {
97
111
  }
98
112
  for (const runtimeTsConfigFileName of runtimeTsConfigFileNames) {
99
113
  const runtimeTsConfigPath = (0, devkit_1.joinPathFragments)(sourceProjectNode.data.root, runtimeTsConfigFileName);
100
- if (!tree.exists(runtimeTsConfigPath)) {
114
+ if (!tsconfigExists(tree, rawTsconfigContentsCache, runtimeTsConfigPath)) {
101
115
  continue;
102
116
  }
103
117
  // Update project references for the runtime tsconfig
104
118
  hasChanges =
105
- updateTsConfigReferences(tree, runtimeTsConfigPath, dependencies, sourceProjectNode.data.root, projectRoots, runtimeTsConfigFileName, runtimeTsConfigFileNames) || hasChanges;
119
+ updateTsConfigReferences(tree, tsSysFromTree, rawTsconfigContentsCache, tsconfigHasCompositeEnabledCache, runtimeTsConfigPath, dependencies, sourceProjectNode.data.root, projectRoots, runtimeTsConfigFileName, runtimeTsConfigFileNames) || hasChanges;
106
120
  }
107
121
  // Update project references for the tsconfig.json file
108
122
  hasChanges =
109
- updateTsConfigReferences(tree, sourceProjectTsconfigPath, dependencies, sourceProjectNode.data.root, projectRoots) || hasChanges;
123
+ updateTsConfigReferences(tree, tsSysFromTree, rawTsconfigContentsCache, tsconfigHasCompositeEnabledCache, sourceProjectTsconfigPath, dependencies, sourceProjectNode.data.root, projectRoots) || hasChanges;
110
124
  }
111
125
  if (hasChanges) {
112
126
  await (0, devkit_1.formatFiles)(tree);
@@ -116,8 +130,28 @@ async function syncGenerator(tree) {
116
130
  }
117
131
  }
118
132
  exports.default = syncGenerator;
119
- function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot, projectRoots, runtimeTsConfigFileName, possibleRuntimeTsConfigFileNames) {
120
- const tsConfig = (0, devkit_1.readJson)(tree, tsConfigPath);
133
+ /**
134
+ * Within the context of a sync generator, performance is a key concern,
135
+ * so avoid FS interactions whenever possible.
136
+ */
137
+ function readRawTsconfigContents(tree, rawTsconfigContentsCache, tsconfigPath) {
138
+ if (!rawTsconfigContentsCache.has(tsconfigPath)) {
139
+ rawTsconfigContentsCache.set(tsconfigPath, tree.read(tsconfigPath, 'utf-8'));
140
+ }
141
+ return rawTsconfigContentsCache.get(tsconfigPath);
142
+ }
143
+ /**
144
+ * Within the context of a sync generator, performance is a key concern,
145
+ * so avoid FS interactions whenever possible.
146
+ */
147
+ function tsconfigExists(tree, rawTsconfigContentsCache, tsconfigPath) {
148
+ return rawTsconfigContentsCache.has(tsconfigPath)
149
+ ? true
150
+ : tree.exists(tsconfigPath);
151
+ }
152
+ function updateTsConfigReferences(tree, tsSysFromTree, rawTsconfigContentsCache, tsconfigHasCompositeEnabledCache, tsConfigPath, dependencies, projectRoot, projectRoots, runtimeTsConfigFileName, possibleRuntimeTsConfigFileNames) {
153
+ const stringifiedJsonContents = readRawTsconfigContents(tree, rawTsconfigContentsCache, tsConfigPath);
154
+ const tsConfig = (0, devkit_1.parseJson)(stringifiedJsonContents);
121
155
  // We have at least one dependency so we can safely set it to an empty array if not already set
122
156
  const references = [];
123
157
  const originalReferencesSet = new Set();
@@ -127,8 +161,8 @@ function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot,
127
161
  originalReferencesSet.add(normalizedPath);
128
162
  // reference path is relative to the tsconfig file
129
163
  const resolvedRefPath = getTsConfigPathFromReferencePath(tree, tsConfigPath, ref.path);
130
- if (isInternalProjectReference(tree, resolvedRefPath, projectRoot, projectRoots)) {
131
- // we keep all internal references
164
+ if (isProjectReferenceWithinNxProject(tree, rawTsconfigContentsCache, resolvedRefPath, projectRoot, projectRoots)) {
165
+ // we keep all references within the current Nx project
132
166
  references.push(ref);
133
167
  newReferencesSet.add(normalizedPath);
134
168
  }
@@ -139,7 +173,11 @@ function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot,
139
173
  let referencePath = dep.data.root;
140
174
  if (runtimeTsConfigFileName) {
141
175
  const runtimeTsConfigPath = (0, devkit_1.joinPathFragments)(dep.data.root, runtimeTsConfigFileName);
142
- if (tree.exists(runtimeTsConfigPath)) {
176
+ if (tsconfigExists(tree, rawTsconfigContentsCache, runtimeTsConfigPath)) {
177
+ // Check composite is true in the dependency runtime tsconfig file before proceeding
178
+ if (!hasCompositeEnabled(tsSysFromTree, tsconfigHasCompositeEnabledCache, runtimeTsConfigPath)) {
179
+ continue;
180
+ }
143
181
  referencePath = runtimeTsConfigPath;
144
182
  }
145
183
  else {
@@ -148,13 +186,23 @@ function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot,
148
186
  for (const possibleRuntimeTsConfigFileName of possibleRuntimeTsConfigFileNames ??
149
187
  []) {
150
188
  const possibleRuntimeTsConfigPath = (0, devkit_1.joinPathFragments)(dep.data.root, possibleRuntimeTsConfigFileName);
151
- if (tree.exists(possibleRuntimeTsConfigPath)) {
189
+ if (tsconfigExists(tree, rawTsconfigContentsCache, possibleRuntimeTsConfigPath)) {
190
+ // Check composite is true in the dependency runtime tsconfig file before proceeding
191
+ if (!hasCompositeEnabled(tsSysFromTree, tsconfigHasCompositeEnabledCache, possibleRuntimeTsConfigPath)) {
192
+ continue;
193
+ }
152
194
  referencePath = possibleRuntimeTsConfigPath;
153
195
  break;
154
196
  }
155
197
  }
156
198
  }
157
199
  }
200
+ else {
201
+ // Check composite is true in the dependency tsconfig.json file before proceeding
202
+ if (!hasCompositeEnabled(tsSysFromTree, tsconfigHasCompositeEnabledCache, (0, devkit_1.joinPathFragments)(dep.data.root, 'tsconfig.json'))) {
203
+ continue;
204
+ }
205
+ }
158
206
  const relativePathToTargetRoot = (0, posix_1.relative)(projectRoot, referencePath);
159
207
  if (!newReferencesSet.has(relativePathToTargetRoot)) {
160
208
  newReferencesSet.add(relativePathToTargetRoot);
@@ -167,8 +215,7 @@ function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot,
167
215
  }
168
216
  hasChanges ||= newReferencesSet.size !== originalReferencesSet.size;
169
217
  if (hasChanges) {
170
- tsConfig.references = references;
171
- (0, devkit_1.writeJson)(tree, tsConfigPath, tsConfig);
218
+ patchTsconfigJsonReferences(tree, rawTsconfigContentsCache, tsConfigPath, references);
172
219
  }
173
220
  return hasChanges;
174
221
  }
@@ -214,8 +261,8 @@ function normalizeReferencePath(path) {
214
261
  .replace(/\/tsconfig.json$/, '')
215
262
  .replace(/^\.\//, '');
216
263
  }
217
- function isInternalProjectReference(tree, refTsConfigPath, projectRoot, projectRoots) {
218
- let currentPath = getTsConfigDirName(tree, refTsConfigPath);
264
+ function isProjectReferenceWithinNxProject(tree, rawTsconfigContentsCache, refTsConfigPath, projectRoot, projectRoots) {
265
+ let currentPath = getTsConfigDirName(tree, rawTsconfigContentsCache, refTsConfigPath);
219
266
  if ((0, posix_1.relative)(projectRoot, currentPath).startsWith('..')) {
220
267
  // it's outside of the project root, so it's an external project reference
221
268
  return false;
@@ -230,8 +277,10 @@ function isInternalProjectReference(tree, refTsConfigPath, projectRoot, projectR
230
277
  // it's inside the project root, so it's an internal project reference
231
278
  return true;
232
279
  }
233
- function getTsConfigDirName(tree, tsConfigPath) {
234
- return tree.isFile(tsConfigPath)
280
+ function getTsConfigDirName(tree, rawTsconfigContentsCache, tsConfigPath) {
281
+ return (rawTsconfigContentsCache.has(tsConfigPath)
282
+ ? true
283
+ : tree.isFile(tsConfigPath))
235
284
  ? (0, posix_1.dirname)(tsConfigPath)
236
285
  : (0, posix_1.normalize)(tsConfigPath);
237
286
  }
@@ -241,3 +290,22 @@ function getTsConfigPathFromReferencePath(tree, ownerTsConfigPath, referencePath
241
290
  ? resolvedRefPath
242
291
  : (0, devkit_1.joinPathFragments)(resolvedRefPath, 'tsconfig.json');
243
292
  }
293
+ /**
294
+ * Minimally patch just the "references" property within the tsconfig file at a given path.
295
+ * This allows comments in other sections of the file to remain intact when syncing is run.
296
+ */
297
+ function patchTsconfigJsonReferences(tree, rawTsconfigContentsCache, tsconfigPath, updatedReferences) {
298
+ const stringifiedJsonContents = readRawTsconfigContents(tree, rawTsconfigContentsCache, tsconfigPath);
299
+ const edits = (0, jsonc_parser_1.modify)(stringifiedJsonContents, ['references'], updatedReferences, {});
300
+ const updatedJsonContents = (0, jsonc_parser_1.applyEdits)(stringifiedJsonContents, edits);
301
+ // The final contents will be formatted by formatFiles() later
302
+ tree.write(tsconfigPath, updatedJsonContents);
303
+ }
304
+ function hasCompositeEnabled(tsSysFromTree, tsconfigHasCompositeEnabledCache, tsconfigPath) {
305
+ if (!tsconfigHasCompositeEnabledCache.has(tsconfigPath)) {
306
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(tsconfigPath, tsSysFromTree.readFile).config, tsSysFromTree, (0, posix_1.dirname)(tsconfigPath));
307
+ const enabledVal = parsed.options.composite === true;
308
+ tsconfigHasCompositeEnabledCache.set(tsconfigPath, enabledVal);
309
+ }
310
+ return tsconfigHasCompositeEnabledCache.get(tsconfigPath);
311
+ }
@@ -2,7 +2,7 @@ import type { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project
2
2
  import type { AssetGlob, FileInputOutput } from './assets/assets';
3
3
  import { TransformerEntry } from './typescript/types';
4
4
  // nx-ignore-next-line
5
- const { Linter } = require('@nx/eslint'); // use require to import to avoid circular dependency
5
+ const { Linter, LinterType } = require('@nx/eslint'); // use require to import to avoid circular dependency
6
6
 
7
7
  export type Compiler = 'tsc' | 'swc';
8
8
  export type Bundler = 'swc' | 'tsc' | 'rollup' | 'vite' | 'esbuild' | 'none';
@@ -17,7 +17,7 @@ export interface LibraryGeneratorSchema {
17
17
  skipPackageJson?: boolean;
18
18
  includeBabelRc?: boolean;
19
19
  unitTestRunner?: 'jest' | 'vitest' | 'none';
20
- linter?: Linter;
20
+ linter?: Linter | LinterType;
21
21
  testEnvironment?: 'jsdom' | 'node';
22
22
  importPath?: string;
23
23
  js?: boolean;
@@ -13,6 +13,7 @@ interface BaseTypeCheckOptions {
13
13
  cacheDir?: string;
14
14
  incremental?: boolean;
15
15
  rootDir?: string;
16
+ projectRoot?: string;
16
17
  }
17
18
  type Mode = NoEmitMode | EmitDeclarationOnlyMode;
18
19
  interface NoEmitMode {
@@ -55,21 +55,28 @@ async function runTypeCheck(options) {
55
55
  }
56
56
  async function setupTypeScript(options) {
57
57
  const ts = await Promise.resolve().then(() => require('typescript'));
58
- const { workspaceRoot, tsConfigPath, cacheDir, incremental, rootDir } = options;
58
+ const { workspaceRoot, tsConfigPath, cacheDir, incremental, projectRoot } = options;
59
59
  const config = (0, ts_config_1.readTsConfig)(tsConfigPath);
60
60
  if (config.errors.length) {
61
61
  const errorMessages = config.errors.map((e) => e.messageText).join('\n');
62
62
  throw new Error(`Invalid config file due to following: ${errorMessages}`);
63
63
  }
64
64
  const emitOptions = options.mode === 'emitDeclarationOnly'
65
- ? { emitDeclarationOnly: true, declaration: true, outDir: options.outDir }
65
+ ? {
66
+ emitDeclarationOnly: true,
67
+ declaration: true,
68
+ outDir: options.outDir,
69
+ declarationDir: options.projectRoot && options.outDir.indexOf(projectRoot)
70
+ ? options.outDir.replace(projectRoot, '')
71
+ : undefined,
72
+ }
66
73
  : { noEmit: true };
67
74
  const compilerOptions = {
68
75
  ...config.options,
69
76
  skipLibCheck: true,
70
77
  ...emitOptions,
71
78
  incremental,
72
- rootDir: rootDir || config.options.rootDir,
79
+ rootDir: options.rootDir || config.options.rootDir,
73
80
  };
74
81
  return { ts, workspaceRoot, cacheDir, config, compilerOptions };
75
82
  }