nx 17.0.1 → 17.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/changelog-renderer/index.d.ts +43 -0
  2. package/changelog-renderer/index.js +180 -0
  3. package/package.json +12 -13
  4. package/schemas/nx-schema.json +141 -0
  5. package/src/adapter/ngcli-adapter.js +32 -0
  6. package/src/command-line/init/implementation/add-nx-to-monorepo.js +1 -0
  7. package/src/command-line/init/implementation/add-nx-to-nest.js +1 -0
  8. package/src/command-line/init/implementation/add-nx-to-npm-repo.js +1 -0
  9. package/src/command-line/init/implementation/utils.d.ts +1 -0
  10. package/src/command-line/init/implementation/utils.js +14 -1
  11. package/src/command-line/release/changelog.js +332 -75
  12. package/src/command-line/release/command-object.d.ts +1 -3
  13. package/src/command-line/release/command-object.js +3 -17
  14. package/src/command-line/release/config/config.d.ts +1 -1
  15. package/src/command-line/release/config/config.js +153 -50
  16. package/src/command-line/release/utils/markdown.d.ts +1 -4
  17. package/src/command-line/release/utils/markdown.js +3 -136
  18. package/src/command-line/release/utils/print-changes.d.ts +5 -1
  19. package/src/command-line/release/utils/print-changes.js +3 -2
  20. package/src/command-line/show/show.js +2 -0
  21. package/src/config/nx-json.d.ts +80 -3
  22. package/src/config/nx-json.js +1 -1
  23. package/src/config/project-graph.d.ts +1 -1
  24. package/src/daemon/client/client.js +0 -6
  25. package/src/daemon/server/outputs-tracking.d.ts +2 -2
  26. package/src/daemon/server/outputs-tracking.js +2 -2
  27. package/src/daemon/server/project-graph-incremental-recomputation.js +1 -28
  28. package/src/daemon/server/server.js +22 -58
  29. package/src/daemon/server/shutdown-utils.d.ts +0 -6
  30. package/src/daemon/server/shutdown-utils.js +1 -36
  31. package/src/daemon/server/watcher.d.ts +2 -6
  32. package/src/daemon/server/watcher.js +1 -92
  33. package/src/generators/utils/project-configuration.js +10 -8
  34. package/src/migrations/update-15-0-0/prefix-outputs.js +9 -9
  35. package/src/project-graph/project-graph.js +6 -1
  36. package/src/project-graph/utils/normalize-project-nodes.js +4 -1
  37. package/src/project-graph/utils/retrieve-workspace-files.js +3 -1
  38. package/src/tasks-runner/utils.js +8 -4
  39. package/src/utils/package-manager.d.ts +1 -0
  40. package/src/utils/package-manager.js +5 -2
  41. package/src/utils/params.js +19 -4
@@ -5,40 +5,62 @@ const chalk = require("chalk");
5
5
  const node_fs_1 = require("node:fs");
6
6
  const semver_1 = require("semver");
7
7
  const tmp_1 = require("tmp");
8
+ const nx_json_1 = require("../../config/nx-json");
8
9
  const tree_1 = require("../../generators/tree");
10
+ const register_1 = require("../../plugins/js/utils/register");
11
+ const project_graph_1 = require("../../project-graph/project-graph");
12
+ const utils_1 = require("../../tasks-runner/utils");
9
13
  const logger_1 = require("../../utils/logger");
10
14
  const output_1 = require("../../utils/output");
11
15
  const path_1 = require("../../utils/path");
16
+ const typescript_1 = require("../../utils/typescript");
12
17
  const workspace_root_1 = require("../../utils/workspace-root");
18
+ const config_1 = require("./config/config");
19
+ const filter_release_groups_1 = require("./config/filter-release-groups");
13
20
  const git_1 = require("./utils/git");
14
21
  const github_1 = require("./utils/github");
15
22
  const launch_editor_1 = require("./utils/launch-editor");
16
23
  const markdown_1 = require("./utils/markdown");
17
24
  const print_changes_1 = require("./utils/print-changes");
