nx 19.6.0-canary.20240803-bd7a2c9 → 19.6.0-canary.20240808-333ab77

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. package/package.json +12 -12
  2. package/release/changelog-renderer/index.js +16 -1
  3. package/release/index.d.ts +1 -1
  4. package/release/index.js +2 -1
  5. package/schemas/nx-schema.json +3 -0
  6. package/src/adapter/compat.d.ts +1 -1
  7. package/src/adapter/compat.js +1 -0
  8. package/src/command-line/connect/connect-to-nx-cloud.js +7 -3
  9. package/src/command-line/release/changelog.d.ts +2 -7
  10. package/src/command-line/release/changelog.js +415 -363
  11. package/src/command-line/release/command-object.d.ts +1 -0
  12. package/src/command-line/release/command-object.js +14 -0
  13. package/src/command-line/release/config/deep-merge-json.d.ts +1 -0
  14. package/src/command-line/release/config/deep-merge-json.js +28 -0
  15. package/src/command-line/release/config/version-plans.d.ts +5 -0
  16. package/src/command-line/release/config/version-plans.js +9 -5
  17. package/src/command-line/release/index.d.ts +16 -4
  18. package/src/command-line/release/index.js +23 -9
  19. package/src/command-line/release/plan.d.ts +2 -1
  20. package/src/command-line/release/plan.js +93 -100
  21. package/src/command-line/release/publish.d.ts +2 -6
  22. package/src/command-line/release/publish.js +67 -54
  23. package/src/command-line/release/release.d.ts +2 -1
  24. package/src/command-line/release/release.js +181 -165
  25. package/src/command-line/release/utils/generate-version-plan-content.js +2 -3
  26. package/src/command-line/release/utils/print-config.d.ts +7 -0
  27. package/src/command-line/release/utils/print-config.js +36 -0
  28. package/src/command-line/release/version.d.ts +7 -6
  29. package/src/command-line/release/version.js +179 -165
  30. package/src/config/nx-json.d.ts +6 -1
  31. package/src/devkit-internals.d.ts +2 -2
  32. package/src/devkit-internals.js +2 -2
  33. package/src/native/nx.wasm32-wasi.wasm +0 -0
  34. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.d.ts +2 -1
  35. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.js +49 -10
  36. package/src/nx-cloud/nx-cloud-tasks-runner-shell.d.ts +1 -0
  37. package/src/nx-cloud/utilities/axios.js +9 -2
  38. package/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +7 -2
  39. package/src/plugins/package-json/create-nodes.js +9 -1
  40. package/src/project-graph/plugins/isolation/plugin-pool.js +32 -10
  41. package/src/tasks-runner/run-command.js +6 -1
  42. package/src/tasks-runner/utils.js +14 -10
  43. package/src/utils/nx-cloud-utils.js +3 -1
  44. package/src/utils/package-manager.js +12 -3
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.releaseChangelogCLIHandler = void 0;
4
- exports.releaseChangelog = releaseChangelog;
4
+ exports.createAPI = createAPI;
5
5
  exports.shouldCreateGitHubRelease = shouldCreateGitHubRelease;
6
6
  const chalk = require("chalk");
7
7
  const enquirer_1 = require("enquirer");
@@ -20,6 +20,7 @@ const params_1 = require("../../utils/params");
20
20
  const path_1 = require("../../utils/path");
21
21
  const workspace_root_1 = require("../../utils/workspace-root");
22
22
  const config_1 = require("./config/config");
23
+ const deep_merge_json_1 = require("./config/deep-merge-json");
23
24
  const filter_release_groups_1 = require("./config/filter-release-groups");
24
25
  const version_plans_1 = require("./config/version-plans");
25
26
  const git_1 = require("./utils/git");
@@ -27,295 +28,424 @@ const github_1 = require("./utils/github");
27
28
  const launch_editor_1 = require("./utils/launch-editor");
28
29
  const markdown_1 = require("./utils/markdown");
29
30
  const print_changes_1 = require("./utils/print-changes");
31
+ const print_config_1 = require("./utils/print-config");
30
32
  const resolve_changelog_renderer_1 = require("./utils/resolve-changelog-renderer");
31
33
  const resolve_nx_json_error_message_1 = require("./utils/resolve-nx-json-error-message");
32
34
  const shared_1 = require("./utils/shared");
33
- const releaseChangelogCLIHandler = (args) => (0, params_1.handleErrors)(args.verbose, () => releaseChangelog(args));
35
+ const releaseChangelogCLIHandler = (args) => (0, params_1.handleErrors)(args.verbose, () => createAPI({})(args));
34
36
  exports.releaseChangelogCLIHandler = releaseChangelogCLIHandler;
