@nx/js 19.6.0-beta.0 → 19.6.0-beta.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/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.0",
3
+ "version": "19.6.0-beta.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": {
@@ -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.0",
62
- "@nx/workspace": "19.6.0-beta.0",
63
- "@nrwl/js": "19.6.0-beta.0"
61
+ "@nx/devkit": "19.6.0-beta.2",
62
+ "@nx/workspace": "19.6.0-beta.2",
63
+ "@nrwl/js": "19.6.0-beta.2"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "verdaccio": "^5.0.4"
@@ -19,6 +19,7 @@ const resolve_local_package_dependencies_1 = require("./utils/resolve-local-pack
19
19
  const sort_projects_topologically_1 = require("./utils/sort-projects-topologically");
20
20
  const update_lock_file_1 = require("./utils/update-lock-file");
21
21
  async function releaseVersionGenerator(tree, options) {
22
+ let logger;
22
23
  try {
23
24
  const versionData = {};
24
25
  // If the user provided a specifier, validate that it is valid semver or a relative semver keyword
@@ -67,6 +68,8 @@ Valid values are: ${version_1.validReleaseVersionPrefixes
67
68
  ? options.specifier
68
69
  : undefined;
69
70
  const deleteVersionPlanCallbacks = [];
71
+ // If the user has set the logUnchangedProjects option to false, we will not print any logs for projects that have no changes.
72
+ const logUnchangedProjects = options.logUnchangedProjects ?? true;
70
73
  for (const project of projects) {
71
74
  const projectName = project.name;
72
75
  const packageRoot = projectNameToPackageRootMap.get(projectName);
@@ -74,18 +77,15 @@ Valid values are: ${version_1.validReleaseVersionPrefixes
74
77
  throw new Error(`The project "${projectName}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`);
75
78
  }
76
79
  const packageJsonPath = (0, node_path_1.join)(packageRoot, 'package.json');
77
- const color = getColor(projectName);
78
- const log = (msg) => {
79
- console.log(color.instance.bold(projectName) + ' ' + msg);
80
- };
81
80
  if (!tree.exists(packageJsonPath)) {
82
81
  throw new Error(`The project "${projectName}" does not have a package.json available at ${packageJsonPath}.
83
82
 
84
83
  To fix this you will either need to add a package.json file at that location, or configure "release" within your nx.json to exclude "${projectName}" from the current release group, or amend the packageRoot configuration to point to where the package.json should be.`);
85
84
  }
86
- devkit_1.output.logSingleLine(`Running release version for project: ${color.instance.bold(project.name)}`);
85
+ const color = getColor(projectName);
86
+ logger = new ProjectLogger(projectName, color);
87
87
  const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
88
- log(`🔍 Reading data for package "${packageJson.name}" from ${packageJsonPath}`);
88
+ logger.buffer(`🔍 Reading data for package "${packageJson.name}" from ${packageJsonPath}`);
89
89
  const { name: packageName, version: currentVersionFromDisk } = packageJson;
90
90
  switch (options.currentVersionResolver) {
91
91
  case 'registry': {
@@ -128,12 +128,12 @@ To fix this you will either need to add a package.json file at that location, or
128
128
  });
129
129
  });
130
130
  spinner.stop();
131
- log(`📄 Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${registry}`);
131
+ logger.buffer(`📄 Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${registry}`);
132
132
  }
133
133
  catch (e) {
134
134
  spinner.stop();
135
135
  if (options.fallbackCurrentVersionResolver === 'disk') {
136
- log(`📄 Unable to resolve the current version from the registry ${registry}. Falling back to the version on disk of ${currentVersionFromDisk}`);
136
+ logger.buffer(`📄 Unable to resolve the current version from the registry ${registry}. Falling back to the version on disk of ${currentVersionFromDisk}`);
137
137
  currentVersion = currentVersionFromDisk;
138
138
  currentVersionResolvedFromFallback = true;
139
139
  }
@@ -144,10 +144,10 @@ To fix this you will either need to add a package.json file at that location, or
144
144
  }
145
145
  else {
146
146
  if (currentVersionResolvedFromFallback) {
147
- log(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
147
+ logger.buffer(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
148
148
  }
149
149
  else {
150
- log(`📄 Using the current version ${currentVersion} already resolved from the registry ${registry}`);
150
+ logger.buffer(`📄 Using the current version ${currentVersion} already resolved from the registry ${registry}`);
151
151
  }
152
152
  }
153
153
  break;
@@ -157,7 +157,7 @@ To fix this you will either need to add a package.json file at that location, or
157
157
  if (!currentVersion) {
158
158
  throw new Error(`Unable to determine the current version for project "${project.name}" from ${packageJsonPath}`);
159
159
  }
160
- log(`📄 Resolved the current version as ${currentVersion} from ${packageJsonPath}`);
160
+ logger.buffer(`📄 Resolved the current version as ${currentVersion} from ${packageJsonPath}`);
161
161
  break;
162
162
  case 'git-tag': {
163
163
  if (!currentVersion ||
@@ -169,7 +169,7 @@ To fix this you will either need to add a package.json file at that location, or
169
169
  });
170
170
  if (!latestMatchingGitTag) {
171
171
  if (options.fallbackCurrentVersionResolver === 'disk') {
172
- log(`📄 Unable to resolve the current version from git tag using pattern "${releaseTagPattern}". Falling back to the version on disk of ${currentVersionFromDisk}`);
172
+ logger.buffer(`📄 Unable to resolve the current version from git tag using pattern "${releaseTagPattern}". Falling back to the version on disk of ${currentVersionFromDisk}`);
173
173
  currentVersion = currentVersionFromDisk;
174
174
  currentVersionResolvedFromFallback = true;
175
175
  }
@@ -179,15 +179,15 @@ To fix this you will either need to add a package.json file at that location, or
179
179
  }
180
180
  else {
181
181
  currentVersion = latestMatchingGitTag.extractedVersion;
182
- log(`📄 Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}".`);
182
+ logger.buffer(`📄 Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}".`);
183
183
  }
184
184
  }
185
185
  else {
186
186
  if (currentVersionResolvedFromFallback) {
187
- log(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
187
+ logger.buffer(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
188
188
  }
189
189
  else {
190
- log(
190
+ logger.buffer(
191
191
  // In this code path we know that latestMatchingGitTag is defined, because we are not relying on the fallbackCurrentVersionResolver, so we can safely use the non-null assertion operator
192
192
  `📄 Using the current version ${currentVersion} already resolved from git tag "${latestMatchingGitTag.tag}".`);
193
193
  }
@@ -198,7 +198,7 @@ To fix this you will either need to add a package.json file at that location, or
198
198
  throw new Error(`Invalid value for options.currentVersionResolver: ${options.currentVersionResolver}`);
199
199
  }
200
200
  if (options.specifier) {
201
- log(`📄 Using the provided version specifier "${options.specifier}".`);
201
+ logger.buffer(`📄 Using the provided version specifier "${options.specifier}".`);
202
202
  // The user is forcibly overriding whatever specifierSource they had otherwise set by imperatively providing a specifier
203
203
  options.specifierSource = 'prompt';
204
204
  }
@@ -240,10 +240,10 @@ To fix this you will either need to add a package.json file at that location, or
240
240
  if (projectToDependencyBumps.has(projectName)) {
241
241
  // No applicable changes to the project directly by the user, but one or more dependencies have been bumped and updateDependents is enabled
242
242
  specifier = updateDependentsBump;
243
- log(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
243
+ logger.buffer(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
244
244
  break;
245
245
  }
246
- log(`🚫 No changes were detected using git history and the conventional commits standard.`);
246
+ logger.buffer(`🚫 No changes were detected using git history and the conventional commits standard.`);
247
247
  break;
248
248
  }
249
249
  // TODO: reevaluate this prerelease logic/workflow for independent projects
@@ -252,7 +252,7 @@ To fix this you will either need to add a package.json file at that location, or
252
252
  // Users must manually graduate from a prerelease to a release by providing an explicit specifier.
253
253
  if ((0, semver_2.prerelease)(currentVersion ?? '')) {
254
254
  specifier = 'prerelease';
255
- log(`📄 Resolved the specifier as "${specifier}" since the current version is a prerelease.`);
255
+ logger.buffer(`📄 Resolved the specifier as "${specifier}" since the current version is a prerelease.`);
256
256
  }
257
257
  else {
258
258
  let extraText = '';
@@ -260,7 +260,7 @@ To fix this you will either need to add a package.json file at that location, or
260
260
  specifier = `pre${specifier}`;
261
261
  extraText = `, combined with your given preid "${options.preid}"`;
262
262
  }
263
- log(`📄 Resolved the specifier as "${specifier}" using git history and the conventional commits standard${extraText}.`);
263
+ logger.buffer(`📄 Resolved the specifier as "${specifier}" using git history and the conventional commits standard${extraText}.`);
264
264
  }
265
265
  break;
266
266
  }
@@ -321,15 +321,15 @@ To fix this you will either need to add a package.json file at that location, or
321
321
  projectToDependencyBumps.has(projectName)) {
322
322
  // No applicable changes to the project directly by the user, but one or more dependencies have been bumped and updateDependents is enabled
323
323
  specifier = updateDependentsBump;
324
- log(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
324
+ logger.buffer(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
325
325
  }
326
326
  else {
327
327
  specifier = null;
328
- log(`🚫 No changes were detected within version plans.`);
328
+ logger.buffer(`🚫 No changes were detected within version plans.`);
329
329
  }
330
330
  }
331
331
  else {
332
- log(`📄 Resolved the specifier as "${specifier}" using version plans.`);
332
+ logger.buffer(`📄 Resolved the specifier as "${specifier}" using version plans.`);
333
333
  }
334
334
  if (options.deleteVersionPlans) {
335
335
  options.releaseGroup.versionPlans.forEach((p) => {
@@ -420,7 +420,7 @@ To fix this you will either need to add a package.json file at that location, or
420
420
  .map((dependentProject) => `${indent}- ${dependentProject.source}`)
421
421
  .join('\n')}`;
422
422
  logMsg += `\n${indent}=> You can adjust this behavior by setting \`version.generatorOptions.updateDependents\` to "auto"`;
423
- log(logMsg);
423
+ logger.buffer(logMsg);
424
424
  }
425
425
  }
426
426
  if (!currentVersion) {
@@ -434,7 +434,11 @@ To fix this you will either need to add a package.json file at that location, or
434
434
  : dependentProjectsInCurrentBatch,
435
435
  };
436
436
  if (!specifier) {
437
- log(`🚫 Skipping versioning "${packageJson.name}" as no changes were detected.`);
437
+ logger.buffer(`🚫 Skipping versioning "${packageJson.name}" as no changes were detected.`);
438
+ // Print the buffered logs for this unchanged project, as long as the user has not explicitly disabled this behavior
439
+ if (logUnchangedProjects) {
440
+ logger.flush();
441
+ }
438
442
  continue;
439
443
  }
440
444
  const newVersion = (0, version_1.deriveNewSemverVersion)(currentVersion, specifier, options.preid);
@@ -443,7 +447,7 @@ To fix this you will either need to add a package.json file at that location, or
443
447
  ...packageJson,
444
448
  version: newVersion,
445
449
  });
446
- log(`✍️ New version ${newVersion} written to ${packageJsonPath}`);
450
+ logger.buffer(`✍️ New version ${newVersion} written to ${packageJsonPath}`);
447
451
  if (allDependentProjects.length > 0) {
448
452
  const totalProjectsToUpdate = updateDependents === 'auto'
449
453
  ? allDependentProjects.length +
@@ -452,7 +456,7 @@ To fix this you will either need to add a package.json file at that location, or
452
456
  circularDependencies.size / 2
453
457
  : dependentProjectsInCurrentBatch.length;
454
458
  if (totalProjectsToUpdate > 0) {
455
- log(`✍️ Applying new version ${newVersion} to ${totalProjectsToUpdate} ${totalProjectsToUpdate > 1
459
+ logger.buffer(`✍️ Applying new version ${newVersion} to ${totalProjectsToUpdate} ${totalProjectsToUpdate > 1
456
460
  ? 'packages which depend'
457
461
  : 'package which depends'} on ${project.name}`);
458
462
  }
@@ -555,6 +559,8 @@ To fix this you will either need to add a package.json file at that location, or
555
559
  : updateDependentsBump,
556
560
  });
557
561
  }
562
+ // Print the logs that have been buffered for this project
563
+ logger.flush();
558
564
  }
559
565
  /**
560
566
  * Ensure that formatting is applied so that version bump diffs are as minimal as possible
@@ -577,6 +583,8 @@ To fix this you will either need to add a package.json file at that location, or
577
583
  };
578
584
  }
579
585
  catch (e) {
586
+ // Flush any pending logs before printing the error to make troubleshooting easier
587
+ logger?.flush();
580
588
  if (process.env.NX_VERBOSE_LOGGING === 'true') {
581
589
  devkit_1.output.error({
582
590
  title: e.message,
@@ -630,3 +638,19 @@ function getColor(projectName) {
630
638
  const colorIndex = code % colors.length;
631
639
  return colors[colorIndex];
632
640
  }
641
+ class ProjectLogger {
642
+ constructor(projectName, color) {
643
+ this.projectName = projectName;
644
+ this.color = color;
645
+ this.logs = [];
646
+ }
647
+ buffer(msg) {
648
+ this.logs.push(msg);
649
+ }
650
+ flush() {
651
+ devkit_1.output.logSingleLine(`Running release version for project: ${this.color.instance.bold(this.projectName)}`);
652
+ this.logs.forEach((msg) => {
653
+ console.log(this.color.instance.bold(this.projectName) + ' ' + msg);
654
+ });
655
+ }
656
+ }
@@ -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
- }