nx 18.1.0-canary.20240213-7d2cb37 → 18.1.0-canary.20240214-b625a79

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",
3
- "version": "18.1.0-canary.20240213-7d2cb37",
3
+ "version": "18.1.0-canary.20240214-b625a79",
4
4
  "private": false,
5
5
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
6
6
  "repository": {
@@ -66,7 +66,7 @@
66
66
  "yargs-parser": "21.1.1",
67
67
  "node-machine-id": "1.1.12",
68
68
  "ora": "5.3.0",
69
- "@nrwl/tao": "18.1.0-canary.20240213-7d2cb37"
69
+ "@nrwl/tao": "18.1.0-canary.20240214-b625a79"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@swc-node/register": "^1.8.0",
@@ -81,16 +81,16 @@
81
81
  }
82
82
  },
83
83
  "optionalDependencies": {
84
- "@nx/nx-darwin-x64": "18.1.0-canary.20240213-7d2cb37",
85
- "@nx/nx-darwin-arm64": "18.1.0-canary.20240213-7d2cb37",
86
- "@nx/nx-linux-x64-gnu": "18.1.0-canary.20240213-7d2cb37",
87
- "@nx/nx-linux-x64-musl": "18.1.0-canary.20240213-7d2cb37",
88
- "@nx/nx-win32-x64-msvc": "18.1.0-canary.20240213-7d2cb37",
89
- "@nx/nx-linux-arm64-gnu": "18.1.0-canary.20240213-7d2cb37",
90
- "@nx/nx-linux-arm64-musl": "18.1.0-canary.20240213-7d2cb37",
91
- "@nx/nx-linux-arm-gnueabihf": "18.1.0-canary.20240213-7d2cb37",
92
- "@nx/nx-win32-arm64-msvc": "18.1.0-canary.20240213-7d2cb37",
93
- "@nx/nx-freebsd-x64": "18.1.0-canary.20240213-7d2cb37"
84
+ "@nx/nx-darwin-x64": "18.1.0-canary.20240214-b625a79",
85
+ "@nx/nx-darwin-arm64": "18.1.0-canary.20240214-b625a79",
86
+ "@nx/nx-linux-x64-gnu": "18.1.0-canary.20240214-b625a79",
87
+ "@nx/nx-linux-x64-musl": "18.1.0-canary.20240214-b625a79",
88
+ "@nx/nx-win32-x64-msvc": "18.1.0-canary.20240214-b625a79",
89
+ "@nx/nx-linux-arm64-gnu": "18.1.0-canary.20240214-b625a79",
90
+ "@nx/nx-linux-arm64-musl": "18.1.0-canary.20240214-b625a79",
91
+ "@nx/nx-linux-arm-gnueabihf": "18.1.0-canary.20240214-b625a79",
92
+ "@nx/nx-win32-arm64-msvc": "18.1.0-canary.20240214-b625a79",
93
+ "@nx/nx-freebsd-x64": "18.1.0-canary.20240214-b625a79"
94
94
  },