35
- /**
36
- * NOTE: This function is also exported for programmatic usage and forms part of the public API
37
- * of Nx. We intentionally do not wrap the implementation with handleErrors because users need
38
- * to have control over their own error handling when using the API.
39
- */
40
- async function releaseChangelog(args) {
41
- const projectGraph = await (0, project_graph_1.createProjectGraphAsync)({ exitOnError: true });
42
- const nxJson = (0, nx_json_1.readNxJson)();
43
- if (args.verbose) {
44
- process.env.NX_VERBOSE_LOGGING = 'true';
45
- }
46
- // Apply default configuration to any optional user configuration
47
- const { error: configError, nxReleaseConfig } = await (0, config_1.createNxReleaseConfig)(projectGraph, await (0, file_map_utils_1.createProjectFileMapUsingProjectGraph)(projectGraph), nxJson.release);
48
- if (configError) {
49
- return await (0, config_1.handleNxReleaseConfigError)(configError);
50
- }
51
- // The nx release top level command will always override these three git args. This is how we can tell
52
- // if the top level release command was used or if the user is using the changelog subcommand.
53
- // If the user explicitly overrides these args, then it doesn't matter if the top level config is set,
54
- // as all of the git options would be overridden anyway.
55
- if ((args.gitCommit === undefined ||
56
- args.gitTag === undefined ||
57
- args.stageChanges === undefined) &&
58
- nxJson.release?.git) {
59
- const nxJsonMessage = await (0, resolve_nx_json_error_message_1.resolveNxJsonConfigErrorMessage)([
60
- 'release',
61
- 'git',
62
- ]);
63
- output_1.output.error({
64
- title: `The "release.git" property in nx.json may not be used with the "nx release changelog" subcommand or programmatic API. Instead, configure git options for subcommands directly with "release.version.git" and "release.changelog.git".`,
65
- bodyLines: [nxJsonMessage],
66
- });
67
- process.exit(1);
68
- }
69
- const { error: filterError, releaseGroups, releaseGroupToFilteredProjects, } = (0, filter_release_groups_1.filterReleaseGroups)(projectGraph, nxReleaseConfig, args.projects, args.groups);
70
- if (filterError) {
71
- output_1.output.error(filterError);
72
- process.exit(1);
73
- }
74
- const rawVersionPlans = await (0, version_plans_1.readRawVersionPlans)();
75
- (0, version_plans_1.setVersionPlansOnGroups)(rawVersionPlans, releaseGroups, Object.keys(projectGraph.nodes));
76
- if (args.deleteVersionPlans === undefined) {
77
- // default to deleting version plans in this command instead of after versioning
78
- args.deleteVersionPlans = true;
79
- }
80
- const changelogGenerationEnabled = !!nxReleaseConfig.changelog.workspaceChangelog ||
81
- Object.values(nxReleaseConfig.groups).some((g) => g.changelog);
82
- if (!changelogGenerationEnabled) {
83
- output_1.output.warn({
84
- title: `Changelogs are disabled. No changelog entries will be generated`,
85
- bodyLines: [
86
- `To explicitly enable changelog generation, configure "release.changelog.workspaceChangelog" or "release.changelog.projectChangelogs" in nx.json.`,
87
- ],
88
- });
89
- return {};
90
- }
91
- const tree = new tree_1.FsTree(workspace_root_1.workspaceRoot, args.verbose);
92
- const useAutomaticFromRef = nxReleaseConfig.changelog?.automaticFromRef || args.firstRelease;
37
+ function createAPI(overrideReleaseConfig) {
93
38
  /**
94
- * For determining the versions to use within changelog files, there are a few different possibilities:
95
- * - the user is using the nx CLI, and therefore passes a single --version argument which represents the version for any and all changelog
96
- * files which will be generated (i.e. both the workspace changelog, and all project changelogs, depending on which of those has been enabled)
97
- * - the user is using the nxReleaseChangelog API programmatically, and:
98
- * - passes only a version property
99
- * - this works in the same way as described above for the CLI
100
- * - passes only a versionData object
101
- * - this is a special case where the user is providing a version for each project, and therefore the version argument is not needed
102
- * - NOTE: it is not possible to generate a workspace level changelog with only a versionData object, and this will produce an error
103
- * - passes both a version and a versionData object
104
- * - in this case, the version property will be used as the reference for the workspace changelog, and the versionData object will be used
105
- * to generate project changelogs
39
+ * NOTE: This function is also exported for programmatic usage and forms part of the public API
40
+ * of Nx. We intentionally do not wrap the implementation with handleErrors because users need
41
+ * to have control over their own error handling when using the API.
106
42
  */
107
- const { workspaceChangelogVersion, projectsVersionData } = resolveChangelogVersions(args, releaseGroups, releaseGroupToFilteredProjects);
108
- const to = args.to || 'HEAD';
109
- const toSHA = await (0, git_1.getCommitHash)(to);
110
- const headSHA = to === 'HEAD' ? toSHA : await (0, git_1.getCommitHash)('HEAD');
111
- /**
112
- * Protect the user against attempting to create a new commit when recreating an old release changelog,
113
- * this seems like it would always be unintentional.
114
- */
115
- const autoCommitEnabled = args.gitCommit ?? nxReleaseConfig.changelog.git.commit;
116
- if (autoCommitEnabled && headSHA !== toSHA) {
117
- throw new Error(`You are attempting to recreate the changelog for an old release, but you have enabled auto-commit mode. Please disable auto-commit mode by updating your nx.json, or passing --git-commit=false`);
118
- }
119
- const commitMessage = args.gitCommitMessage || nxReleaseConfig.changelog.git.commitMessage;
120
- const commitMessageValues = (0, shared_1.createCommitMessageValues)(releaseGroups, releaseGroupToFilteredProjects, projectsVersionData, commitMessage);
121
- // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command
122
- const gitTagValues = args.gitTag ?? nxReleaseConfig.changelog.git.tag
123
- ? (0, shared_1.createGitTagValues)(releaseGroups, releaseGroupToFilteredProjects, projectsVersionData)
124
- : [];
125
- (0, shared_1.handleDuplicateGitTags)(gitTagValues);
126
- const postGitTasks = [];
127
- let workspaceChangelogChanges = [];
128
- // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
129
- let workspaceChangelogCommits = [];
130
- // If there are multiple release groups, we'll just skip the workspace changelog anyway.
131
- const versionPlansEnabledForWorkspaceChangelog = releaseGroups[0].versionPlans;
132
- if (versionPlansEnabledForWorkspaceChangelog) {
133
- if (releaseGroups.length === 1) {
134
- const releaseGroup = releaseGroups[0];
135
- if (releaseGroup.projectsRelationship === 'fixed') {
136
- const versionPlans = releaseGroup.versionPlans;
137
- workspaceChangelogChanges = filterHiddenChanges(versionPlans
138
- .map((vp) => {
139
- const parsedMessage = (0, git_1.parseConventionalCommitsMessage)(vp.message);
140
- // only properly formatted conventional commits messages will be included in the changelog
141
- if (!parsedMessage) {
142
- return null;
143
- }
144
- return {
145
- type: parsedMessage.type,
146
- scope: parsedMessage.scope,
147
- description: parsedMessage.description,
148
- body: '',
149
- isBreaking: parsedMessage.breaking,
150
- githubReferences: [],
151
- };
152
- })
153
- .filter(Boolean), nxReleaseConfig.conventionalCommits);
154
- }
43
+ return async function releaseChangelog(args) {
44
+ const projectGraph = await (0, project_graph_1.createProjectGraphAsync)({ exitOnError: true });
45
+ const nxJson = (0, nx_json_1.readNxJson)();
46
+ const userProvidedReleaseConfig = (0, deep_merge_json_1.deepMergeJson)(nxJson.release ?? {}, overrideReleaseConfig ?? {});
47
+ if (args.verbose) {
48
+ process.env.NX_VERBOSE_LOGGING = 'true';
155
49
  }
156
- }
157
- else {
158
- let workspaceChangelogFromRef = args.from ||
159
- (await (0, git_1.getLatestGitTagForPattern)(nxReleaseConfig.releaseTagPattern))?.tag;
160
- if (!workspaceChangelogFromRef) {
161
- if (useAutomaticFromRef) {
162
- workspaceChangelogFromRef = await (0, git_1.getFirstGitCommit)();
163
- if (args.verbose) {
164
- console.log(`Determined workspace --from ref from the first commit in the workspace: ${workspaceChangelogFromRef}`);
165
- }
166
- }
167
- else {
168
- throw new Error(`Unable to determine the previous git tag. If this is the first release of your workspace, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
169
- }
50
+ // Apply default configuration to any optional user configuration
51
+ const { error: configError, nxReleaseConfig } = await (0, config_1.createNxReleaseConfig)(projectGraph, await (0, file_map_utils_1.createProjectFileMapUsingProjectGraph)(projectGraph), userProvidedReleaseConfig);
52
+ if (configError) {
53
+ return await (0, config_1.handleNxReleaseConfigError)(configError);
170
54
  }
171
- // Make sure that the fromRef is actually resolvable
172
- const workspaceChangelogFromSHA = await (0, git_1.getCommitHash)(workspaceChangelogFromRef);
173
- workspaceChangelogCommits = await getCommits(workspaceChangelogFromSHA, toSHA);
174
- workspaceChangelogChanges = filterHiddenChanges(workspaceChangelogCommits.map((c) => {
175
- return {
176
- type: c.type,
177
- scope: c.scope,
178
- description: c.description,
179
- body: c.body,
180
- isBreaking: c.isBreaking,
181
- githubReferences: c.references,
182
- author: c.author,
183
- shortHash: c.shortHash,
184
- revertedHashes: c.revertedHashes,
185
- affectedProjects: '*',
186
- };
187
- }), nxReleaseConfig.conventionalCommits);
188
- }
189
- const workspaceChangelog = await generateChangelogForWorkspace({
190
- tree,
191
- args,
192
- projectGraph,
193
- nxReleaseConfig,
194
- workspaceChangelogVersion,
195
- changes: workspaceChangelogChanges,
55
+ // --print-config exits directly as it is not designed to be combined with any other programmatic operations
56
+ if (args.printConfig) {
57
+ return (0, print_config_1.printConfigAndExit)({
58
+ userProvidedReleaseConfig,
59
+ nxReleaseConfig,
60
+ isDebug: args.printConfig === 'debug',
61
+ });
62
+ }
63
+ // The nx release top level command will always override these three git args. This is how we can tell
64
+ // if the top level release command was used or if the user is using the changelog subcommand.
65
+ // If the user explicitly overrides these args, then it doesn't matter if the top level config is set,
66
+ // as all of the git options would be overridden anyway.
67
+ if ((args.gitCommit === undefined ||
68
+ args.gitTag === undefined ||
69
+ args.stageChanges === undefined) &&
70
+ userProvidedReleaseConfig.git) {
71
+ const nxJsonMessage = await (0, resolve_nx_json_error_message_1.resolveNxJsonConfigErrorMessage)([
72
+ 'release',
73
+ 'git',
74
+ ]);
75
+ output_1.output.error({
76
+ title: `The "release.git" property in nx.json may not be used with the "nx release changelog" subcommand or programmatic API. Instead, configure git options for subcommands directly with "release.version.git" and "release.changelog.git".`,
77
+ bodyLines: [nxJsonMessage],
78
+ });
79
+ process.exit(1);
80
+ }
81
+ const { error: filterError, releaseGroups, releaseGroupToFilteredProjects, } = (0, filter_release_groups_1.filterReleaseGroups)(projectGraph, nxReleaseConfig, args.projects, args.groups);
82
+ if (filterError) {
83
+ output_1.output.error(filterError);
84
+ process.exit(1);
85
+ }
86
+ const rawVersionPlans = await (0, version_plans_1.readRawVersionPlans)();
87
+ (0, version_plans_1.setVersionPlansOnGroups)(rawVersionPlans, releaseGroups, Object.keys(projectGraph.nodes));
88
+ if (args.deleteVersionPlans === undefined) {
89
+ // default to deleting version plans in this command instead of after versioning
90
+ args.deleteVersionPlans = true;
91
+ }
92
+ const changelogGenerationEnabled = !!nxReleaseConfig.changelog.workspaceChangelog ||
93
+ Object.values(nxReleaseConfig.groups).some((g) => g.changelog);
94
+ if (!changelogGenerationEnabled) {
95
+ output_1.output.warn({
96
+ title: `Changelogs are disabled. No changelog entries will be generated`,
97
+ bodyLines: [
98
+ `To explicitly enable changelog generation, configure "release.changelog.workspaceChangelog" or "release.changelog.projectChangelogs" in nx.json.`,
99
+ ],
100
+ });
101
+ return {};
102
+ }
103
+ const tree = new tree_1.FsTree(workspace_root_1.workspaceRoot, args.verbose);
104
+ const useAutomaticFromRef = nxReleaseConfig.changelog?.automaticFromRef || args.firstRelease;
105
+ /**
106
+ * For determining the versions to use within changelog files, there are a few different possibilities:
107
+ * - the user is using the nx CLI, and therefore passes a single --version argument which represents the version for any and all changelog
108
+ * files which will be generated (i.e. both the workspace changelog, and all project changelogs, depending on which of those has been enabled)
109
+ * - the user is using the nxReleaseChangelog API programmatically, and:
110
+ * - passes only a version property
111
+ * - this works in the same way as described above for the CLI
112
+ * - passes only a versionData object
113
+ * - this is a special case where the user is providing a version for each project, and therefore the version argument is not needed
114
+ * - NOTE: it is not possible to generate a workspace level changelog with only a versionData object, and this will produce an error
115
+ * - passes both a version and a versionData object
116
+ * - in this case, the version property will be used as the reference for the workspace changelog, and the versionData object will be used
117
+ * to generate project changelogs
118
+ */
119
+ const { workspaceChangelogVersion, projectsVersionData } = resolveChangelogVersions(args, releaseGroups, releaseGroupToFilteredProjects);
120
+ const to = args.to || 'HEAD';
121
+ const toSHA = await (0, git_1.getCommitHash)(to);
122
+ const headSHA = to === 'HEAD' ? toSHA : await (0, git_1.getCommitHash)('HEAD');
123
+ /**
124
+ * Protect the user against attempting to create a new commit when recreating an old release changelog,
125
+ * this seems like it would always be unintentional.
126
+ */
127
+ const autoCommitEnabled = args.gitCommit ?? nxReleaseConfig.changelog.git.commit;
128
+ if (autoCommitEnabled && headSHA !== toSHA) {
129
+ throw new Error(`You are attempting to recreate the changelog for an old release, but you have enabled auto-commit mode. Please disable auto-commit mode by updating your nx.json, or passing --git-commit=false`);
130
+ }
131
+ const commitMessage = args.gitCommitMessage || nxReleaseConfig.changelog.git.commitMessage;
132
+ const commitMessageValues = (0, shared_1.createCommitMessageValues)(releaseGroups, releaseGroupToFilteredProjects, projectsVersionData, commitMessage);
133
+ // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command
134
+ const gitTagValues = args.gitTag ?? nxReleaseConfig.changelog.git.tag
135
+ ? (0, shared_1.createGitTagValues)(releaseGroups, releaseGroupToFilteredProjects, projectsVersionData)
136
+ : [];
137
+ (0, shared_1.handleDuplicateGitTags)(gitTagValues);
138
+ const postGitTasks = [];
139
+ let workspaceChangelogChanges = [];
196
140
  // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
197
- commits: filterHiddenCommits(workspaceChangelogCommits, nxReleaseConfig.conventionalCommits),
198
- });
199
- if (workspaceChangelog &&
200
- shouldCreateGitHubRelease(nxReleaseConfig.changelog.workspaceChangelog, args.createRelease)) {
201
- let hasPushed = false;
202
- postGitTasks.push(async (latestCommit) => {
203
- if (!hasPushed) {
204
- output_1.output.logSingleLine(`Pushing to git remote`);
205
- // Before we can create/update the release we need to ensure the commit exists on the remote
206
- await (0, git_1.gitPush)({
207
- gitRemote: args.gitRemote,
208
- dryRun: args.dryRun,
209
- verbose: args.verbose,
210
- });
211
- hasPushed = true;
141
+ let workspaceChangelogCommits = [];
142
+ // If there are multiple release groups, we'll just skip the workspace changelog anyway.
143
+ const versionPlansEnabledForWorkspaceChangelog = releaseGroups[0].versionPlans;
144
+ if (versionPlansEnabledForWorkspaceChangelog) {
145
+ if (releaseGroups.length === 1) {
146
+ const releaseGroup = releaseGroups[0];
147
+ if (releaseGroup.projectsRelationship === 'fixed') {
148
+ const versionPlans = releaseGroup.versionPlans;
149
+ workspaceChangelogChanges = versionPlans
150
+ .flatMap((vp) => {
151
+ const releaseType = versionPlanSemverReleaseTypeToChangelogType(vp.groupVersionBump);
152
+ const changes = !vp.triggeredByProjects
153
+ ? {
154
+ type: releaseType.type,
155
+ scope: '',
156
+ description: vp.message,
157
+ body: '',
158
+ isBreaking: releaseType.isBreaking,
159
+ githubReferences: [],
160
+ affectedProjects: '*',
161
+ }
162
+ : vp.triggeredByProjects.map((project) => {
163
+ return {
164
+ type: releaseType.type,
165
+ scope: project,
166
+ description: vp.message,
167
+ body: '',
168
+ // TODO: what about github references?
169
+ isBreaking: releaseType.isBreaking,
170
+ githubReferences: [],
171
+ affectedProjects: [project],
172
+ };
173
+ });
174
+ return changes;
175
+ })
176
+ .filter(Boolean);
177
+ }
212
178
  }
213
- output_1.output.logSingleLine(`Creating GitHub Release`);
214
- await (0, github_1.createOrUpdateGithubRelease)(workspaceChangelog.releaseVersion, workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
215
- });
216
- }
217
- /**
218
- * Compute any additional dependency bumps up front because there could be cases of circular dependencies,
219
- * and figuring them out during the main iteration would be too late.
220
- */
221
- const projectToAdditionalDependencyBumps = new Map();
222
- for (const releaseGroup of releaseGroups) {
223
- if (releaseGroup.projectsRelationship !== 'independent') {
224
- continue;
225
179
  }
226
- for (const project of releaseGroup.projects) {
227
- // If the project does not have any changes, do not process its dependents
228
- if (!projectsVersionData[project] ||
229
- projectsVersionData[project].newVersion === null) {
230
- continue;
180
+ else {
181
+ let workspaceChangelogFromRef = args.from ||
182
+ (await (0, git_1.getLatestGitTagForPattern)(nxReleaseConfig.releaseTagPattern))
183
+ ?.tag;
184
+ if (!workspaceChangelogFromRef) {
185
+ if (useAutomaticFromRef) {
186
+ workspaceChangelogFromRef = await (0, git_1.getFirstGitCommit)();
187
+ if (args.verbose) {
188
+ console.log(`Determined workspace --from ref from the first commit in the workspace: ${workspaceChangelogFromRef}`);
189
+ }
190
+ }
191
+ else {
192
+ throw new Error(`Unable to determine the previous git tag. If this is the first release of your workspace, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
193
+ }
231
194
  }
232
- const dependentProjects = (projectsVersionData[project].dependentProjects || [])
233
- .map((dep) => {
195
+ // Make sure that the fromRef is actually resolvable
196
+ const workspaceChangelogFromSHA = await (0, git_1.getCommitHash)(workspaceChangelogFromRef);
197
+ workspaceChangelogCommits = await getCommits(workspaceChangelogFromSHA, toSHA);
198
+ workspaceChangelogChanges = filterHiddenChanges(workspaceChangelogCommits.map((c) => {
234
199
  return {
235
- dependencyName: dep.source,
236
- newVersion: projectsVersionData[dep.source].newVersion,
200
+ type: c.type,
201
+ scope: c.scope,
202
+ description: c.description,
203
+ body: c.body,
204
+ isBreaking: c.isBreaking,
205
+ githubReferences: c.references,
206
+ author: c.author,
207
+ shortHash: c.shortHash,
208
+ revertedHashes: c.revertedHashes,
209
+ affectedProjects: '*',
237
210
  };
238
- })
239
- .filter((b) => b.newVersion !== null);
240
- for (const dependent of dependentProjects) {
241
- const additionalDependencyBumpsForProject = projectToAdditionalDependencyBumps.has(dependent.dependencyName)
242
- ? projectToAdditionalDependencyBumps.get(dependent.dependencyName)
243
- : [];
244
- additionalDependencyBumpsForProject.push({
245
- dependencyName: project,
246
- newVersion: projectsVersionData[project].newVersion,
247
- });
248
- projectToAdditionalDependencyBumps.set(dependent.dependencyName, additionalDependencyBumpsForProject);
249
- }
211
+ }), nxReleaseConfig.conventionalCommits);
250
212
  }
251
- }
252
- const allProjectChangelogs = {};
253
- for (const releaseGroup of releaseGroups) {
254
- const config = releaseGroup.changelog;
255
- // The entire feature is disabled at the release group level, exit early
256
- if (config === false) {
257
- continue;
213
+ const workspaceChangelog = await generateChangelogForWorkspace({
214
+ tree,
215
+ args,
216
+ projectGraph,
217
+ nxReleaseConfig,
218
+ workspaceChangelogVersion,
219
+ changes: workspaceChangelogChanges,
220
+ // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
221
+ commits: filterHiddenCommits(workspaceChangelogCommits, nxReleaseConfig.conventionalCommits),
222
+ });
223
+ if (workspaceChangelog &&
224
+ shouldCreateGitHubRelease(nxReleaseConfig.changelog.workspaceChangelog, args.createRelease)) {
225
+ let hasPushed = false;
226
+ postGitTasks.push(async (latestCommit) => {
227
+ if (!hasPushed) {
228
+ output_1.output.logSingleLine(`Pushing to git remote`);
229
+ // Before we can create/update the release we need to ensure the commit exists on the remote
230
+ await (0, git_1.gitPush)({
231
+ gitRemote: args.gitRemote,
232
+ dryRun: args.dryRun,
233
+ verbose: args.verbose,
234
+ });
235
+ hasPushed = true;
236
+ }
237
+ output_1.output.logSingleLine(`Creating GitHub Release`);
238
+ await (0, github_1.createOrUpdateGithubRelease)(workspaceChangelog.releaseVersion, workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
239
+ });
258
240
  }
259
- const projects = args.projects?.length
260
- ? // If the user has passed a list of projects, we need to use the filtered list of projects within the release group, plus any dependents
261
- Array.from(releaseGroupToFilteredProjects.get(releaseGroup)).flatMap((project) => {
262
- return [
263
- project,
264
- ...(projectsVersionData[project]?.dependentProjects.map((dep) => dep.source) || []),
265
- ];
241
+ /**
242
+ * Compute any additional dependency bumps up front because there could be cases of circular dependencies,
243
+ * and figuring them out during the main iteration would be too late.
244
+ */
245
+ const projectToAdditionalDependencyBumps = new Map();
246
+ for (const releaseGroup of releaseGroups) {
247
+ if (releaseGroup.projectsRelationship !== 'independent') {
248
+ continue;
249
+ }
250
+ for (const project of releaseGroup.projects) {
251
+ // If the project does not have any changes, do not process its dependents
252
+ if (!projectsVersionData[project] ||
253
+ projectsVersionData[project].newVersion === null) {
254
+ continue;
255
+ }
256
+ const dependentProjects = (projectsVersionData[project].dependentProjects || [])
257
+ .map((dep) => {
258
+ return {
259
+ dependencyName: dep.source,
260
+ newVersion: projectsVersionData[dep.source].newVersion,
261
+ };
266
262
  })
267
- : // Otherwise, we use the full list of projects within the release group
268
- releaseGroup.projects;
269
- const projectNodes = projects.map((name) => projectGraph.nodes[name]);
270
- if (releaseGroup.projectsRelationship === 'independent') {
271
- for (const project of projectNodes) {
272
- let changes = null;
263
+ .filter((b) => b.newVersion !== null);
264
+ for (const dependent of dependentProjects) {
265
+ const additionalDependencyBumpsForProject = projectToAdditionalDependencyBumps.has(dependent.dependencyName)
266
+ ? projectToAdditionalDependencyBumps.get(dependent.dependencyName)
267
+ : [];
268
+ additionalDependencyBumpsForProject.push({
269
+ dependencyName: project,
270
+ newVersion: projectsVersionData[project].newVersion,
271
+ });
272
+ projectToAdditionalDependencyBumps.set(dependent.dependencyName, additionalDependencyBumpsForProject);
273
+ }
274
+ }
275
+ }
276
+ const allProjectChangelogs = {};
277
+ for (const releaseGroup of releaseGroups) {
278
+ const config = releaseGroup.changelog;
279
+ // The entire feature is disabled at the release group level, exit early
280
+ if (config === false) {
281
+ continue;
282
+ }
283
+ const projects = args.projects?.length
284
+ ? // If the user has passed a list of projects, we need to use the filtered list of projects within the release group, plus any dependents
285
+ Array.from(releaseGroupToFilteredProjects.get(releaseGroup)).flatMap((project) => {
286
+ return [
287
+ project,
288
+ ...(projectsVersionData[project]?.dependentProjects.map((dep) => dep.source) || []),
289
+ ];
290
+ })
291
+ : // Otherwise, we use the full list of projects within the release group
292
+ releaseGroup.projects;
293
+ const projectNodes = projects.map((name) => projectGraph.nodes[name]);
294
+ if (releaseGroup.projectsRelationship === 'independent') {
295
+ for (const project of projectNodes) {
296
+ let changes = null;
297
+ // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
298
+ let commits;
299
+ if (releaseGroup.versionPlans) {
300
+ changes = releaseGroup.versionPlans
301
+ .map((vp) => {
302
+ const bumpForProject = vp.projectVersionBumps[project.name];
303
+ if (!bumpForProject) {
304
+ return null;
305
+ }
306
+ const releaseType = versionPlanSemverReleaseTypeToChangelogType(bumpForProject);
307
+ return {
308
+ type: releaseType.type,
309
+ scope: project.name,
310
+ description: vp.message,
311
+ body: '',
312
+ isBreaking: releaseType.isBreaking,
313
+ affectedProjects: Object.keys(vp.projectVersionBumps),
314
+ // TODO: can we include github references when using version plans?
315
+ githubReferences: [],
316
+ };
317
+ })
318
+ .filter(Boolean);
319
+ }
320
+ else {
321
+ let fromRef = args.from ||
322
+ (await (0, git_1.getLatestGitTagForPattern)(releaseGroup.releaseTagPattern, {
323
+ projectName: project.name,
324
+ releaseGroupName: releaseGroup.name,
325
+ }))?.tag;
326
+ if (!fromRef && useAutomaticFromRef) {
327
+ const firstCommit = await (0, git_1.getFirstGitCommit)();
328
+ const allCommits = await getCommits(firstCommit, toSHA);
329
+ const commitsForProject = allCommits.filter((c) => c.affectedFiles.find((f) => f.startsWith(project.data.root)));
330
+ fromRef = commitsForProject[0]?.shortHash;
331
+ if (args.verbose) {
332
+ console.log(`Determined --from ref for ${project.name} from the first commit in which it exists: ${fromRef}`);
333
+ }
334
+ commits = commitsForProject;
335
+ }
336
+ if (!fromRef && !commits) {
337
+ throw new Error(`Unable to determine the previous git tag. If this is the first release of your workspace, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
338
+ }
339
+ if (!commits) {
340
+ commits = await getCommits(fromRef, toSHA);
341
+ }
342
+ const { fileMap } = await (0, file_map_utils_1.createFileMapUsingProjectGraph)(projectGraph);
343
+ const fileToProjectMap = createFileToProjectMap(fileMap.projectFileMap);
344
+ changes = filterHiddenChanges(commits.map((c) => ({
345
+ type: c.type,
346
+ scope: c.scope,
347
+ description: c.description,
348
+ body: c.body,
349
+ isBreaking: c.isBreaking,
350
+ githubReferences: c.references,
351
+ author: c.author,
352
+ shortHash: c.shortHash,
353
+ revertedHashes: c.revertedHashes,
354
+ affectedProjects: commitChangesNonProjectFiles(c, fileMap.nonProjectFiles)
355
+ ? '*'
356
+ : getProjectsAffectedByCommit(c, fileToProjectMap),
357
+ })), nxReleaseConfig.conventionalCommits);
358
+ }
359
+ const projectChangelogs = await generateChangelogForProjects({
360
+ tree,
361
+ args,
362
+ projectGraph,
363
+ changes,
364
+ projectsVersionData,
365
+ releaseGroup,
366
+ projects: [project],
367
+ nxReleaseConfig,
368
+ projectToAdditionalDependencyBumps,
369
+ // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
370
+ commits: filterHiddenCommits(commits, nxReleaseConfig.conventionalCommits),
371
+ });
372
+ let hasPushed = false;
373
+ for (const [projectName, projectChangelog] of Object.entries(projectChangelogs)) {
374
+ if (projectChangelogs &&
375
+ shouldCreateGitHubRelease(releaseGroup.changelog, args.createRelease)) {
376
+ postGitTasks.push(async (latestCommit) => {
377
+ if (!hasPushed) {
378
+ output_1.output.logSingleLine(`Pushing to git remote`);
379
+ // Before we can create/update the release we need to ensure the commit exists on the remote
380
+ await (0, git_1.gitPush)({
381
+ gitRemote: args.gitRemote,
382
+ dryRun: args.dryRun,
383
+ verbose: args.verbose,
384
+ });
385
+ hasPushed = true;
386
+ }
387
+ output_1.output.logSingleLine(`Creating GitHub Release`);
388
+ await (0, github_1.createOrUpdateGithubRelease)(projectChangelog.releaseVersion, projectChangelog.contents, latestCommit, { dryRun: args.dryRun });
389
+ });
390
+ }
391
+ allProjectChangelogs[projectName] = projectChangelog;
392
+ }
393
+ }
394
+ }
395
+ else {
396
+ let changes = [];
273
397
  // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
274
- let commits;
398
+ let commits = [];
275
399
  if (releaseGroup.versionPlans) {
276
- changes = filterHiddenChanges(releaseGroup.versionPlans
277
- .map((vp) => {
278
- const parsedMessage = (0, git_1.parseConventionalCommitsMessage)(vp.message);
279
- // only properly formatted conventional commits messages will be included in the changelog
280
- if (!parsedMessage) {
281
- return null;
282
- }
283
- return {
284
- type: parsedMessage.type,
285
- scope: parsedMessage.scope,
286
- description: parsedMessage.description,
287
- body: '',
288
- isBreaking: parsedMessage.breaking,
289
- affectedProjects: Object.keys(vp.projectVersionBumps),
290
- githubReferences: [],
291
- };
400
+ changes = releaseGroup.versionPlans
401
+ .flatMap((vp) => {
402
+ const releaseType = versionPlanSemverReleaseTypeToChangelogType(vp.groupVersionBump);
403
+ const changes = !vp.triggeredByProjects
404
+ ? {
405
+ type: releaseType.type,
406
+ scope: '',
407
+ description: vp.message,
408
+ body: '',
409
+ isBreaking: releaseType.isBreaking,
410
+ githubReferences: [],
411
+ affectedProjects: '*',
412
+ }
413
+ : vp.triggeredByProjects.map((project) => {
414
+ return {
415
+ type: releaseType.type,
416
+ scope: project,
417
+ description: vp.message,
418
+ body: '',
419
+ // TODO: what about github references?
420
+ isBreaking: releaseType.isBreaking,
421
+ githubReferences: [],
422
+ affectedProjects: [project],
423
+ };
424
+ });
425
+ return changes;
292
426
  })
293
- .filter(Boolean), nxReleaseConfig.conventionalCommits);
427
+ .filter(Boolean);
294
428
  }
295
429
  else {
296
430
  let fromRef = args.from ||
297
- (await (0, git_1.getLatestGitTagForPattern)(releaseGroup.releaseTagPattern, {
298
- projectName: project.name,
299
- releaseGroupName: releaseGroup.name,
300
- }))?.tag;
301
- if (!fromRef && useAutomaticFromRef) {
302
- const firstCommit = await (0, git_1.getFirstGitCommit)();
303
- const allCommits = await getCommits(firstCommit, toSHA);
304
- const commitsForProject = allCommits.filter((c) => c.affectedFiles.find((f) => f.startsWith(project.data.root)));
305
- fromRef = commitsForProject[0]?.shortHash;
306
- if (args.verbose) {
307
- console.log(`Determined --from ref for ${project.name} from the first commit in which it exists: ${fromRef}`);
431
+ (await (0, git_1.getLatestGitTagForPattern)(releaseGroup.releaseTagPattern))
432
+ ?.tag;
433
+ if (!fromRef) {
434
+ if (useAutomaticFromRef) {
435
+ fromRef = await (0, git_1.getFirstGitCommit)();
436
+ if (args.verbose) {
437
+ console.log(`Determined release group --from ref from the first commit in the workspace: ${fromRef}`);
438
+ }
439
+ }
440
+ else {
441
+ throw new Error(`Unable to determine the previous git tag. If this is the first release of your release group, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
308
442
  }
309
- commits = commitsForProject;
310
- }
311
- if (!fromRef && !commits) {
312
- throw new Error(`Unable to determine the previous git tag. If this is the first release of your workspace, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
313
- }
314
- if (!commits) {
315
- commits = await getCommits(fromRef, toSHA);
316
443
  }
444
+ // Make sure that the fromRef is actually resolvable
445
+ const fromSHA = await (0, git_1.getCommitHash)(fromRef);
317
446
  const { fileMap } = await (0, file_map_utils_1.createFileMapUsingProjectGraph)(projectGraph);
318
447
  const fileToProjectMap = createFileToProjectMap(fileMap.projectFileMap);
448
+ commits = await getCommits(fromSHA, toSHA);
319
449
  changes = filterHiddenChanges(commits.map((c) => ({
320
450
  type: c.type,
321
451
  scope: c.scope,
@@ -338,7 +468,7 @@ async function releaseChangelog(args) {
338
468
  changes,
339
469
  projectsVersionData,
340
470
  releaseGroup,
341
- projects: [project],
471
+ projects: projectNodes,
342
472
  nxReleaseConfig,
343
473
  projectToAdditionalDependencyBumps,
344
474
  // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
@@ -367,105 +497,11 @@ async function releaseChangelog(args) {
367
497
  }
368
498
  }
369
499
  }
370
- else {
371
- let changes = [];
372
- // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
373
- let commits = [];
374
- if (releaseGroup.versionPlans) {
375
- changes = filterHiddenChanges(releaseGroup.versionPlans
376
- .map((vp) => {
377
- const parsedMessage = (0, git_1.parseConventionalCommitsMessage)(vp.message);
378
- // only properly formatted conventional commits messages will be included in the changelog
379
- if (!parsedMessage) {
380
- return null;
381
- }
382
- return {
383
- type: parsedMessage.type,
384
- scope: parsedMessage.scope,
385
- description: parsedMessage.description,
386
- body: '',
387
- isBreaking: parsedMessage.breaking,
388
- githubReferences: [],
389
- affectedProjects: '*',
390
- };
391
- })
392
- .filter(Boolean), nxReleaseConfig.conventionalCommits);
393
- }
394
- else {
395
- let fromRef = args.from ||
396
- (await (0, git_1.getLatestGitTagForPattern)(releaseGroup.releaseTagPattern))
397
- ?.tag;
398
- if (!fromRef) {
399
- if (useAutomaticFromRef) {
400
- fromRef = await (0, git_1.getFirstGitCommit)();
401
- if (args.verbose) {
402
- console.log(`Determined release group --from ref from the first commit in the workspace: ${fromRef}`);
403
- }
404
- }
405
- else {
406
- throw new Error(`Unable to determine the previous git tag. If this is the first release of your release group, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTagPattern" property in nx.json to match the structure of your repository's git tags.`);
407
- }
408
- }
409
- // Make sure that the fromRef is actually resolvable
410
- const fromSHA = await (0, git_1.getCommitHash)(fromRef);
411
- const { fileMap } = await (0, file_map_utils_1.createFileMapUsingProjectGraph)(projectGraph);
412
- const fileToProjectMap = createFileToProjectMap(fileMap.projectFileMap);
413
- commits = await getCommits(fromSHA, toSHA);
414
- changes = filterHiddenChanges(commits.map((c) => ({
415
- type: c.type,
416
- scope: c.scope,
417
- description: c.description,
418
- body: c.body,
419
- isBreaking: c.isBreaking,
420
- githubReferences: c.references,
421
- author: c.author,
422
- shortHash: c.shortHash,
423
- revertedHashes: c.revertedHashes,
424
- affectedProjects: commitChangesNonProjectFiles(c, fileMap.nonProjectFiles)
425
- ? '*'
426
- : getProjectsAffectedByCommit(c, fileToProjectMap),
427
- })), nxReleaseConfig.conventionalCommits);
428
- }
429
- const projectChangelogs = await generateChangelogForProjects({
430
- tree,
431
- args,
432
- projectGraph,
433
- changes,
434
- projectsVersionData,
435
- releaseGroup,
436
- projects: projectNodes,
437
- nxReleaseConfig,
438
- projectToAdditionalDependencyBumps,
439
- // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
440
- commits: filterHiddenCommits(commits, nxReleaseConfig.conventionalCommits),
441
- });
442
- let hasPushed = false;
443
- for (const [projectName, projectChangelog] of Object.entries(projectChangelogs)) {
444
- if (projectChangelogs &&
445
- shouldCreateGitHubRelease(releaseGroup.changelog, args.createRelease)) {
446
- postGitTasks.push(async (latestCommit) => {
447
- if (!hasPushed) {
448
- output_1.output.logSingleLine(`Pushing to git remote`);
449
- // Before we can create/update the release we need to ensure the commit exists on the remote
450
- await (0, git_1.gitPush)({
451
- gitRemote: args.gitRemote,
452
- dryRun: args.dryRun,
453
- verbose: args.verbose,
454
- });
455
- hasPushed = true;
456
- }
457
- output_1.output.logSingleLine(`Creating GitHub Release`);
458
- await (0, github_1.createOrUpdateGithubRelease)(projectChangelog.releaseVersion, projectChangelog.contents, latestCommit, { dryRun: args.dryRun });
459
- });
460
- }
461
- allProjectChangelogs[projectName] = projectChangelog;
462
- }
463
- }
464
- }
465
- await applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTasks, commitMessageValues, gitTagValues, releaseGroups);
466
- return {
467
- workspaceChangelog,
468
- projectChangelogs: allProjectChangelogs,
500
+ await applyChangesAndExit(args, nxReleaseConfig, tree, toSHA, postGitTasks, commitMessageValues, gitTagValues, releaseGroups);
501
+ return {
502
+ workspaceChangelog,
503
+ projectChangelogs: allProjectChangelogs,
504
+ };
469
505
  };
470
506
  }
471
507
  function resolveChangelogVersions(args, releaseGroups, releaseGroupToFilteredProjects) {
@@ -900,3 +936,19 @@ function createFileToProjectMap(projectFileMap) {
900
936
  }
901
937
  return fileToProjectMap;
902
938
  }
939
+ function versionPlanSemverReleaseTypeToChangelogType(bump) {
940
+ switch (bump) {
941
+ case 'premajor':
942
+ case 'major':
943
+ return { type: 'feat', isBreaking: true };
944
+ case 'preminor':
945
+ case 'minor':
946
+ return { type: 'feat', isBreaking: false };
947
+ case 'prerelease':
948
+ case 'prepatch':
949
+ case 'patch':
950
+ return { type: 'fix', isBreaking: false };
951
+ default:
952
+ throw new Error(`Invalid semver bump type: ${bump}`);
953
+ }
954
+ }