25
+ class ReleaseVersion {
26
+ constructor({ version, // short form version string with no prefixes or patterns, e.g. 1.0.0
27
+ releaseTagPattern, // full pattern to interpolate, e.g. "v{version}" or "{projectName}@{version}"
28
+ projectName, // optional project name to interpolate into the releaseTagPattern
29
+ }) {
30
+ this.rawVersion = version;
31
+ this.gitTag = (0, utils_1.interpolate)(releaseTagPattern, {
32
+ version,
33
+ projectName,
34
+ });
35
+ this.isPrerelease = isPrerelease(version);
36
+ }
37
+ }
18
38
  async function changelogHandler(args) {
19
- /**
20
- * TODO: allow the prefix and version to be controllable via config as well once we flesh out
21
- * changelog customization, and how it will interact with independently released projects.
22
- */
23
- const tagVersionPrefix = args.tagVersionPrefix ?? 'v';
24
- // Allow the user to pass the version with or without the prefix already applied
25
- const releaseVersion = args.version.startsWith(tagVersionPrefix)
26
- ? args.version
27
- : `${tagVersionPrefix}${args.version}`;
28
- // We are either creating/previewing a changelog file, a Github release, or both
29
- let logTitle = args.dryRun ? 'Previewing a ' : 'Generating a ';
30
- switch (true) {
31
- case args.file !== false && args.createRelease === 'github':
32
- logTitle += `${args.file} entry and a Github release for ${chalk.white(releaseVersion)}`;
33
- break;
34
- case args.file !== false:
35
- logTitle += `${args.file} entry for ${chalk.white(releaseVersion)}`;
36
- break;
37
- case args.createRelease === 'github':
38
- logTitle += `Github release for ${chalk.white(releaseVersion)}`;
39
+ // Right now, the given version must be valid semver in order to proceed
40
+ if (!(0, semver_1.valid)(args.version)) {
41
+ output_1.output.error({
42
+ title: `The given version "${args.version}" is not a valid semver version. Please provide your version in the format "1.0.0", "1.0.0-beta.1" etc`,
43
+ });
44
+ process.exit(1);
39
45
  }
40
- output_1.output.log({
41
- title: logTitle,
46
+ const projectGraph = await (0, project_graph_1.createProjectGraphAsync)({ exitOnError: true });
47
+ const nxJson = (0, nx_json_1.readNxJson)();
48
+ if (args.verbose) {
49
+ process.env.NX_VERBOSE_LOGGING = 'true';
50
+ }
51
+ // Apply default configuration to any optional user configuration
52
+ const { error: configError, nxReleaseConfig } = await (0, config_1.createNxReleaseConfig)(projectGraph, nxJson.release);
53
+ if (configError) {
54
+ return await (0, config_1.handleNxReleaseConfigError)(configError);
55
+ }
56
+ const { error: filterError, releaseGroups, releaseGroupToFilteredProjects, } = (0, filter_release_groups_1.filterReleaseGroups)(projectGraph, nxReleaseConfig, args.projects, args.groups);
57
+ if (filterError) {
58
+ output_1.output.error(filterError);
59
+ process.exit(1);
60
+ }
61
+ const releaseVersion = new ReleaseVersion({
62
+ version: args.version,
63
+ releaseTagPattern: nxReleaseConfig.releaseTagPattern,
42
64
  });
43
65
  const from = args.from || (await (0, git_1.getLastGitTag)());
44
66
  if (!from) {
@@ -58,38 +80,141 @@ async function changelogHandler(args) {
58
80
  }
59
81
  return false;
60
82
  });
61
- const githubRepoSlug = args.createRelease === 'github'
62
- ? (0, github_1.getGitHubRepoSlug)(args.gitRemote)
83
+ const tree = new tree_1.FsTree(workspace_root_1.workspaceRoot, args.verbose);
84
+ await generateChangelogForWorkspace(tree, releaseVersion, !!args.dryRun,
85
+ // Only trigger interactive mode for the workspace changelog if the user explicitly requested it via "all" or "workspace"
86
+ args.interactive === 'all' || args.interactive === 'workspace', commits, nxReleaseConfig.changelog.workspaceChangelog, args.gitRemote);
87
+ if (args.projects?.length) {
88
+ /**
89
+ * Run changelog generation for all remaining release groups and filtered projects within them
90
+ */
91
+ for (const releaseGroup of releaseGroups) {
92
+ const projectNodes = Array.from(releaseGroupToFilteredProjects.get(releaseGroup)).map((name) => projectGraph.nodes[name]);
93
+ await generateChangelogForProjects(tree, args.version, !!args.dryRun,
94
+ // Only trigger interactive mode for the workspace changelog if the user explicitly requested it via "all" or "projects"
95
+ args.interactive === 'all' || args.interactive === 'projects', commits, releaseGroup.changelog, releaseGroup.releaseTagPattern, projectNodes, args.gitRemote);
96
+ }
97
+ return process.exit(0);
98
+ }
99
+ /**
100
+ * Run changelog generation for all remaining release groups
101
+ */
102
+ for (const releaseGroup of releaseGroups) {
103
+ const projectNodes = releaseGroup.projects.map((name) => projectGraph.nodes[name]);
104
+ await generateChangelogForProjects(tree, args.version, !!args.dryRun,
105
+ // Only trigger interactive mode for the workspace changelog if the user explicitly requested it via "all" or "projects"
106
+ args.interactive === 'all' || args.interactive === 'projects', commits, releaseGroup.changelog, releaseGroup.releaseTagPattern, projectNodes, args.gitRemote);
107
+ }
108
+ if (args.dryRun) {
109
+ logger_1.logger.warn(`\nNOTE: The "dryRun" flag means no changelogs were actually created.`);
110
+ }
111
+ process.exit(0);
112
+ }
113
+ exports.changelogHandler = changelogHandler;
114
+ function isPrerelease(version) {
115
+ // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
116
+ return (0, semver_1.prerelease)(version) !== null;
117
+ }
118
+ function resolveChangelogRenderer(changelogRendererPath) {
119
+ // Try and load the provided (or default) changelog renderer
120
+ let changelogRenderer;
121
+ let cleanupTranspiler = () => { };
122
+ try {
123
+ const rootTsconfigPath = (0, typescript_1.getRootTsConfigPath)();
124
+ if (rootTsconfigPath) {
125
+ cleanupTranspiler = (0, register_1.registerTsProject)(rootTsconfigPath);
126
+ }
127
+ const r = require(changelogRendererPath);
128
+ changelogRenderer = r.default || r;
129
+ }
130
+ catch {
131
+ }
132
+ finally {
133
+ cleanupTranspiler();
134
+ }
135
+ return changelogRenderer;
136
+ }
137
+ async function generateChangelogForWorkspace(tree, releaseVersion, dryRun, interactive, commits, config, gitRemote) {
138
+ // The entire feature is disabled at the workspace level, exit early
139
+ if (config === false) {
140
+ return;
141
+ }
142
+ const changelogRenderer = resolveChangelogRenderer(config.renderer);
143
+ let interpolatedTreePath = config.file || '';
144
+ if (interpolatedTreePath) {
145
+ interpolatedTreePath = (0, utils_1.interpolate)(interpolatedTreePath, {
146
+ projectName: '',
147
+ projectRoot: '',
148
+ workspaceRoot: '', // within the tree, workspaceRoot is the root
149
+ });
150
+ }
151
+ // We are either creating/previewing a changelog file, a Github release, or both
152
+ let logTitle = dryRun ? 'Previewing a' : 'Generating a';
153
+ switch (true) {
154
+ case interpolatedTreePath && config.createRelease === 'github':
155
+ logTitle += ` Github release and an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
156
+ break;
157
+ case !!interpolatedTreePath:
158
+ logTitle += `n entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
159
+ break;
160
+ case config.createRelease === 'github':
161
+ logTitle += ` Github release for ${chalk.white(releaseVersion.gitTag)}`;
162
+ }
163
+ output_1.output.log({
164
+ title: logTitle,
165
+ });
166
+ const githubRepoSlug = config.createRelease === 'github'
167
+ ? (0, github_1.getGitHubRepoSlug)(gitRemote)
63
168
  : undefined;
64
- const finalMarkdown = await resolveFinalMarkdown(args, commits, releaseVersion, githubRepoSlug);
169
+ let contents = await changelogRenderer({
170
+ commits,
171
+ releaseVersion: releaseVersion.rawVersion,
172
+ project: null,
173
+ repoSlug: githubRepoSlug,
174
+ entryWhenNoChanges: config.entryWhenNoChanges,
175
+ changelogRenderOptions: config.renderOptions,
176
+ });
177
+ /**
178
+ * If interactive mode, make the changelog contents available for the user to modify in their editor of choice,
179
+ * in a similar style to git interactive rebases/merges.
180
+ */
181
+ if (interactive) {
182
+ const tmpDir = (0, tmp_1.dirSync)().name;
183
+ const changelogPath = (0, path_1.joinPathFragments)(tmpDir,
184
+ // Include the tree path in the name so that it is easier to identify which changelog file is being edited
185
+ `PREVIEW__${interpolatedTreePath.replace(/\//g, '_')}`);
186
+ (0, node_fs_1.writeFileSync)(changelogPath, contents);
187
+ await (0, launch_editor_1.launchEditor)(changelogPath);
188
+ contents = (0, node_fs_1.readFileSync)(changelogPath, 'utf-8');
189
+ }
65
190
  /**
66
191
  * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating
67
- * a CHANGELOG.md file, a Github release, or both.
192
+ * a changelog file, a Github release, or both.
68
193
  */
69
194
  let printSummary = () => { };
70
195
  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?`);
71
- if (args.file !== false) {
72
- const tree = new tree_1.FsTree(workspace_root_1.workspaceRoot, args.verbose);
73
- let rootChangelogContents = tree.read(args.file)?.toString() ?? '';
196
+ if (interpolatedTreePath) {
197
+ let rootChangelogContents = tree.read(interpolatedTreePath)?.toString() ?? '';
74
198
  if (rootChangelogContents) {
75
- const changelogReleases = (0, markdown_1.parseChangelogMarkdown)(rootChangelogContents, args.tagVersionPrefix).releases;
76
- const existingVersionToUpdate = changelogReleases.find((r) => `${tagVersionPrefix}${r.version}` === releaseVersion);
199
+ // NOTE: right now existing releases are always expected to be in markdown format, but in the future we could potentially support others via a custom parser option
200
+ const changelogReleases = (0, markdown_1.parseChangelogMarkdown)(rootChangelogContents).releases;
201
+ const existingVersionToUpdate = changelogReleases.find((r) => r.version === releaseVersion.rawVersion);
77
202
  if (existingVersionToUpdate) {
78
- rootChangelogContents = rootChangelogContents.replace(`## ${releaseVersion}\n\n\n${existingVersionToUpdate.body}`, finalMarkdown);
203
+ rootChangelogContents = rootChangelogContents.replace(`## ${releaseVersion.rawVersion}\n\n\n${existingVersionToUpdate.body}`, contents);
79
204
  }
80
205
  else {
81
206
  // No existing version, simply prepend the new release to the top of the file
82
- rootChangelogContents = `${finalMarkdown}\n\n${rootChangelogContents}`;
207
+ rootChangelogContents = `${contents}\n\n${rootChangelogContents}`;
83
208
  }
84
209
  }
85
210
  else {
86
- // No existing changelog contents, simply create a new one using the generated markdown
87
- rootChangelogContents = finalMarkdown;
211
+ // No existing changelog contents, simply create a new one using the generated contents
212
+ rootChangelogContents = contents;
88
213
  }
89
- tree.write(args.file, rootChangelogContents);
90
- printSummary = () => (0, print_changes_1.printChanges)(tree, !!args.dryRun, 3, false, noDiffInChangelogMessage);
214
+ tree.write(interpolatedTreePath, rootChangelogContents);
215
+ printSummary = () => (0, print_changes_1.printChanges)(tree, !!dryRun, 3, false, noDiffInChangelogMessage);
91
216
  }