95
95
  "nx-migrations": {
96
96
  "migrations": "./migrations.json",
@@ -862,7 +862,17 @@ function runInstall() {
862
862
  async function executeMigrations(root, migrations, isVerbose, shouldCreateCommits, commitPrefix) {
863
863
  const depsBeforeMigrations = getStringifiedPackageJsonDeps(root);
864
864
  const migrationsWithNoChanges = [];
865
- for (const m of migrations) {
865
+ const sortedMigrations = migrations.sort((a, b) => {
866
+ // special case for the split configuration migration to run first
867
+ if (a.name === '15-7-0-split-configuration-into-project-json-files') {
868
+ return -1;
869
+ }
870
+ if (b.name === '15-7-0-split-configuration-into-project-json-files') {
871
+ return 1;
872
+ }
873
+ return (0, semver_1.lt)(a.version, b.version) ? -1 : 1;
874
+ });
875
+ for (const m of sortedMigrations) {
866
876
  try {
867
877
  const { collection, collectionPath } = readMigrationCollection(m.package, root);
868
878
  if (!isAngularMigration(collection, collectionPath, m.name)) {
@@ -1,8 +1,23 @@
1
+ import { NxReleaseChangelogConfiguration } from '../../config/nx-json';
1
2
  import { ChangelogOptions } from './command-object';
3
+ import { ReleaseVersion } from './utils/shared';
4
+ export interface NxReleaseChangelogResult {
5
+ workspaceChangelog?: {
6
+ releaseVersion: ReleaseVersion;
7
+ contents: string;
8
+ };
9
+ projectChangelogs?: {
10
+ [projectName: string]: {
11
+ releaseVersion: ReleaseVersion;
12
+ contents: string;
13
+ };
14
+ };
15
+ }
2
16
  export declare const releaseChangelogCLIHandler: (args: ChangelogOptions) => Promise<any>;
3
17
  /**
4
18
  * NOTE: This function is also exported for programmatic usage and forms part of the public API
5
19
  * of Nx. We intentionally do not wrap the implementation with handleErrors because users need
6
20
  * to have control over their own error handling when using the API.
7
21
  */
8
- export declare function releaseChangelog(args: ChangelogOptions): Promise<number>;
22
+ export declare function releaseChangelog(args: ChangelogOptions): Promise<NxReleaseChangelogResult>;
23
+ export declare function shouldCreateGitHubRelease(changelogConfig: NxReleaseChangelogConfiguration | false | undefined, createReleaseArg?: ChangelogOptions['createRelease'] | undefined): boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.releaseChangelog = exports.releaseChangelogCLIHandler = void 0;
3
+ exports.shouldCreateGitHubRelease = exports.releaseChangelog = exports.releaseChangelogCLIHandler = void 0;
4
4
  const chalk = require("chalk");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const semver_1 = require("semver");
@@ -74,7 +74,7 @@ async function releaseChangelog(args) {
74
74
  `To explicitly enable changelog generation, configure "release.changelog.workspaceChangelog" or "release.changelog.projectChangelogs" in nx.json.`,
75
75
  ],
76
76
  });
77
- return 0;
77
+ return {};
78
78
  }
79
79
  const useAutomaticFromRef = nxReleaseConfig.changelog?.automaticFromRef || args.firstRelease;
80
80
  /**
@@ -128,7 +128,26 @@ async function releaseChangelog(args) {
128
128
  // Make sure that the fromRef is actually resolvable
129
129
  const workspaceChangelogFromSHA = await (0, git_1.getCommitHash)(workspaceChangelogFromRef);
130
130
  const workspaceChangelogCommits = await getCommits(workspaceChangelogFromSHA, toSHA);
131
- await generateChangelogForWorkspace(tree, args, projectGraph, nxReleaseConfig, workspaceChangelogVersion, workspaceChangelogCommits, postGitTasks);
131
+ const workspaceChangelog = await generateChangelogForWorkspace(tree, args, projectGraph, nxReleaseConfig, workspaceChangelogVersion, workspaceChangelogCommits);
132
+ if (workspaceChangelog &&
133
+ shouldCreateGitHubRelease(nxReleaseConfig.changelog.workspaceChangelog, args.createRelease)) {
134
+ let hasPushed = false;
135
+ postGitTasks.push(async (latestCommit) => {
136
+ if (!hasPushed) {
137
+ output_1.output.logSingleLine(`Pushing to git remote`);
138
+ // Before we can create/update the release we need to ensure the commit exists on the remote
139
+ await (0, git_1.gitPush)({
140
+ gitRemote: args.gitRemote,
141
+ dryRun: args.dryRun,
142
+ verbose: args.verbose,
143
+ });
144
+ hasPushed = true;
145
+ }
146
+ output_1.output.logSingleLine(`Creating GitHub Release`);
147
+ await (0, github_1.createOrUpdateGithubRelease)(workspaceChangelog.releaseVersion, workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
148
+ });
149
+ }
150
+ const allProjectChangelogs = {};
132
151
  for (const releaseGroup of releaseGroups) {
133
152
  const config = releaseGroup.changelog;
134
153
  // The entire feature is disabled at the release group level, exit early
@@ -164,7 +183,28 @@ async function releaseChangelog(args) {
164
183
  if (!commits) {
165
184
  commits = await getCommits(fromRef, toSHA);
166
185
  }
167
- await generateChangelogForProjects(tree, args, projectGraph, commits, projectsVersionData, postGitTasks, releaseGroup, [project]);
186
+ const projectChangelogs = await generateChangelogForProjects(tree, args, projectGraph, commits, projectsVersionData, postGitTasks, releaseGroup, [project]);
187
+ let hasPushed = false;
188
+ for (const [projectName, projectChangelog] of Object.entries(projectChangelogs)) {
189
+ if (projectChangelogs &&
190
+ shouldCreateGitHubRelease(releaseGroup.changelog, args.createRelease)) {
191
+ postGitTasks.push(async (latestCommit) => {
192
+ if (!hasPushed) {
193
+ output_1.output.logSingleLine(`Pushing to git remote`);
194
+ // Before we can create/update the release we need to ensure the commit exists on the remote
195
+ await (0, git_1.gitPush)({
196
+ gitRemote: args.gitRemote,
197
+ dryRun: args.dryRun,
198
+ verbose: args.verbose,
199
+ });
200
+ hasPushed = true;
201
+ }
202
+ output_1.output.logSingleLine(`Creating GitHub Release`);
203
+ await (0, github_1.createOrUpdateGithubRelease)(projectChangelog.releaseVersion, projectChangelog.contents, latestCommit, { dryRun: args.dryRun });
204
+ });
205
+ }
206
+ allProjectChangelogs[projectName] = projectChangelog;
207
+ }
168
208
  }
169
209
  }
170
210
  else {
@@ -176,10 +216,35 @@ async function releaseChangelog(args) {
176
216
  // Make sure that the fromRef is actually resolvable
177
217
  const fromSHA = await (0, git_1.getCommitHash)(fromRef);
178
218
  const commits = await getCommits(fromSHA, toSHA);
179
- await generateChangelogForProjects(tree, args, projectGraph, commits, projectsVersionData, postGitTasks, releaseGroup, projectNodes);
219
+ const projectChangelogs = await generateChangelogForProjects(tree, args, projectGraph, commits, projectsVersionData, postGitTasks, releaseGroup, projectNodes);
220
+ let hasPushed = false;
221
+ for (const [projectName, projectChangelog] of Object.entries(projectChangelogs)) {
222
+ if (projectChangelogs &&
223
+ shouldCreateGitHubRelease(releaseGroup.changelog, args.createRelease)) {
224
+ postGitTasks.push(async (latestCommit) => {
225
+ if (!hasPushed) {
226
+ output_1.output.logSingleLine(`Pushing to git remote`);
227
+ // Before we can create/update the release we need to ensure the commit exists on the remote
228
+ await (0, git_1.gitPush)({
229
+ gitRemote: args.gitRemote,
230
+ dryRun: args.dryRun,
231
+ verbose: args.verbose,
232
+ });
233
+ hasPushed = true;
234
+ }
235
+ output_1.output.logSingleLine(`Creating GitHub Release`);
236
+ await (0, github_1.createOrUpdateGithubRelease)(projectChangelog.releaseVersion, projectChangelog.contents, latestCommit, { dryRun: args.dryRun });
237
+ });
238
+ }
239
+ allProjectChangelogs[projectName] = projectChangelog;
240
+ }
180
241
  }
181
242
  }
182
- return await applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTasks, commitMessageValues, gitTagValues);
243
+ await applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTasks, commitMessageValues, gitTagValues);
244
+ return {
245
+ workspaceChangelog,
246
+ projectChangelogs: allProjectChangelogs,
247
+ };
183
248
  }
184
249
  exports.releaseChangelog = releaseChangelog;
185
250
  function resolveChangelogVersions(args, releaseGroups, releaseGroupToFilteredProjects) {
@@ -237,7 +302,7 @@ async function applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTa
237
302
  `No changes were detected for any changelog files, so no changelog entries will be generated.`,
238
303
  ],
239
304
  });
240
- return 0;
305
+ return;
241
306
  }
242
307
  // Generate a new commit for the changes, if configured to do so
243
308
  if (args.gitCommit ?? nxReleaseConfig.changelog.git.commit) {
@@ -271,7 +336,7 @@ async function applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTa
271
336
  for (const postGitTask of postGitTasks) {
272
337
  await postGitTask(latestCommit);
273
338
  }
274
- return 0;
339
+ return;
275
340
  }
276
341
  function resolveChangelogRenderer(changelogRendererPath) {
277
342
  // Try and load the provided (or default) changelog renderer
@@ -292,7 +357,7 @@ function resolveChangelogRenderer(changelogRendererPath) {
292
357
  }
293
358
  return changelogRenderer;
294
359
  }
295
- async function generateChangelogForWorkspace(tree, args, projectGraph, nxReleaseConfig, workspaceChangelogVersion, commits, postGitTasks) {
360
+ async function generateChangelogForWorkspace(tree, args, projectGraph, nxReleaseConfig, workspaceChangelogVersion, commits) {
296
361
  const config = nxReleaseConfig.changelog.workspaceChangelog;
297
362
  // The entire feature is disabled at the workspace level, exit early
298
363
  if (config === false) {
@@ -344,21 +409,12 @@ async function generateChangelogForWorkspace(tree, args, projectGraph, nxRelease
344
409
  version: workspaceChangelogVersion,
345
410
  releaseTagPattern: nxReleaseConfig.releaseTagPattern,
346
411
  });
347
- // We are either creating/previewing a changelog file, a GitHub release, or both
348
- let logTitle = dryRun ? 'Previewing a' : 'Generating a';
349
- switch (true) {
350
- case interpolatedTreePath && config.createRelease === 'github':
351
- logTitle += ` GitHub release and an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
352
- break;
353
- case !!interpolatedTreePath:
354
- logTitle += `n entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
355
- break;
356
- case config.createRelease === 'github':
357
- logTitle += ` GitHub release for ${chalk.white(releaseVersion.gitTag)}`;
358
- }
359
- output_1.output.log({
360
- title: logTitle,
361
- });
412
+ if (interpolatedTreePath) {
413
+ const prefix = dryRun ? 'Previewing' : 'Generating';
414
+ output_1.output.log({
415
+ title: `${prefix} an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`,
416
+ });
417
+ }
362
418
  const githubRepoSlug = (0, github_1.getGitHubRepoSlug)(gitRemote);
363
419
  let contents = await changelogRenderer({
364
420
  projectGraph,
@@ -382,12 +438,6 @@ async function generateChangelogForWorkspace(tree, args, projectGraph, nxRelease
382
438
  await (0, launch_editor_1.launchEditor)(changelogPath);
383
439
  contents = (0, node_fs_1.readFileSync)(changelogPath, 'utf-8');
384
440
  }
385
- /**
386
- * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating
387
- * a changelog file, a GitHub release, or both.
388
- */
389
- let printSummary = () => { };
390
- const noDiffInChangelogMessage = chalk.yellow(`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`);
391
441
  if (interpolatedTreePath) {
392
442
  let rootChangelogContents = tree.exists(interpolatedTreePath)
393
443
  ? tree.read(interpolatedTreePath).toString()
@@ -409,79 +459,12 @@ async function generateChangelogForWorkspace(tree, args, projectGraph, nxRelease
409
459
  rootChangelogContents = contents;
410
460
  }
411
461
  tree.write(interpolatedTreePath, rootChangelogContents);
412
- printSummary = () => (0, print_changes_1.printAndFlushChanges)(tree, !!dryRun, 3, false, noDiffInChangelogMessage);
413
- }
414
- if (config.createRelease === 'github') {
415
- if (!githubRepoSlug) {
416
- output_1.output.error({
417
- title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`,
418
- bodyLines: [
419
- `Please ensure you have a valid GitHub remote configured. You can run \`git remote -v\` to list your current remotes.`,
420
- ],
421
- });
422
- process.exit(1);
423
- }
424
- const token = await (0, github_1.resolveGithubToken)();
425
- const githubRequestConfig = {
426
- repo: githubRepoSlug,
427
- token,
428
- };
429
- let existingGithubReleaseForVersion;
430
- try {
431
- existingGithubReleaseForVersion = await (0, github_1.getGithubReleaseByTag)(githubRequestConfig, releaseVersion.gitTag);
432
- }
433
- catch (err) {
434
- if (err.response?.status === 401) {
435
- output_1.output.error({
436
- title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`,
437
- bodyLines: [
438
- '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope',
439
- '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal',
440
- ],
441
- });
442
- process.exit(1);
443
- }
444
- if (err.response?.status === 404) {
445
- // No existing release found, this is fine
446
- }
447
- else {
448
- // Rethrow unknown errors for now
449
- throw err;
450
- }
451
- }
452
- let existingPrintSummaryFn = printSummary;
453
- printSummary = () => {
454
- const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
455
- if (existingGithubReleaseForVersion) {
456
- console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
457
- }
458
- else {
459
- console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
460
- }
461
- // Only print the diff here if we are not already going to be printing changes from the Tree
462
- if (!interpolatedTreePath) {
463
- console.log('');
464
- (0, print_changes_1.printDiff)(existingGithubReleaseForVersion
465
- ? existingGithubReleaseForVersion.body
466
- : '', contents, 3, noDiffInChangelogMessage);
467
- }
468
- existingPrintSummaryFn();
469
- };
470
- // Only schedule the actual GitHub update when not in dry-run mode
471
- if (!dryRun) {
472
- postGitTasks.push(async (latestCommit) => {
473
- // Before we can create/update the release we need to ensure the commit exists on the remote
474
- await (0, git_1.gitPush)();
475
- await (0, github_1.createOrUpdateGithubRelease)(githubRequestConfig, {
476
- version: releaseVersion.gitTag,
477
- prerelease: releaseVersion.isPrerelease,
478
- body: contents,
479
- commit: latestCommit,
480
- }, existingGithubReleaseForVersion);
481
- });
482
- }
462
+ (0, print_changes_1.printAndFlushChanges)(tree, !!dryRun, 3, false, shared_1.noDiffInChangelogMessage);
483
463
  }
484
- printSummary();
464
+ return {
465
+ releaseVersion,
466
+ contents,
467
+ };
485
468
  }
486
469
  async function generateChangelogForProjects(tree, args, projectGraph, commits, projectsVersionData, postGitTasks, releaseGroup, projects) {
487
470
  const config = releaseGroup.changelog;
@@ -494,6 +477,7 @@ async function generateChangelogForProjects(tree, args, projectGraph, commits, p
494
477
  const dryRun = !!args.dryRun;
495
478
  const gitRemote = args.gitRemote;
496
479
  const changelogRenderer = resolveChangelogRenderer(config.renderer);
480
+ const projectChangelogs = {};
497
481
  for (const project of projects) {
498
482
  let interpolatedTreePath = config.file || '';
499
483
  if (interpolatedTreePath) {
@@ -515,21 +499,12 @@ async function generateChangelogForProjects(tree, args, projectGraph, commits, p
515
499
  releaseTagPattern: releaseGroup.releaseTagPattern,
516
500
  projectName: project.name,
517
501
  });
518
- // We are either creating/previewing a changelog file, a GitHub release, or both
519
- let logTitle = dryRun ? 'Previewing a' : 'Generating a';
520
- switch (true) {
521
- case interpolatedTreePath && config.createRelease === 'github':
522
- logTitle += ` GitHub release and an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
523
- break;
524
- case !!interpolatedTreePath:
525
- logTitle += `n entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
526
- break;
527
- case config.createRelease === 'github':
528
- logTitle += ` GitHub release for ${chalk.white(releaseVersion.gitTag)}`;
502
+ if (interpolatedTreePath) {
503
+ const prefix = dryRun ? 'Previewing' : 'Generating';
504
+ output_1.output.log({
505
+ title: `${prefix} an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`,
506
+ });
529
507
  }
530
- output_1.output.log({
531
- title: logTitle,
532
- });
533
508
  const githubRepoSlug = config.createRelease === 'github'
534
509
  ? (0, github_1.getGitHubRepoSlug)(gitRemote)
535
510
  : undefined;
@@ -561,12 +536,6 @@ async function generateChangelogForProjects(tree, args, projectGraph, commits, p
561
536
  await (0, launch_editor_1.launchEditor)(changelogPath);
562
537
  contents = (0, node_fs_1.readFileSync)(changelogPath, 'utf-8');
563
538
  }
564
- /**
565
- * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating
566
- * a changelog file, a GitHub release, or both.
567
- */
568
- let printSummary = () => { };
569
- const noDiffInChangelogMessage = chalk.yellow(`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`);
570
539
  if (interpolatedTreePath) {
571
540
  let changelogContents = tree.exists(interpolatedTreePath)
572
541
  ? tree.read(interpolatedTreePath).toString()
@@ -588,82 +557,16 @@ async function generateChangelogForProjects(tree, args, projectGraph, commits, p
588
557
  changelogContents = contents;
589
558
  }
590
559
  tree.write(interpolatedTreePath, changelogContents);
591
- printSummary = () => (0, print_changes_1.printAndFlushChanges)(tree, !!dryRun, 3, false, noDiffInChangelogMessage,
560
+ (0, print_changes_1.printAndFlushChanges)(tree, !!dryRun, 3, false, shared_1.noDiffInChangelogMessage,
592
561
  // Only print the change for the current changelog file at this point
593
562
  (f) => f.path === interpolatedTreePath);
594
563
  }
595
- if (config.createRelease === 'github') {
596
- if (!githubRepoSlug) {
597
- output_1.output.error({
598
- title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`,
599
- bodyLines: [
600
- `Please ensure you have a valid GitHub remote configured. You can run \`git remote -v\` to list your current remotes.`,
601
- ],
602
- });
603
- process.exit(1);
604
- }
605
- const token = await (0, github_1.resolveGithubToken)();
606
- const githubRequestConfig = {
607
- repo: githubRepoSlug,
608
- token,
609
- };
610
- let existingGithubReleaseForVersion;
611
- try {
612
- existingGithubReleaseForVersion = await (0, github_1.getGithubReleaseByTag)(githubRequestConfig, releaseVersion.gitTag);
613
- }
614
- catch (err) {
615
- if (err.response?.status === 401) {
616
- output_1.output.error({
617
- title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`,
618
- bodyLines: [
619
- '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope',
620
- '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal',
621
- ],
622
- });
623
- process.exit(1);
624
- }
625
- if (err.response?.status === 404) {
626
- // No existing release found, this is fine
627
- }
628
- else {
629
- // Rethrow unknown errors for now
630
- throw err;
631
- }
632
- }
633
- let existingPrintSummaryFn = printSummary;
634
- printSummary = () => {
635
- const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
636
- if (existingGithubReleaseForVersion) {
637
- console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
638
- }
639
- else {
640
- console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
641
- }
642
- // Only print the diff here if we are not already going to be printing changes from the Tree
643
- if (!interpolatedTreePath) {
644
- console.log('');
645
- (0, print_changes_1.printDiff)(existingGithubReleaseForVersion
646
- ? existingGithubReleaseForVersion.body
647
- : '', contents, 3, noDiffInChangelogMessage);
648
- }
649
- existingPrintSummaryFn();
650
- };
651
- // Only schedule the actual GitHub update when not in dry-run mode
652
- if (!dryRun) {
653
- postGitTasks.push(async (latestCommit) => {
654
- // Before we can create/update the release we need to ensure the commit exists on the remote
655
- await (0, git_1.gitPush)(gitRemote);
656
- await (0, github_1.createOrUpdateGithubRelease)(githubRequestConfig, {
657
- version: releaseVersion.gitTag,
658
- prerelease: releaseVersion.isPrerelease,
659
- body: contents,
660
- commit: latestCommit,
661
- }, existingGithubReleaseForVersion);
662
- });
663
- }
664
- }
665
- printSummary();
564
+ projectChangelogs[project.name] = {
565
+ releaseVersion,
566
+ contents,
567
+ };
666
568
  }
569
+ return projectChangelogs;
667
570
  }
668
571
  function checkChangelogFilesEnabled(nxReleaseConfig) {
669
572
  if (nxReleaseConfig.changelog.workspaceChangelog &&
@@ -690,3 +593,10 @@ async function getCommits(fromSHA, toSHA) {
690
593
  return false;
691
594
  });
692
595
  }
596
+ function shouldCreateGitHubRelease(changelogConfig, createReleaseArg = undefined) {
597
+ if (createReleaseArg !== undefined) {
598
+ return createReleaseArg === 'github';
599
+ }
600
+ return (changelogConfig || {}).createRelease === 'github';
601
+ }
602
+ exports.shouldCreateGitHubRelease = shouldCreateGitHubRelease;
@@ -29,6 +29,7 @@ export type ChangelogOptions = NxReleaseArgs & GitCommitAndTagOptions & {
29
29
  from?: string;
30
30
  interactive?: string;
31
31
  gitRemote?: string;
32
+ createRelease?: false | 'github';
32
33
  };
33
34
  export type PublishOptions = NxReleaseArgs & Partial<RunManyOptions> & {
34
35
  outputStyle?: OutputStyle;
@@ -11,6 +11,7 @@ const config_1 = require("./config/config");
11
11
  const filter_release_groups_1 = require("./config/filter-release-groups");
12
12
  const publish_1 = require("./publish");
13
13
  const git_1 = require("./utils/git");
14
+ const github_1 = require("./utils/github");
14
15
  const resolve_nx_json_error_message_1 = require("./utils/resolve-nx-json-error-message");
15
16
  const shared_1 = require("./utils/shared");
16
17
  const version_1 = require("./version");
@@ -51,13 +52,14 @@ async function release(args) {
51
52
  gitCommit: false,
52
53
  gitTag: false,
53
54
  });
54
- await (0, changelog_1.releaseChangelog)({
55
+ const changelogResult = await (0, changelog_1.releaseChangelog)({
55
56
  ...args,
56
57
  versionData: versionResult.projectsVersionData,
57
58
  version: versionResult.workspaceVersion,
58
59
  stageChanges: shouldStage,
59
60
  gitCommit: false,
60
61
  gitTag: false,
62
+ createRelease: false,
61
63
  });
62
64
  const { error: filterError, releaseGroups, releaseGroupToFilteredProjects, } = (0, filter_release_groups_1.filterReleaseGroups)(projectGraph, nxReleaseConfig, args.projects, args.groups);
63
65
  if (filterError) {
@@ -90,6 +92,52 @@ async function release(args) {
90
92
  });
91
93
  }
92
94
  }
95
+ const shouldCreateWorkspaceRelease = (0, changelog_1.shouldCreateGitHubRelease)(nxReleaseConfig.changelog.workspaceChangelog);
96
+ let hasPushedChanges = false;
97
+ let latestCommit;
98
+ if (shouldCreateWorkspaceRelease && changelogResult.workspaceChangelog) {
99
+ devkit_exports_1.output.logSingleLine(`Pushing to git remote`);
100
+ // Before we can create/update the release we need to ensure the commit exists on the remote
101
+ await (0, git_1.gitPush)({
102
+ dryRun: args.dryRun,
103
+ verbose: args.verbose,
104
+ });
105
+ hasPushedChanges = true;
106
+ devkit_exports_1.output.logSingleLine(`Creating GitHub Release`);
107
+ latestCommit = await (0, git_1.getCommitHash)('HEAD');
108
+ await (0, github_1.createOrUpdateGithubRelease)(changelogResult.workspaceChangelog.releaseVersion, changelogResult.workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
109
+ }
110
+ for (const releaseGroup of releaseGroups) {
111
+ const shouldCreateProjectReleases = (0, changelog_1.shouldCreateGitHubRelease)(releaseGroup.changelog);
112
+ if (shouldCreateProjectReleases && changelogResult.projectChangelogs) {
113
+ const projects = args.projects?.length
114
+ ? // If the user has passed a list of projects, we need to use the filtered list of projects within the release group
115
+ Array.from(releaseGroupToFilteredProjects.get(releaseGroup))
116
+ : // Otherwise, we use the full list of projects within the release group
117
+ releaseGroup.projects;
118
+ const projectNodes = projects.map((name) => projectGraph.nodes[name]);
119
+ for (const project of projectNodes) {
120
+ const changelog = changelogResult.projectChangelogs[project.name];
121
+ if (!changelog) {
122
+ continue;
123
+ }
124
+ if (!hasPushedChanges) {
125
+ devkit_exports_1.output.logSingleLine(`Pushing to git remote`);
126
+ // Before we can create/update the release we need to ensure the commit exists on the remote
127
+ await (0, git_1.gitPush)({
128
+ dryRun: args.dryRun,
129
+ verbose: args.verbose,
130
+ });
131
+ hasPushedChanges = true;
132
+ }
133
+ devkit_exports_1.output.logSingleLine(`Creating GitHub Release`);
134
+ if (!latestCommit) {
135
+ latestCommit = await (0, git_1.getCommitHash)('HEAD');
136
+ }
137
+ await (0, github_1.createOrUpdateGithubRelease)(changelog.releaseVersion, changelog.contents, latestCommit, { dryRun: args.dryRun });
138
+ }
139
+ }
140
+ }
93
141
  let hasNewVersion = false;
94
142
  // null means that all projects are versioned together but there were no changes
95
143
  if (versionResult.workspaceVersion !== null) {
@@ -48,7 +48,11 @@ export declare function gitTag({ tag, message, additionalArgs, dryRun, verbose,
48
48
  verbose?: boolean;
49
49
  logFn?: (message: string) => void;
50
50
  }): Promise<string>;
51
- export declare function gitPush(gitRemote?: string): Promise<void>;
51
+ export declare function gitPush({ gitRemote, dryRun, verbose, }: {
52
+ gitRemote?: string;
53
+ dryRun?: boolean;
54
+ verbose?: boolean;
55
+ }): Promise<void>;
52
56
  export declare function parseCommits(commits: RawGitCommit[]): GitCommit[];
53
57
  export declare function parseGitCommit(commit: RawGitCommit): GitCommit | null;
54
58
  export declare function getCommitHash(ref: string): Promise<string>;
@@ -155,17 +155,27 @@ async function gitTag({ tag, message, additionalArgs, dryRun, verbose, logFn, })
155
155
  }
156
156
  }
157
157
  exports.gitTag = gitTag;
158
- async function gitPush(gitRemote) {
158
+ async function gitPush({ gitRemote, dryRun, verbose, }) {
159
+ const commandArgs = [
160
+ 'push',
161
+ // NOTE: It's important we use --follow-tags, and not --tags, so that we are precise about what we are pushing
162
+ '--follow-tags',
163
+ '--no-verify',
164
+ '--atomic',
165
+ // Set custom git remote if provided
166
+ ...(gitRemote ? [gitRemote] : []),
167
+ ];
168
+ if (verbose) {
169
+ console.log(dryRun
170
+ ? `Would push the current branch to the remote with the following command, but --dry-run was set:`
171
+ : `Pushing the current branch to the remote with the following command:`);
172
+ console.log(`git ${commandArgs.join(' ')}`);
173
+ }
174
+ if (dryRun) {
175
+ return;
176
+ }
159
177
  try {
160
- await (0, exec_command_1.execCommand)('git', [
161
- 'push',
162
- // NOTE: It's important we use --follow-tags, and not --tags, so that we are precise about what we are pushing
163
- '--follow-tags',
164
- '--no-verify',
165
- '--atomic',
166
- // Set custom git remote if provided
167
- ...(gitRemote ? [gitRemote] : []),
168
- ]);
178
+ await (0, exec_command_1.execCommand)('git', commandArgs);
169
179
  }
170
180
  catch (err) {
171
181
  throw new Error(`Unexpected git push error: ${err}`);
@@ -252,7 +262,12 @@ async function getCommitHash(ref) {
252
262
  exports.getCommitHash = getCommitHash;
253
263
  async function getFirstGitCommit() {
254
264
  try {
255
- return (await (0, exec_command_1.execCommand)('git', ['rev-list', '--max-parents=0', 'HEAD'])).trim();
265
+ return (await (0, exec_command_1.execCommand)('git', [
266
+ 'rev-list',
267
+ '--max-parents=0',
268
+ 'HEAD',
269
+ '--first-parent',
270
+ ])).trim();
256
271
  }
257
272
  catch (e) {
258
273
  throw new Error(`Unable to find first commit in git history`);
@@ -1,4 +1,5 @@
1
1
  import { Reference } from './git';
2
+ import { ReleaseVersion } from './shared';
2
3
  export type RepoSlug = `${string}/${string}`;
3
4
  export interface GithubRequestConfig {
4
5
  repo: string;
@@ -14,14 +15,9 @@ export interface GithubRelease {
14
15
  prerelease?: boolean;
15
16
  }
16
17
  export declare function getGitHubRepoSlug(remoteName?: string): RepoSlug;
17
- interface GithubReleaseOptions {
18
- version: string;
19
- body: string;
20
- prerelease: boolean;
21
- commit: string;
22
- }
23
- export declare function createOrUpdateGithubRelease(githubRequestConfig: GithubRequestConfig, release: GithubReleaseOptions, existingGithubReleaseForVersion?: GithubRelease): Promise<void>;
18
+ export declare function createOrUpdateGithubRelease(releaseVersion: ReleaseVersion, changelogContents: string, latestCommit: string, { dryRun }: {
19
+ dryRun: boolean;
20
+ }): Promise<void>;
24
21
  export declare function resolveGithubToken(): Promise<string | null>;
25
22
  export declare function getGithubReleaseByTag(config: GithubRequestConfig, tag: string): Promise<GithubRelease>;
26
23
  export declare function formatReferences(references: Reference[], repoSlug: RepoSlug): string;
27
- export {};
@@ -8,6 +8,8 @@ const node_fs_1 = require("node:fs");
8
8
  const node_os_1 = require("node:os");
9
9
  const output_1 = require("../../../utils/output");
10
10
  const path_1 = require("../../../utils/path");
11
+ const print_changes_1 = require("./print-changes");
12
+ const shared_1 = require("./shared");
11
13
  // axios types and values don't seem to match
12
14
  const _axios = require("axios");
13
15
  const axios = _axios;
@@ -32,7 +34,65 @@ function getGitHubRepoSlug(remoteName = 'origin') {
32
34
  }
33
35
  }
34
36
  exports.getGitHubRepoSlug = getGitHubRepoSlug;
35
- async function createOrUpdateGithubRelease(githubRequestConfig, release, existingGithubReleaseForVersion) {
37
+ async function createOrUpdateGithubRelease(releaseVersion, changelogContents, latestCommit, { dryRun }) {
38
+ const githubRepoSlug = getGitHubRepoSlug();
39
+ if (!githubRepoSlug) {
40
+ output_1.output.error({
41
+ title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`,
42
+ bodyLines: [
43
+ `Please ensure you have a valid GitHub remote configured. You can run \`git remote -v\` to list your current remotes.`,
44
+ ],
45
+ });
46
+ process.exit(1);
47
+ }
48
+ const token = await resolveGithubToken();
49
+ const githubRequestConfig = {
50
+ repo: githubRepoSlug,
51
+ token,
52
+ };
53
+ let existingGithubReleaseForVersion;
54
+ try {
55
+ existingGithubReleaseForVersion = await getGithubReleaseByTag(githubRequestConfig, releaseVersion.gitTag);
56
+ }
57
+ catch (err) {
58
+ if (err.response?.status === 401) {
59
+ output_1.output.error({
60
+ title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`,
61
+ bodyLines: [
62
+ '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope',
63
+ '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal',
64
+ ],
65
+ });
66
+ process.exit(1);
67
+ }
68
+ if (err.response?.status === 404) {
69
+ // No existing release found, this is fine
70
+ }
71
+ else {
72
+ // Rethrow unknown errors for now
73
+ throw err;
74
+ }
75
+ }
76
+ const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
77
+ if (existingGithubReleaseForVersion) {
78
+ console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
79
+ }
80
+ else {
81
+ console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
82
+ }
83
+ console.log('');
84
+ (0, print_changes_1.printDiff)(existingGithubReleaseForVersion ? existingGithubReleaseForVersion.body : '', changelogContents, 3, shared_1.noDiffInChangelogMessage);
85
+ if (!dryRun) {
86
+ await createOrUpdateGithubReleaseInternal(githubRequestConfig, {
87
+ version: releaseVersion.gitTag,
88
+ prerelease: releaseVersion.isPrerelease,
89
+ body: changelogContents,
90
+ commit: latestCommit,
91
+ }, existingGithubReleaseForVersion);
92
+ }
93
+ }
94
+ exports.createOrUpdateGithubRelease = createOrUpdateGithubRelease;
95
+ async function createOrUpdateGithubReleaseInternal(githubRequestConfig, release, existingGithubReleaseForVersion) {
36
96
  const result = await syncGithubRelease(githubRequestConfig, release, existingGithubReleaseForVersion);
37
97
  /**
38
98
  * If something went wrong POSTing to Github we can still pre-populate the web form on github.com
@@ -98,7 +158,6 @@ async function createOrUpdateGithubRelease(githubRequestConfig, release, existin
98
158
  });
99
159
  }
100
160
  }
101
- exports.createOrUpdateGithubRelease = createOrUpdateGithubRelease;
102
161
  async function promptForContinueInGitHub() {
103
162
  try {
104
163
  const reply = await (0, enquirer_1.prompt)([
@@ -2,6 +2,7 @@ import { ProjectGraph } from '../../../config/project-graph';
2
2
  import { Tree } from '../../../generators/tree';
3
3
  import type { ReleaseGroupWithName } from '../config/filter-release-groups';
4
4
  import { GitCommit } from './git';
5
+ export declare const noDiffInChangelogMessage: string;
5
6
  export type ReleaseVersionGeneratorResult = {
6
7
  data: VersionData;
7
8
  callback: (tree: Tree, opts: {
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getCommitsRelevantToProjects = exports.handleDuplicateGitTags = exports.createGitTagValues = exports.createCommitMessageValues = exports.commitChanges = exports.ReleaseVersion = void 0;
3
+ exports.getCommitsRelevantToProjects = exports.handleDuplicateGitTags = exports.createGitTagValues = exports.createCommitMessageValues = exports.commitChanges = exports.ReleaseVersion = exports.noDiffInChangelogMessage = void 0;
4
+ const chalk = require("chalk");
4
5
  const semver_1 = require("semver");
5
6
  const file_map_utils_1 = require("../../../project-graph/file-map-utils");
6
7
  const utils_1 = require("../../../tasks-runner/utils");
7
8
  const output_1 = require("../../../utils/output");
8
9
  const git_1 = require("./git");
10
+ exports.noDiffInChangelogMessage = chalk.yellow(`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`);
9
11
  function isPrerelease(version) {
10
12
  // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
11
13
  return (0, semver_1.prerelease)(version) !== null;