@nx/js 19.1.0-canary.20240523-261b0ff → 19.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/js",
3
- "version": "19.1.0-canary.20240523-261b0ff",
3
+ "version": "19.1.0",
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.1.0-canary.20240523-261b0ff",
62
- "@nx/workspace": "19.1.0-canary.20240523-261b0ff",
63
- "@nrwl/js": "19.1.0-canary.20240523-261b0ff"
61
+ "@nx/devkit": "19.1.0",
62
+ "@nx/workspace": "19.1.0",
63
+ "@nrwl/js": "19.1.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "verdaccio": "^5.0.4"
@@ -16,6 +16,7 @@ const semver_2 = require("semver");
16
16
  const npm_config_1 = require("../../utils/npm-config");
17
17
  const resolve_local_package_dependencies_1 = require("./utils/resolve-local-package-dependencies");
18
18
  const update_lock_file_1 = require("./utils/update-lock-file");
19
+ const sort_projects_topologically_1 = require("./utils/sort-projects-topologically");
19
20
  async function releaseVersionGenerator(tree, options) {
20
21
  try {
21
22
  const versionData = {};
@@ -39,7 +40,15 @@ Valid values are: ${version_1.validReleaseVersionPrefixes
39
40
  // always use disk as a fallback for the first release
40
41
  options.fallbackCurrentVersionResolver = 'disk';
41
42
  }
42
- const projects = options.projects;
43
+ // Set default for updateDependents
44
+ const updateDependents = options.updateDependents ?? 'never';
45
+ const updateDependentsBump = 'patch';
46
+ // Sort the projects topologically if update dependents is enabled
47
+ // TODO: maybe move this sorting to the command level?
48
+ const projects = updateDependents === 'never'
49
+ ? options.projects
50
+ : (0, sort_projects_topologically_1.sortProjectsTopologically)(options.projectGraph, options.projects);
51
+ const projectToDependencyBumps = new Map();
43
52
  const resolvePackageRoot = createResolvePackageRoot(options.packageRoot);
44
53
  // Resolve any custom package roots for each project upfront as they will need to be reused during dependency resolution
45
54
  const projectNameToPackageRootMap = new Map();
@@ -143,6 +152,9 @@ To fix this you will either need to add a package.json file at that location, or
143
152
  }
144
153
  case 'disk':
145
154
  currentVersion = currentVersionFromDisk;
155
+ if (!currentVersion) {
156
+ throw new Error(`Unable to determine the current version for project "${project.name}" from ${packageJsonPath}`);
157
+ }
146
158
  log(`📄 Resolved the current version as ${currentVersion} from ${packageJsonPath}`);
147
159
  break;
148
160
  case 'git-tag': {
@@ -221,10 +233,16 @@ To fix this you will either need to add a package.json file at that location, or
221
233
  }
222
234
  specifier = await (0, resolve_semver_specifier_1.resolveSemverSpecifierFromConventionalCommits)(previousVersionRef, options.projectGraph, affectedProjects, options.conventionalCommitsConfig);
223
235
  if (!specifier) {
236
+ if (projectToDependencyBumps.has(projectName)) {
237
+ // No applicable changes to the project directly by the user, but one or more dependencies have been bumped and updateDependents is enabled
238
+ specifier = updateDependentsBump;
239
+ log(`📄 Resolved the specifier as "${specifier}" because "release.version.generatorOptions.updateDependents" is enabled`);
240
+ break;
241
+ }
224
242
  log(`🚫 No changes were detected using git history and the conventional commits standard.`);
225
243
  break;
226
244
  }
227
- // TODO: reevaluate this logic/workflow for independent projects
245
+ // TODO: reevaluate this prerelease logic/workflow for independent projects
228
246
  //
229
247
  // Always assume that if the current version is a prerelease, then the next version should be a prerelease.
230
248
  // Users must manually graduate from a prerelease to a release by providing an explicit specifier.
@@ -261,19 +279,70 @@ To fix this you will either need to add a package.json file at that location, or
261
279
  const localPackageDependencies = (0, resolve_local_package_dependencies_1.resolveLocalPackageDependencies)(tree, options.projectGraph, projects, projectNameToPackageRootMap, resolvePackageRoot,
262
280
  // includeAll when the release group is independent, as we may be filtering to a specific subset of projects, but we still want to update their dependents
263
281
  options.releaseGroup.projectsRelationship === 'independent');
264
- const dependentProjects = Object.values(localPackageDependencies)
282
+ const allDependentProjects = Object.values(localPackageDependencies)
265
283
  .flat()
266
284
  .filter((localPackageDependency) => {
267
285
  return localPackageDependency.target === project.name;
268
286
  });
287
+ const includeTransitiveDependents = updateDependents === 'auto';
288
+ const transitiveLocalPackageDependents = [];
289
+ if (includeTransitiveDependents) {
290
+ for (const directDependent of allDependentProjects) {
291
+ // Look through localPackageDependencies to find any which have a target on the current dependent
292
+ for (const localPackageDependency of Object.values(localPackageDependencies).flat()) {
293
+ if (localPackageDependency.target === directDependent.source) {
294
+ transitiveLocalPackageDependents.push(localPackageDependency);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ const dependentProjectsInCurrentBatch = [];
300
+ const dependentProjectsOutsideCurrentBatch = [];
301
+ // Track circular dependencies using value of project1:project2
302
+ const circularDependencies = new Set();
303
+ for (const dependentProject of allDependentProjects) {
304
+ // Track circular dependencies (add both directions for easy look up)
305
+ if (dependentProject.target === projectName) {
306
+ circularDependencies.add(`${dependentProject.source}:${dependentProject.target}`);
307
+ circularDependencies.add(`${dependentProject.target}:${dependentProject.source}`);
308
+ }
309
+ const isInCurrentBatch = options.projects.some((project) => project.name === dependentProject.source);
310
+ if (!isInCurrentBatch) {
311
+ dependentProjectsOutsideCurrentBatch.push(dependentProject);
312
+ }
313
+ else {
314
+ dependentProjectsInCurrentBatch.push(dependentProject);
315
+ }
316
+ }
317
+ // If not always updating dependents (when they don't already appear in the batch itself), print a warning to the user about what is being skipped and how to change it
318
+ if (updateDependents === 'never') {
319
+ if (dependentProjectsOutsideCurrentBatch.length > 0) {
320
+ let logMsg = `⚠️ Warning, the following packages depend on "${project.name}"`;
321
+ if (options.releaseGroup.name === config_1.IMPLICIT_DEFAULT_RELEASE_GROUP) {
322
+ logMsg += ` but have been filtered out via --projects, and therefore will not be updated:`;
323
+ }
324
+ else {
325
+ logMsg += ` but are either not part of the current release group "${options.releaseGroup.name}", or have been filtered out via --projects, and therefore will not be updated:`;
326
+ }
327
+ const indent = Array.from(new Array(projectName.length + 4))
328
+ .map(() => ' ')
329
+ .join('');
330
+ logMsg += `\n${dependentProjectsOutsideCurrentBatch
331
+ .map((dependentProject) => `${indent}- ${dependentProject.source}`)
332
+ .join('\n')}`;
333
+ logMsg += `\n${indent}=> You can adjust this behavior by setting \`version.generatorOptions.updateDependents\` to "auto"`;
334
+ log(logMsg);
335
+ }
336
+ }
269
337
  if (!currentVersion) {
270
338
  throw new Error(`The current version for project "${project.name}" could not be resolved. Please report this on https://github.com/nrwl/nx`);
271
339
  }
272
340
  versionData[projectName] = {
273
341
  currentVersion,
274
- dependentProjects,
275
- // @ts-ignore: The types will be updated in a future version of Nx
276
342
  newVersion: null, // will stay as null in the final result in the case that no changes are detected
343
+ dependentProjects: updateDependents === 'auto'
344
+ ? allDependentProjects
345
+ : dependentProjectsInCurrentBatch,
277
346
  };
278
347
  if (!specifier) {
279
348
  log(`🚫 Skipping versioning "${packageJson.name}" as no changes were detected.`);
@@ -286,25 +355,30 @@ To fix this you will either need to add a package.json file at that location, or
286
355
  version: newVersion,
287
356
  });
288
357
  log(`✍️ New version ${newVersion} written to ${packageJsonPath}`);
289
- if (dependentProjects.length > 0) {
290
- log(`✍️ Applying new version ${newVersion} to ${dependentProjects.length} ${dependentProjects.length > 1
291
- ? 'packages which depend'
292
- : 'package which depends'} on ${project.name}`);
293
- }
294
- for (const dependentProject of dependentProjects) {
295
- const dependentPackageRoot = projectNameToPackageRootMap.get(dependentProject.source);
296
- if (!dependentPackageRoot) {
297
- throw new Error(`The dependent project "${dependentProject.source}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`);
358
+ if (allDependentProjects.length > 0) {
359
+ const totalProjectsToUpdate = updateDependents === 'auto'
360
+ ? allDependentProjects.length +
361
+ transitiveLocalPackageDependents.length -
362
+ // There are two entries per circular dep
363
+ circularDependencies.size / 2
364
+ : dependentProjectsInCurrentBatch.length;
365
+ if (totalProjectsToUpdate > 0) {
366
+ log(`✍️ Applying new version ${newVersion} to ${totalProjectsToUpdate} ${totalProjectsToUpdate > 1
367
+ ? 'packages which depend'
368
+ : 'package which depends'} on ${project.name}`);
298
369
  }
299
- (0, devkit_1.updateJson)(tree, (0, node_path_1.join)(dependentPackageRoot, 'package.json'), (json) => {
370
+ }
371
+ const updateDependentProjectAndAddToVersionData = ({ dependentProject, dependencyPackageName, newDependencyVersion, forceVersionBump, }) => {
372
+ const updatedFilePath = (0, devkit_1.joinPathFragments)(projectNameToPackageRootMap.get(dependentProject.source), 'package.json');
373
+ (0, devkit_1.updateJson)(tree, updatedFilePath, (json) => {
300
374
  // Auto (i.e.infer existing) by default
301
375
  let versionPrefix = options.versionPrefix ?? 'auto';
376
+ const currentDependencyVersion = json[dependentProject.dependencyCollection][dependencyPackageName];
302
377
  // For auto, we infer the prefix based on the current version of the dependent
303
378
  if (versionPrefix === 'auto') {
304
379
  versionPrefix = ''; // we don't want to end up printing auto
305
- const current = json[dependentProject.dependencyCollection][packageName];
306
- if (current) {
307
- const prefixMatch = current.match(/^[~^]/);
380
+ if (currentDependencyVersion) {
381
+ const prefixMatch = currentDependencyVersion.match(/^[~^]/);
308
382
  if (prefixMatch) {
309
383
  versionPrefix = prefixMatch[0];
310
384
  }
@@ -313,9 +387,76 @@ To fix this you will either need to add a package.json file at that location, or
313
387
  }
314
388
  }
315
389
  }
316
- json[dependentProject.dependencyCollection][packageName] = `${versionPrefix}${newVersion}`;
390
+ // Apply the new version of the dependency to the dependent
391
+ const newDepVersion = `${versionPrefix}${newDependencyVersion}`;
392
+ json[dependentProject.dependencyCollection][dependencyPackageName] =
393
+ newDepVersion;
394
+ // Bump the dependent's version if applicable and record it in the version data
395
+ if (forceVersionBump) {
396
+ const currentPackageVersion = json.version;
397
+ const newPackageVersion = (0, version_1.deriveNewSemverVersion)(currentPackageVersion, forceVersionBump, options.preid);
398
+ json.version = newPackageVersion;
399
+ // Look up any dependent projects from the transitiveLocalPackageDependents list
400
+ const transitiveDependentProjects = transitiveLocalPackageDependents.filter((localPackageDependency) => localPackageDependency.target === dependentProject.source);
401
+ versionData[dependentProject.source] = {
402
+ currentVersion: currentPackageVersion,
403
+ newVersion: newPackageVersion,
404
+ dependentProjects: transitiveDependentProjects,
405
+ };
406
+ }
317
407
  return json;
318
408
  });
409
+ };
410
+ for (const dependentProject of dependentProjectsInCurrentBatch) {
411
+ if (projectToDependencyBumps.has(dependentProject.source)) {
412
+ const dependencyBumps = projectToDependencyBumps.get(dependentProject.source);
413
+ dependencyBumps.add(projectName);
414
+ }
415
+ else {
416
+ projectToDependencyBumps.set(dependentProject.source, new Set([projectName]));
417
+ }
418
+ updateDependentProjectAndAddToVersionData({
419
+ dependentProject,
420
+ dependencyPackageName: packageName,
421
+ newDependencyVersion: newVersion,
422
+ // We don't force bump because we know they will come later in the topologically sorted projects loop and may have their own version update logic to take into account
423
+ forceVersionBump: false,
424
+ });
425
+ }
426
+ if (updateDependents === 'auto') {
427
+ for (const dependentProject of dependentProjectsOutsideCurrentBatch) {
428
+ updateDependentProjectAndAddToVersionData({
429
+ dependentProject,
430
+ dependencyPackageName: packageName,
431
+ newDependencyVersion: newVersion,
432
+ // For these additional dependents, we need to update their package.json version as well because we know they will not come later in the topologically sorted projects loop
433
+ forceVersionBump: updateDependentsBump,
434
+ });
435
+ }
436
+ }
437
+ for (const transitiveDependentProject of transitiveLocalPackageDependents) {
438
+ // Check if the transitive dependent originates from a circular dependency
439
+ const isFromCircularDependency = circularDependencies.has(`${transitiveDependentProject.source}:${transitiveDependentProject.target}`);
440
+ const dependencyProjectName = transitiveDependentProject.target;
441
+ const dependencyPackageRoot = projectNameToPackageRootMap.get(dependencyProjectName);
442
+ if (!dependencyPackageRoot) {
443
+ throw new Error(`The project "${dependencyProjectName}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`);
444
+ }
445
+ const dependencyPackageJsonPath = (0, node_path_1.join)(dependencyPackageRoot, 'package.json');
446
+ const dependencyPackageJson = (0, devkit_1.readJson)(tree, dependencyPackageJsonPath);
447
+ updateDependentProjectAndAddToVersionData({
448
+ dependentProject: transitiveDependentProject,
449
+ dependencyPackageName: dependencyPackageJson.name,
450
+ newDependencyVersion: dependencyPackageJson.version,
451
+ /**
452
+ * For these additional dependents, we need to update their package.json version as well because we know they will not come later in the topologically sorted projects loop.
453
+ * The one exception being if the dependent is part of a circular dependency, in which case we don't want to force a version bump as this would come in addition to the one
454
+ * already applied.
455
+ */
456
+ forceVersionBump: isFromCircularDependency
457
+ ? false
458
+ : updateDependentsBump,
459
+ });
319
460
  }
320
461
  }
321
462
  /**
@@ -1,5 +1,5 @@
1
1
  import { ProjectGraph, ProjectGraphDependency, ProjectGraphProjectNode, Tree } from '@nx/devkit';
2
- interface LocalPackageDependency extends ProjectGraphDependency {
2
+ export interface LocalPackageDependency extends ProjectGraphDependency {
3
3
  /**
4
4
  * The rawVersionSpec contains the value of the version spec as it was defined in the package.json
5
5
  * of the dependent project. This can be useful in cases where the version spec is a range, path or
@@ -9,4 +9,3 @@ interface LocalPackageDependency extends ProjectGraphDependency {
9
9
  dependencyCollection: 'dependencies' | 'devDependencies' | 'optionalDependencies';
10
10
  }
11
11
  export declare function resolveLocalPackageDependencies(tree: Tree, projectGraph: ProjectGraph, filteredProjects: ProjectGraphProjectNode[], projectNameToPackageRootMap: Map<string, string>, resolvePackageRoot: (projectNode: ProjectGraphProjectNode) => string, includeAll?: boolean): Record<string, LocalPackageDependency[]>;
12
- export {};
@@ -0,0 +1,2 @@
1
+ import { ProjectGraph, ProjectGraphProjectNode } from '@nx/devkit';
2
+ export declare function sortProjectsTopologically(projectGraph: ProjectGraph, projectNodes: ProjectGraphProjectNode[]): ProjectGraphProjectNode[];
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sortProjectsTopologically = void 0;
4
+ function sortProjectsTopologically(projectGraph, projectNodes) {
5
+ const edges = new Map(projectNodes.map((node) => [node, 0]));
6
+ const filteredDependencies = [];
7
+ for (const node of projectNodes) {
8
+ const deps = projectGraph.dependencies[node.name];
9
+ if (deps) {
10
+ filteredDependencies.push(...deps.filter((dep) => projectNodes.find((n) => n.name === dep.target)));
11
+ }
12
+ }
13
+ filteredDependencies.forEach((dep) => {
14
+ const sourceNode = projectGraph.nodes[dep.source];
15
+ // dep.source depends on dep.target
16
+ edges.set(sourceNode, (edges.get(sourceNode) || 0) + 1);
17
+ });
18
+ // Initialize queue with projects that have no dependencies
19
+ const processQueue = [...edges]
20
+ .filter(([_, count]) => count === 0)
21
+ .map(([node]) => node);
22
+ const sortedProjects = [];
23
+ while (processQueue.length > 0) {
24
+ const node = processQueue.shift();
25
+ sortedProjects.push(node);
26
+ // Process each project that depends on the current node
27
+ filteredDependencies.forEach((dep) => {
28
+ const dependentNode = projectGraph.nodes[dep.source];
29
+ const count = edges.get(dependentNode) - 1;
30
+ edges.set(dependentNode, count);
31
+ if (count === 0) {
32
+ processQueue.push(dependentNode);
33
+ }
34
+ });
35
+ }
36
+ /**
37
+ * We cannot reliably sort the nodes, e.g. when a circular dependency is present.
38
+ * For releases, we allow the user to work with a graph that contains cycles, so
39
+ * we should not throw an error here and simply return the original list of projects.
40
+ */
41
+ if (sortedProjects.length !== projectNodes.length) {
42
+ return projectNodes;
43
+ }
44
+ return sortedProjects;
45
+ }
46
+ exports.sortProjectsTopologically = sortProjectsTopologically;
@@ -82,8 +82,10 @@ async function updateLockFile(cwd, { dryRun, verbose, generatorOptions, }) {
82
82
  exports.updateLockFile = updateLockFile;
83
83
  function execLockFileUpdate(command, cwd, env = {}) {
84
84
  try {
85
+ const LARGE_BUFFER = 1024 * 1000000;
85
86
  (0, child_process_1.execSync)(command, {
86
87
  cwd,
88
+ maxBuffer: LARGE_BUFFER,
87
89
  env: {
88
90
  ...process.env,
89
91
  ...env,
@@ -12,16 +12,19 @@ const lock_file_1 = require("nx/src/plugins/js/lock-file/lock-file");
12
12
  const cache_directory_1 = require("nx/src/utils/cache-directory");
13
13
  const ts_config_1 = require("../../utils/typescript/ts-config");
14
14
  const cachePath = (0, node_path_1.join)(cache_directory_1.projectGraphCacheDirectory, 'tsc.hash');
15
- const targetsCache = (0, node_fs_1.existsSync)(cachePath) ? readTargetsCache() : {};
16
- const calculatedTargets = {};
15
+ const targetsCache = readTargetsCache();
17
16
  function readTargetsCache() {
18
- return (0, devkit_1.readJsonFile)(cachePath);
17
+ return (0, node_fs_1.existsSync)(cachePath) ? (0, devkit_1.readJsonFile)(cachePath) : {};
19
18
  }
20
- function writeTargetsToCache(targets) {
21
- (0, devkit_1.writeJsonFile)(cachePath, targets);
19
+ function writeTargetsToCache() {
20
+ const oldCache = readTargetsCache();
21
+ (0, devkit_1.writeJsonFile)(cachePath, {
22
+ ...oldCache,
23
+ ...targetsCache,
24
+ });
22
25
  }
23
26
  const createDependencies = () => {
24
- writeTargetsToCache(calculatedTargets);
27
+ writeTargetsToCache();
25
28
  return [];
26
29
  };
27
30
  exports.createDependencies = createDependencies;
@@ -46,15 +49,12 @@ exports.createNodes = [
46
49
  const nodeHash = (0, calculate_hash_for_create_nodes_1.calculateHashForCreateNodes)(projectRoot, pluginOptions, context, [(0, lock_file_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot))]);
47
50
  // The hash is calculated at the node/project level, so we add the config file path to avoid conflicts when caching
48
51
  const cacheKey = `${nodeHash}_${configFilePath}`;
49
- const targets = targetsCache[cacheKey]
50
- ? targetsCache[cacheKey]
51
- : buildTscTargets(fullConfigPath, projectRoot, pluginOptions, context);
52
- calculatedTargets[cacheKey] = targets;
52
+ targetsCache[cacheKey] ??= buildTscTargets(fullConfigPath, projectRoot, pluginOptions, context);
53
53
  return {
54
54
  projects: {
55
55
  [projectRoot]: {
56
56
  projectType: 'library',
57
- targets,
57
+ targets: targetsCache[cacheKey],
58
58
  },
59
59
  },
60
60
  };
@@ -23,10 +23,12 @@ function extractTsConfigBase(host) {
23
23
  return;
24
24
  const tsconfig = (0, json_1.readJson)(host, 'tsconfig.json');
25
25
  const baseCompilerOptions = {};
26
- for (let compilerOption of Object.keys(exports.tsConfigBaseOptions)) {
27
- baseCompilerOptions[compilerOption] =
28
- tsconfig.compilerOptions[compilerOption];
29
- delete tsconfig.compilerOptions[compilerOption];
26
+ if (tsconfig.compilerOptions) {
27
+ for (let compilerOption of Object.keys(exports.tsConfigBaseOptions)) {
28
+ baseCompilerOptions[compilerOption] =
29
+ tsconfig.compilerOptions[compilerOption];
30
+ delete tsconfig.compilerOptions[compilerOption];
31
+ }
30
32
  }
31
33
  (0, json_1.writeJson)(host, 'tsconfig.base.json', {
32
34
  compileOnSave: false,