92
- if (args.createRelease === 'github') {
217
+ if (config.createRelease === 'github') {
93
218
  if (!githubRepoSlug) {
94
219
  output_1.output.error({
95
220
  title: `Unable to create a Github release because the Github repo slug could not be determined.`,
@@ -106,7 +231,7 @@ async function changelogHandler(args) {
106
231
  };
107
232
  let existingGithubReleaseForVersion;
108
233
  try {
109
- existingGithubReleaseForVersion = await (0, github_1.getGithubReleaseByTag)(githubRequestConfig, releaseVersion);
234
+ existingGithubReleaseForVersion = await (0, github_1.getGithubReleaseByTag)(githubRequestConfig, releaseVersion.gitTag);
110
235
  }
111
236
  catch (err) {
112
237
  if (err.response?.status === 401) {
@@ -129,58 +254,190 @@ async function changelogHandler(args) {
129
254
  }
130
255
  let existingPrintSummaryFn = printSummary;
131
256
  printSummary = () => {
132
- const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion}`;
257
+ const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
133
258
  if (existingGithubReleaseForVersion) {
134
- console.error(`${chalk.white('UPDATE')} ${logTitle}${args.dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
259
+ console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
135
260
  }
136
261
  else {
137
- console.error(`${chalk.green('CREATE')} ${logTitle}${args.dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
262
+ console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
138
263
  }
139
264
  // Only print the diff here if we are not already going to be printing changes from the Tree
140
- if (args.file === false) {
265
+ if (!interpolatedTreePath) {
141
266
  console.log('');
142
267
  (0, print_changes_1.printDiff)(existingGithubReleaseForVersion
143
268
  ? existingGithubReleaseForVersion.body
144
- : '', finalMarkdown, 3, noDiffInChangelogMessage);
269
+ : '', contents, 3, noDiffInChangelogMessage);
145
270
  }
146
271
  existingPrintSummaryFn();
147
272
  };
148
- if (!args.dryRun) {
273
+ if (!dryRun) {
149
274
  await (0, github_1.createOrUpdateGithubRelease)(githubRequestConfig, {
150
- version: releaseVersion,
151
- body: finalMarkdown,
152
- prerelease: isPrerelease(releaseVersion.replace(args.tagVersionPrefix, '')),
275
+ version: releaseVersion.gitTag,
276
+ prerelease: releaseVersion.isPrerelease,
277
+ body: contents,
153
278
  }, existingGithubReleaseForVersion);
154
279
  }
155
280
  }
156
281
  printSummary();
157
- if (args.dryRun) {
158
- logger_1.logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
159
- }
160
- process.exit(0);
161
282
  }
162
- exports.changelogHandler = changelogHandler;
163
- /**
164
- * Based on the commits available, and some optional additional user modifications,
165
- * generate the final markdown for the changelog which will be used for a CHANGELOG.md
166
- * file and/or a Github release.
167
- */
168
- async function resolveFinalMarkdown(args, commits, releaseVersion, githubRepoSlug) {
169
- let markdown = await (0, markdown_1.generateMarkdown)(commits, releaseVersion, githubRepoSlug);
170
- /**
171
- * If interactive mode, make the markdown available for the user to modify in their editor of choice,
172
- * in a similar style to git interactive rebases/merges.
173
- */
174
- if (args.interactive) {
175
- const tmpDir = (0, tmp_1.dirSync)().name;
176
- const changelogPath = (0, path_1.joinPathFragments)(tmpDir, 'c.md');
177
- (0, node_fs_1.writeFileSync)(changelogPath, markdown);
178
- await (0, launch_editor_1.launchEditor)(changelogPath);
179
- markdown = (0, node_fs_1.readFileSync)(changelogPath, 'utf-8');
283
+ async function generateChangelogForProjects(tree, rawVersion, dryRun, interactive, commits, config, releaseTagPattern, projects, gitRemote) {
284
+ // The entire feature is disabled at the project level, exit early
285
+ if (config === false) {
286
+ return;
287
+ }
288
+ const changelogRenderer = resolveChangelogRenderer(config.renderer);
289
+ for (const project of projects) {
290
+ let interpolatedTreePath = config.file || '';
291
+ if (interpolatedTreePath) {
292
+ interpolatedTreePath = (0, utils_1.interpolate)(interpolatedTreePath, {
293
+ projectName: project.name,
294
+ projectRoot: project.data.root,
295
+ workspaceRoot: '', // within the tree, workspaceRoot is the root
296
+ });
297
+ }
298
+ const releaseVersion = new ReleaseVersion({
299
+ version: rawVersion,
300
+ releaseTagPattern,
301
+ projectName: project.name,
302
+ });
303
+ // We are either creating/previewing a changelog file, a Github release, or both
304
+ let logTitle = dryRun ? 'Previewing a' : 'Generating a';
305
+ switch (true) {
306
+ case interpolatedTreePath && config.createRelease === 'github':
307
+ logTitle += ` Github release and an entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
308
+ break;
309
+ case !!interpolatedTreePath:
310
+ logTitle += `n entry in ${interpolatedTreePath} for ${chalk.white(releaseVersion.gitTag)}`;
311
+ break;
312
+ case config.createRelease === 'github':
313
+ logTitle += ` Github release for ${chalk.white(releaseVersion.gitTag)}`;
314
+ }
315
+ output_1.output.log({
316
+ title: logTitle,
317
+ });
318
+ const githubRepoSlug = config.createRelease === 'github'
319
+ ? (0, github_1.getGitHubRepoSlug)(gitRemote)
320
+ : undefined;
321
+ let contents = await changelogRenderer({
322
+ commits,
323
+ releaseVersion: releaseVersion.rawVersion,
324
+ project: null,
325
+ repoSlug: githubRepoSlug,
326
+ entryWhenNoChanges: typeof config.entryWhenNoChanges === 'string'
327
+ ? (0, utils_1.interpolate)(config.entryWhenNoChanges, {
328
+ projectName: project.name,
329
+ projectRoot: project.data.root,
330
+ workspaceRoot: '', // within the tree, workspaceRoot is the root
331
+ })
332
+ : false,
333
+ changelogRenderOptions: config.renderOptions,
334
+ });
335
+ /**
336
+ * If interactive mode, make the changelog contents available for the user to modify in their editor of choice,
337
+ * in a similar style to git interactive rebases/merges.
338
+ */
339
+ if (interactive) {
340
+ const tmpDir = (0, tmp_1.dirSync)().name;
341
+ const changelogPath = (0, path_1.joinPathFragments)(tmpDir,
342
+ // Include the tree path in the name so that it is easier to identify which changelog file is being edited
343
+ `PREVIEW__${interpolatedTreePath.replace(/\//g, '_')}`);
344
+ (0, node_fs_1.writeFileSync)(changelogPath, contents);
345
+ await (0, launch_editor_1.launchEditor)(changelogPath);
346
+ contents = (0, node_fs_1.readFileSync)(changelogPath, 'utf-8');
347
+ }
348
+ /**
349
+ * The exact logic we use for printing the summary/diff to the user is dependent upon whether they are creating
350
+ * a changelog file, a Github release, or both.
351
+ */
352
+ let printSummary = () => { };
353
+ 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?`);
354
+ if (interpolatedTreePath) {
355
+ let changelogContents = tree.read(interpolatedTreePath)?.toString() ?? '';
356
+ if (changelogContents) {
357
+ // NOTE: right now existing releases are always expected to be in markdown format, but in the future we could potentially support others via a custom parser option
358
+ const changelogReleases = (0, markdown_1.parseChangelogMarkdown)(changelogContents).releases;
359
+ const existingVersionToUpdate = changelogReleases.find((r) => r.version === releaseVersion.rawVersion);
360
+ if (existingVersionToUpdate) {
361
+ changelogContents = changelogContents.replace(`## ${releaseVersion.rawVersion}\n\n\n${existingVersionToUpdate.body}`, contents);
362
+ }
363
+ else {
364
+ // No existing version, simply prepend the new release to the top of the file
365
+ changelogContents = `${contents}\n\n${changelogContents}`;
366
+ }
367
+ }
368
+ else {
369
+ // No existing changelog contents, simply create a new one using the generated contents
370
+ changelogContents = contents;
371
+ }
372
+ tree.write(interpolatedTreePath, changelogContents);
373
+ printSummary = () => (0, print_changes_1.printChanges)(tree, !!dryRun, 3, false, noDiffInChangelogMessage,
374
+ // Only print the change for the current changelog file at this point
375
+ (f) => f.path === interpolatedTreePath);
376
+ }
377
+ if (config.createRelease === 'github') {
378
+ if (!githubRepoSlug) {
379
+ output_1.output.error({
380
+ title: `Unable to create a Github release because the Github repo slug could not be determined.`,
381
+ bodyLines: [
382
+ `Please ensure you have a valid Github remote configured. You can run \`git remote -v\` to list your current remotes.`,
383
+ ],
384
+ });
385
+ process.exit(1);
386
+ }
387
+ const token = await (0, github_1.resolveGithubToken)();
388
+ const githubRequestConfig = {
389
+ repo: githubRepoSlug,
390
+ token,
391
+ };
392
+ let existingGithubReleaseForVersion;
393
+ try {
394
+ existingGithubReleaseForVersion = await (0, github_1.getGithubReleaseByTag)(githubRequestConfig, releaseVersion.gitTag);
395
+ }
396
+ catch (err) {
397
+ if (err.response?.status === 401) {
398
+ output_1.output.error({
399
+ title: `Unable to resolve data via the Github API. You can use any of the following options to resolve this:`,
400
+ bodyLines: [
401
+ '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid Github token with `repo` scope',
402
+ '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal',
403
+ ],
404
+ });
405
+ process.exit(1);
406
+ }
407
+ if (err.response?.status === 404) {
408
+ // No existing release found, this is fine
409
+ }
410
+ else {
411
+ // Rethrow unknown errors for now
412
+ throw err;
413
+ }
414
+ }
415
+ let existingPrintSummaryFn = printSummary;
416
+ printSummary = () => {
417
+ const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
418
+ if (existingGithubReleaseForVersion) {
419
+ console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
420
+ }
421
+ else {
422
+ console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
423
+ }
424
+ // Only print the diff here if we are not already going to be printing changes from the Tree
425
+ if (!interpolatedTreePath) {
426
+ console.log('');
427
+ (0, print_changes_1.printDiff)(existingGithubReleaseForVersion
428
+ ? existingGithubReleaseForVersion.body
429
+ : '', contents, 3, noDiffInChangelogMessage);
430
+ }
431
+ existingPrintSummaryFn();
432
+ };
433
+ if (!dryRun) {
434
+ await (0, github_1.createOrUpdateGithubRelease)(githubRequestConfig, {
435
+ version: releaseVersion.gitTag,
436
+ prerelease: releaseVersion.isPrerelease,
437
+ body: contents,
438
+ }, existingGithubReleaseForVersion);
439
+ }
440
+ }
441
+ printSummary();
180
442
  }
181
- return markdown;
182
- }
183
- function isPrerelease(version) {
184
- // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
185
- return (0, semver_1.prerelease)(version) !== null;
186
443
  }
@@ -14,11 +14,9 @@ export type ChangelogOptions = NxReleaseArgs & {
14
14
  version: string;
15
15
  to: string;
16
16
  from?: string;
17
- interactive?: boolean;
17
+ interactive?: string;
18
18
  gitRemote?: string;
19
19
  tagVersionPrefix?: string;
20
- createRelease?: string;
21
- file?: string | false;
22
20
  };
23
21
  export type PublishOptions = NxReleaseArgs & RunManyOptions & {
24
22
  registry?: string;
@@ -91,7 +91,9 @@ const changelogCommand = {
91
91
  })
92
92
  .option('interactive', {
93
93
  alias: 'i',
94
- type: 'boolean',
94
+ type: 'string',
95
+ description: 'Interactively modify changelog markdown contents in your code editor before applying the changes. You can set it to be interactive for all changelogs, or only the workspace level, or only the project level',
96
+ choices: ['all', 'workspace', 'projects'],
95
97
  })
96
98
  .option('gitRemote', {
97
99
  type: 'string',
@@ -102,22 +104,6 @@ const changelogCommand = {
102
104
  type: 'string',
103
105
  description: 'Prefix to apply to the version when creating the Github release tag',
104
106
  default: 'v',
105
- })
106
- .option('createRelease', {
107
- describe: 'Create a release for the given version on a supported source control service provider, such as Github.',
108
- type: 'string',
109
- choices: ['github'],
110
- })
111
- .option('file', {
112
- type: 'string',
113
- description: 'The name of the file to write the changelog to. It can also be set to `false` to disable file generation. Defaults to CHANGELOG.md.',
114
- default: 'CHANGELOG.md',
115
- coerce: (file) => {
116
- if (file === 'false') {
117
- return false;
118
- }
119
- return file;
120
- },
121
107
  })
122
108
  .check((argv) => {
123
109
  if (!argv.version) {
@@ -34,7 +34,7 @@ export declare const CATCH_ALL_RELEASE_GROUP = "__default__";
34
34
  * pattern such as directories and globs).
35
35
  */
36
36
  export type NxReleaseConfig = DeepRequired<NxJsonConfiguration['release'] & {
37
- groups: EnsureProjectsArray<NxJsonConfiguration['release']['groups']>;
37
+ groups: DeepRequired<EnsureProjectsArray<NxJsonConfiguration['release']['groups']>>;
38
38
  }>;
39
39
  export interface CreateNxReleaseConfigError {
40
40
  code: 'RELEASE_GROUP_MATCHES_NO_PROJECTS' | 'PROJECT_MATCHES_MULTIPLE_GROUPS' | 'PROJECTS_MISSING_TARGET';