@nx/js 19.6.0-beta.1 → 19.6.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.
package/generators.json CHANGED
@@ -42,10 +42,11 @@
42
42
  "alias": ["build"],
43
43
  "description": "setup-build generator"
44
44
  },
45
- "sync": {
46
- "factory": "./src/generators/sync/sync#syncGenerator",
47
- "schema": "./src/generators/sync/schema.json",
45
+ "typescript-sync": {
46
+ "factory": "./src/generators/typescript-sync/typescript-sync",
47
+ "schema": "./src/generators/typescript-sync/schema.json",
48
48
  "description": "Synchronize TypeScript project references based on the project graph",
49
+ "alias": ["sync"],
49
50
  "hidden": true
50
51
  }
51
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "19.6.0-beta.1",
3
+ "version": "19.6.0-beta.3",
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": {
@@ -58,9 +58,9 @@
58
58
  "semver": "^7.5.3",
59
59
  "source-map-support": "0.5.19",
60
60
  "tslib": "^2.3.0",
61
- "@nx/devkit": "19.6.0-beta.1",
62
- "@nx/workspace": "19.6.0-beta.1",
63
- "@nrwl/js": "19.6.0-beta.1"
61
+ "@nx/devkit": "19.6.0-beta.3",
62
+ "@nx/workspace": "19.6.0-beta.3",
63
+ "@nrwl/js": "19.6.0-beta.3"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "verdaccio": "^5.0.4"
@@ -290,7 +290,8 @@ To fix this you will either need to add a package.json file at that location, or
290
290
  }
291
291
  }
