@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 +4 -3
- package/package.json +4 -4
- package/src/generators/release-version/release-version.js +51 -27
- package/src/generators/typescript-sync/typescript-sync.d.ts +4 -0
- package/src/generators/typescript-sync/typescript-sync.js +243 -0
- package/src/plugins/typescript/plugin.js +2 -0
- package/src/generators/sync/sync.d.ts +0 -3
- package/src/generators/sync/sync.js +0 -75
- /package/src/generators/{sync → typescript-sync}/schema.d.ts +0 -0
- /package/src/generators/{sync → typescript-sync}/schema.json +0 -0
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
|
|
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.
|
|
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.
|
|
62
|
-
"@nx/workspace": "19.6.0-beta.
|
|
63
|
-
"@nrwl/js": "19.6.0-beta.
|
|
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
|
-
|
|
85
|
+
const color = getColor(projectName);
|
|
86
|
+
logger = new ProjectLogger(projectName, color);
|
|
87
87
|
const packageJson = (0, devkit_1.readJson)(tree, packageJsonPath);
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
logger.buffer(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
|
|
148
148
|
}
|
|
149
149
|
else {
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
+
logger.buffer(`📄 Using the current version ${currentVersion} already resolved from disk fallback.`);
|
|
188
188
|
}
|
|
189
189
|
else {
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
+
logger.buffer(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
|
|
244
244
|
break;
|
|
245
245
|
}
|
|
246
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
+
logger.buffer(`🚫 No changes were detected within version plans.`);
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
331
|
else {
|
|
332
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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,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
|
-
}
|
|
File without changes
|
|
File without changes
|