292
292
  if (options.releaseGroup.projectsRelationship === 'independent') {
293
- specifier = options.releaseGroup.versionPlans.reduce((spec, plan) => {
293
+ specifier = options.releaseGroup
294
+ .resolvedVersionPlans.reduce((spec, plan) => {
294
295
  if (!spec) {
295
296
  return plan.projectVersionBumps[projectName];
296
297
  }
@@ -305,7 +306,7 @@ To fix this you will either need to add a package.json file at that location, or
305
306
  }, null);
306
307
  }
307
308
  else {
308
- specifier = options.releaseGroup.versionPlans.reduce((spec, plan) => {
309
+ specifier = options.releaseGroup.resolvedVersionPlans.reduce((spec, plan) => {
309
310
  if (!spec) {
310
311
  return plan.groupVersionBump;
311
312
  }
@@ -332,7 +333,7 @@ To fix this you will either need to add a package.json file at that location, or
332
333
  logger.buffer(`📄 Resolved the specifier as "${specifier}" using version plans.`);
333
334
  }
334
335
  if (options.deleteVersionPlans) {
335
- options.releaseGroup.versionPlans.forEach((p) => {
336
+ (options.releaseGroup.resolvedVersionPlans || []).forEach((p) => {
336
337
  deleteVersionPlanCallbacks.push(async (dryRun) => {
337
338
  if (!dryRun) {
338
339
  await (0, fs_extra_1.remove)(p.absolutePath);
@@ -386,7 +387,7 @@ To fix this you will either need to add a package.json file at that location, or
386
387
  let isInCurrentBatch = options.projects.some((project) => project.name === dependentProject.source);
387
388
  // For version-plans, we don't just need to consider the current batch of projects, but also the ones that are actually being updated as part of the plan file(s)
388
389
  if (isInCurrentBatch && options.specifierSource === 'version-plans') {
389
- isInCurrentBatch = (options.releaseGroup.versionPlans || []).some((plan) => {
390
+ isInCurrentBatch = (options.releaseGroup.resolvedVersionPlans || []).some((plan) => {
390
391
  if ('projectVersionBumps' in plan) {
391
392
  return plan.projectVersionBumps[dependentProject.source];
392
393
  }
@@ -0,0 +1,4 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ import type { SyncGeneratorResult } from 'nx/src/utils/sync-generators';
3
+ export declare function syncGenerator(tree: Tree): Promise<SyncGeneratorResult>;
4
+ export default syncGenerator;
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.syncGenerator = syncGenerator;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const posix_1 = require("node:path/posix");
6
+ const plugin_1 = require("../../plugins/typescript/plugin");
7
+ const COMMON_RUNTIME_TS_CONFIG_FILE_NAMES = [
8
+ 'tsconfig.app.json',
9
+ 'tsconfig.lib.json',
10
+ 'tsconfig.build.json',
11
+ 'tsconfig.cjs.json',
12
+ 'tsconfig.esm.json',
13
+ 'tsconfig.runtime.json',
14
+ ];
15
+ async function syncGenerator(tree) {
16
+ // Ensure that the plugin has been wired up in nx.json
17
+ const nxJson = (0, devkit_1.readNxJson)(tree);
18
+ const tscPluginConfig = nxJson.plugins.find((p) => {
19
+ if (typeof p === 'string') {
20
+ return p === plugin_1.PLUGIN_NAME;
21
+ }
22
+ return p.plugin === plugin_1.PLUGIN_NAME;
23
+ });
24
+ if (!tscPluginConfig) {
25
+ throw new Error(`The ${plugin_1.PLUGIN_NAME} plugin must be added to the "plugins" array in nx.json before syncing tsconfigs`);
26
+ }
27
+ // Root tsconfig containing project references for the whole workspace
28
+ const rootTsconfigPath = 'tsconfig.json';
29
+ if (!tree.exists(rootTsconfigPath)) {
30
+ throw new Error(`A "tsconfig.json" file must exist in the workspace root.`);
31
+ }
32
+ const rootTsconfig = (0, devkit_1.readJson)(tree, rootTsconfigPath);
33
+ const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
34
+ const projectRoots = new Set();
35
+ const tsconfigProjectNodeValues = Object.values(projectGraph.nodes).filter((node) => {
36
+ projectRoots.add(node.data.root);
37
+ const projectTsconfigPath = (0, devkit_1.joinPathFragments)(node.data.root, 'tsconfig.json');
38
+ return tree.exists(projectTsconfigPath);
39
+ });
40
+ // Track if any changes were made to the tsconfig files. We check the changes
41
+ // made by this generator to know if the TS config is out of sync with the
42
+ // project graph. Therefore, we don't format the files if there were no changes
43
+ // to avoid potential format-only changes that can lead to false positives.
44
+ let hasChanges = false;
45
+ if (tsconfigProjectNodeValues.length > 0) {
46
+ const referencesSet = new Set();
47
+ for (const ref of rootTsconfig.references ?? []) {
48
+ // reference path is relative to the tsconfig file
49
+ const resolvedRefPath = getTsConfigPathFromReferencePath(tree, rootTsconfigPath, ref.path);
50
+ if (tree.exists(resolvedRefPath)) {
51
+ // we only keep the references that still exist
52
+ referencesSet.add(normalizeReferencePath(ref.path));
53
+ }
54
+ else {
55
+ hasChanges = true;
56
+ }
57
+ }
58
+ for (const node of tsconfigProjectNodeValues) {
59
+ const normalizedPath = normalizeReferencePath(node.data.root);
60
+ // Skip the root tsconfig itself
61
+ if (node.data.root !== '.' && !referencesSet.has(normalizedPath)) {
62
+ referencesSet.add(normalizedPath);
63
+ hasChanges = true;
64
+ }
65
+ }
66
+ if (hasChanges) {
67
+ rootTsconfig.references = Array.from(referencesSet).map((ref) => ({
68
+ path: `./${ref}`,
69
+ }));
70
+ (0, devkit_1.writeJson)(tree, rootTsconfigPath, rootTsconfig);
71
+ }
72
+ }
73
+ const runtimeTsConfigFileNames = nxJson.sync?.generatorOptions?.['@nx/js:typescript-sync']
74
+ ?.runtimeTsConfigFileNames ??
75
+ COMMON_RUNTIME_TS_CONFIG_FILE_NAMES;
76
+ const collectedDependencies = new Map();
77
+ for (const [name, data] of Object.entries(projectGraph.dependencies)) {
78
+ if (!projectGraph.nodes[name] ||
79
+ projectGraph.nodes[name].data.root === '.' ||
80
+ !data.length) {
81
+ continue;
82
+ }
83
+ // Get the source project nodes for the source and target
84
+ const sourceProjectNode = projectGraph.nodes[name];
85
+ // Find the relevant tsconfig file for the source project
86
+ const sourceProjectTsconfigPath = (0, devkit_1.joinPathFragments)(sourceProjectNode.data.root, 'tsconfig.json');
87
+ if (!tree.exists(sourceProjectTsconfigPath)) {
88
+ if (process.env.NX_VERBOSE_LOGGING === 'true') {
89
+ devkit_1.logger.warn(`Skipping project "${name}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}".`);
90
+ }
91
+ continue;
92
+ }
93
+ // Collect the dependencies of the source project
94
+ const dependencies = collectProjectDependencies(tree, name, projectGraph, collectedDependencies);
95
+ if (!dependencies.length) {
96
+ continue;
97
+ }
98
+ for (const runtimeTsConfigFileName of runtimeTsConfigFileNames) {
99
+ const runtimeTsConfigPath = (0, devkit_1.joinPathFragments)(sourceProjectNode.data.root, runtimeTsConfigFileName);
100
+ if (!tree.exists(runtimeTsConfigPath)) {
101
+ continue;
102
+ }
103
+ // Update project references for the runtime tsconfig
104
+ hasChanges =
105
+ updateTsConfigReferences(tree, runtimeTsConfigPath, dependencies, sourceProjectNode.data.root, projectRoots, runtimeTsConfigFileName, runtimeTsConfigFileNames) || hasChanges;
106
+ }
107
+ // Update project references for the tsconfig.json file
108
+ hasChanges =
109
+ updateTsConfigReferences(tree, sourceProjectTsconfigPath, dependencies, sourceProjectNode.data.root, projectRoots) || hasChanges;
110
+ }
111
+ if (hasChanges) {
112
+ await (0, devkit_1.formatFiles)(tree);
113
+ return {
114
+ outOfSyncMessage: 'Based on the workspace project graph, some TypeScript configuration files are missing project references to the projects they depend on.',
115
+ };
116
+ }
117
+ }
118
+ exports.default = syncGenerator;
119
+ function updateTsConfigReferences(tree, tsConfigPath, dependencies, projectRoot, projectRoots, runtimeTsConfigFileName, possibleRuntimeTsConfigFileNames) {
120
+ const tsConfig = (0, devkit_1.readJson)(tree, tsConfigPath);
121
+ // We have at least one dependency so we can safely set it to an empty array if not already set
122
+ const references = [];
123
+ const originalReferencesSet = new Set();
124
+ const newReferencesSet = new Set();
125
+ for (const ref of tsConfig.references ?? []) {
126
+ const normalizedPath = normalizeReferencePath(ref.path);
127
+ originalReferencesSet.add(normalizedPath);
128
+ // reference path is relative to the tsconfig file
129
+ const resolvedRefPath = getTsConfigPathFromReferencePath(tree, tsConfigPath, ref.path);
130
+ if (isInternalProjectReference(tree, resolvedRefPath, projectRoot, projectRoots)) {
131
+ // we keep all internal references
132
+ references.push(ref);
133
+ newReferencesSet.add(normalizedPath);
134
+ }
135
+ }
136
+ let hasChanges = false;
137
+ for (const dep of dependencies) {
138
+ // Ensure the project reference for the target is set
139
+ let referencePath = dep.data.root;
140
+ if (runtimeTsConfigFileName) {
141
+ const runtimeTsConfigPath = (0, devkit_1.joinPathFragments)(dep.data.root, runtimeTsConfigFileName);
142
+ if (tree.exists(runtimeTsConfigPath)) {
143
+ referencePath = runtimeTsConfigPath;
144
+ }
145
+ else {
146
+ // Check for other possible runtime tsconfig file names
147
+ // TODO(leo): should we check if there are more than one runtime tsconfig files and throw an error?
148
+ for (const possibleRuntimeTsConfigFileName of possibleRuntimeTsConfigFileNames ??
149
+ []) {
150
+ const possibleRuntimeTsConfigPath = (0, devkit_1.joinPathFragments)(dep.data.root, possibleRuntimeTsConfigFileName);
151
+ if (tree.exists(possibleRuntimeTsConfigPath)) {
152
+ referencePath = possibleRuntimeTsConfigPath;
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ }
158
+ const relativePathToTargetRoot = (0, posix_1.relative)(projectRoot, referencePath);
159
+ if (!newReferencesSet.has(relativePathToTargetRoot)) {
160
+ newReferencesSet.add(relativePathToTargetRoot);
161
+ // Make sure we unshift rather than push so that dependencies are built in the right order by TypeScript when it is run directly from the root of the workspace
162
+ references.unshift({ path: relativePathToTargetRoot });
163
+ }
164
+ if (!originalReferencesSet.has(relativePathToTargetRoot)) {
165
+ hasChanges = true;
166
+ }
167
+ }
168
+ hasChanges ||= newReferencesSet.size !== originalReferencesSet.size;
169
+ if (hasChanges) {
170
+ tsConfig.references = references;
171
+ (0, devkit_1.writeJson)(tree, tsConfigPath, tsConfig);
172
+ }
173
+ return hasChanges;
174
+ }
175
+ // TODO(leo): follow up with the TypeScript team to confirm if we really need
176
+ // to reference transitive dependencies.
177
+ // Collect the dependencies of a project recursively sorted from root to leaf
178
+ function collectProjectDependencies(tree, projectName, projectGraph, collectedDependencies) {
179
+ if (collectedDependencies.has(projectName)) {
180
+ // We've already collected the dependencies for this project
181
+ return collectedDependencies.get(projectName);
182
+ }
183
+ collectedDependencies.set(projectName, []);
184
+ for (const dep of projectGraph.dependencies[projectName]) {
185
+ const targetProjectNode = projectGraph.nodes[dep.target];
186
+ if (!targetProjectNode) {
187
+ // It's an npm dependency
188
+ continue;
189
+ }
190
+ // Add the target project node to the list of dependencies for the current project
191
+ if (!collectedDependencies
192
+ .get(projectName)
193
+ .some((d) => d.name === targetProjectNode.name)) {
194
+ collectedDependencies.get(projectName).push(targetProjectNode);
195
+ }
196
+ if (process.env.NX_DISABLE_TS_SYNC_TRANSITIVE_DEPENDENCIES === 'true') {
197
+ continue;
198
+ }
199
+ // Recursively get the dependencies of the target project
200
+ const transitiveDependencies = collectProjectDependencies(tree, dep.target, projectGraph, collectedDependencies);
201
+ for (const transitiveDep of transitiveDependencies) {
202
+ if (!collectedDependencies
203
+ .get(projectName)
204
+ .some((d) => d.name === transitiveDep.name)) {
205
+ collectedDependencies.get(projectName).push(transitiveDep);
206
+ }
207
+ }
208
+ }
209
+ return collectedDependencies.get(projectName);
210
+ }
211
+ // Normalize the paths to strip leading `./` and trailing `/tsconfig.json`
212
+ function normalizeReferencePath(path) {
213
+ return (0, posix_1.normalize)(path)
214
+ .replace(/\/tsconfig.json$/, '')
215
+ .replace(/^\.\//, '');
216
+ }
217
+ function isInternalProjectReference(tree, refTsConfigPath, projectRoot, projectRoots) {
218
+ let currentPath = getTsConfigDirName(tree, refTsConfigPath);
219
+ if ((0, posix_1.relative)(projectRoot, currentPath).startsWith('..')) {
220
+ // it's outside of the project root, so it's an external project reference
221
+ return false;
222
+ }
223
+ while (currentPath !== projectRoot) {
224
+ if (projectRoots.has(currentPath)) {
225
+ // it's inside a nested project root, so it's and external project reference
226
+ return false;
227
+ }
228
+ currentPath = (0, posix_1.dirname)(currentPath);
229
+ }
230
+ // it's inside the project root, so it's an internal project reference
231
+ return true;
232
+ }
233
+ function getTsConfigDirName(tree, tsConfigPath) {
234
+ return tree.isFile(tsConfigPath)
235
+ ? (0, posix_1.dirname)(tsConfigPath)
236
+ : (0, posix_1.normalize)(tsConfigPath);
237
+ }
238
+ function getTsConfigPathFromReferencePath(tree, ownerTsConfigPath, referencePath) {
239
+ const resolvedRefPath = (0, devkit_1.joinPathFragments)((0, posix_1.dirname)(ownerTsConfigPath), referencePath);
240
+ return tree.isFile(resolvedRefPath)
241
+ ? resolvedRefPath
242
+ : (0, devkit_1.joinPathFragments)(resolvedRefPath, 'tsconfig.json');
243
+ }
@@ -109,6 +109,7 @@ function buildTscTargets(configFilePath, projectRoot, options, context) {
109
109
  cache: true,
110
110
  inputs: getInputs(namedInputs, configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
111
111
  outputs: getOutputs(configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
112
+ syncGenerators: ['@nx/js:typescript-sync'],
112
113
  };
113
114
  }
114
115
  }
@@ -123,6 +124,7 @@ function buildTscTargets(configFilePath, projectRoot, options, context) {
123
124
  cache: true,
124
125
  inputs: getInputs(namedInputs, configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
125
126
  outputs: getOutputs(configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
127
+ syncGenerators: ['@nx/js:typescript-sync'],
126
128
  };
127
129
  }
128
130
  return { targets };
@@ -1,3 +0,0 @@
1
- import { Tree } from '@nx/devkit';
2
- import { SyncSchema } from './schema';
3
- export declare function syncGenerator(tree: Tree, options: SyncSchema): Promise<void>;
@@ -1,75 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.syncGenerator = syncGenerator;
4
- const devkit_1 = require("@nx/devkit");
5
- const node_path_1 = require("node:path");
6
- const plugin_1 = require("../../plugins/typescript/plugin");
7
- async function syncGenerator(tree, options) {
8
- // Ensure that the plugin has been wired up in nx.json
9
- const nxJson = (0, devkit_1.readNxJson)(tree);
10
- let tscPluginConfig = nxJson.plugins.find((p) => {
11
- if (typeof p === 'string') {
12
- return p === plugin_1.PLUGIN_NAME;
13
- }
14
- return p.plugin === plugin_1.PLUGIN_NAME;
15
- });
16
- if (!tscPluginConfig) {
17
- throw new Error(`The ${plugin_1.PLUGIN_NAME} plugin must be added to the "plugins" array in nx.json before syncing tsconfigs`);
18
- }
19
- const projectGraph = await (0, devkit_1.createProjectGraphAsync)();
20
- const firstPartyDeps = Object.entries(projectGraph.dependencies).filter(([name, data]) => !name.startsWith('npm:') && data.length > 0);
21
- // Root tsconfig containing project references for the whole workspace
22
- const rootTsconfigPath = 'tsconfig.json';
23
- const rootTsconfig = (0, devkit_1.readJson)(tree, rootTsconfigPath);
24
- const tsconfigProjectNodeValues = Object.values(projectGraph.nodes).filter((node) => {
25
- const projectTsconfigPath = (0, devkit_1.joinPathFragments)(node.data.root, 'tsconfig.json');
26
- return tree.exists(projectTsconfigPath);
27
- });
28
- if (tsconfigProjectNodeValues.length > 0) {
29
- // Sync the root tsconfig references from the project graph (do not destroy existing references)
30
- rootTsconfig.references = rootTsconfig.references || [];
31
- const referencesSet = new Set(rootTsconfig.references.map((ref) => normalizeReferencePath(ref.path)));
32
- for (const node of tsconfigProjectNodeValues) {
33
- const normalizedPath = normalizeReferencePath(node.data.root);
34
- // Skip the root tsconfig itself
35
- if (node.data.root !== '.' && !referencesSet.has(normalizedPath)) {
36
- rootTsconfig.references.push({ path: `./${normalizedPath}` });
37
- }
38
- }
39
- (0, devkit_1.writeJson)(tree, rootTsconfigPath, rootTsconfig);
40
- }
41
- for (const [name, data] of firstPartyDeps) {
42
- // Get the source project nodes for the source and target
43
- const sourceProjectNode = projectGraph.nodes[name];
44
- // Find the relevant tsconfig files for the source project
45
- const sourceProjectTsconfigPath = (0, devkit_1.joinPathFragments)(sourceProjectNode.data.root, 'tsconfig.json');
46
- if (!tree.exists(sourceProjectTsconfigPath)) {
47
- console.warn(`Skipping project "${name}" as there is no tsconfig.json file found in the project root "${sourceProjectNode.data.root}"`);
48
- continue;
49
- }
50
- const sourceTsconfig = (0, devkit_1.readJson)(tree, sourceProjectTsconfigPath);
51
- for (const dep of data) {
52
- // Get the target project node
53
- const targetProjectNode = projectGraph.nodes[dep.target];
54
- if (!targetProjectNode) {
55
- // It's an external dependency
56
- continue;
57
- }
58
- // Set defaults only in the case where we have at least one dependency so that we don't patch files when not necessary
59
- sourceTsconfig.references = sourceTsconfig.references || [];
60
- // Ensure the project reference for the target is set
61
- const relativePathToTargetRoot = (0, node_path_1.relative)(sourceProjectNode.data.root, targetProjectNode.data.root);
62
- if (!sourceTsconfig.references.some((ref) => ref.path === relativePathToTargetRoot)) {
63
- // Make sure we unshift rather than push so that dependencies are built in the right order by TypeScript when it is run directly from the root of the workspace
64
- sourceTsconfig.references.unshift({ path: relativePathToTargetRoot });
65
- }
66
- }
67
- // Update the source tsconfig files
68
- (0, devkit_1.writeJson)(tree, sourceProjectTsconfigPath, sourceTsconfig);
69
- }
70
- await (0, devkit_1.formatFiles)(tree);
71
- }
72
- // Normalize the paths to strip leading `./` and trailing `/tsconfig.json`
73
- function normalizeReferencePath(path) {
74
- return path.replace(/\/tsconfig.json$/, '').replace(/^\.\//, '');
75
- }