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.
- package/changelog-renderer/index.d.ts +43 -0
- package/changelog-renderer/index.js +180 -0
- package/package.json +12 -13
- package/schemas/nx-schema.json +141 -0
- package/src/adapter/ngcli-adapter.js +32 -0
- package/src/command-line/init/implementation/add-nx-to-monorepo.js +1 -0
- package/src/command-line/init/implementation/add-nx-to-nest.js +1 -0
- package/src/command-line/init/implementation/add-nx-to-npm-repo.js +1 -0
- package/src/command-line/init/implementation/utils.d.ts +1 -0
- package/src/command-line/init/implementation/utils.js +14 -1
- package/src/command-line/release/changelog.js +332 -75
- package/src/command-line/release/command-object.d.ts +1 -3
- package/src/command-line/release/command-object.js +3 -17
- package/src/command-line/release/config/config.d.ts +1 -1
- package/src/command-line/release/config/config.js +153 -50
- package/src/command-line/release/utils/markdown.d.ts +1 -4
- package/src/command-line/release/utils/markdown.js +3 -136
- package/src/command-line/release/utils/print-changes.d.ts +5 -1
- package/src/command-line/release/utils/print-changes.js +3 -2
- package/src/command-line/show/show.js +2 -0
- package/src/config/nx-json.d.ts +80 -3
- package/src/config/nx-json.js +1 -1
- package/src/config/project-graph.d.ts +1 -1
- package/src/daemon/client/client.js +0 -6
- package/src/daemon/server/outputs-tracking.d.ts +2 -2
- package/src/daemon/server/outputs-tracking.js +2 -2
- package/src/daemon/server/project-graph-incremental-recomputation.js +1 -28
- package/src/daemon/server/server.js +22 -58
- package/src/daemon/server/shutdown-utils.d.ts +0 -6
- package/src/daemon/server/shutdown-utils.js +1 -36
- package/src/daemon/server/watcher.d.ts +2 -6
- package/src/daemon/server/watcher.js +1 -92
- package/src/generators/utils/project-configuration.js +10 -8
- package/src/migrations/update-15-0-0/prefix-outputs.js +9 -9
- package/src/project-graph/project-graph.js +6 -1
- package/src/project-graph/utils/normalize-project-nodes.js +4 -1
- package/src/project-graph/utils/retrieve-workspace-files.js +3 -1
- package/src/tasks-runner/utils.js +8 -4
- package/src/utils/package-manager.d.ts +1 -0
- package/src/utils/package-manager.js +5 -2
- 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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
72
|
-
|
|
73
|
-
let rootChangelogContents = tree.read(args.file)?.toString() ?? '';
|
|
196
|
+
if (interpolatedTreePath) {
|
|
197
|
+
let rootChangelogContents = tree.read(interpolatedTreePath)?.toString() ?? '';
|
|
74
198
|
if (rootChangelogContents) {
|
|
75
|
-
|
|
76
|
-
const
|
|
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}`,
|
|
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 = `${
|
|
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
|
|
87
|
-
rootChangelogContents =
|
|
211
|
+
// No existing changelog contents, simply create a new one using the generated contents
|
|
212
|
+
rootChangelogContents = contents;
|
|
88
213
|
}
|
|
89
|
-
tree.write(
|
|
90
|
-
printSummary = () => (0, print_changes_1.printChanges)(tree, !!
|
|
214
|
+
tree.write(interpolatedTreePath, rootChangelogContents);
|
|
215
|
+
printSummary = () => (0, print_changes_1.printChanges)(tree, !!dryRun, 3, false, noDiffInChangelogMessage);
|
|
91
216
|
}
|
|
92
|
-
if (
|
|
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}${
|
|
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}${
|
|
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 (
|
|
265
|
+
if (!interpolatedTreePath) {
|
|
141
266
|
console.log('');
|
|
142
267
|
(0, print_changes_1.printDiff)(existingGithubReleaseForVersion
|
|
143
268
|
? existingGithubReleaseForVersion.body
|
|
144
|
-
: '',
|
|
269
|
+
: '', contents, 3, noDiffInChangelogMessage);
|
|
145
270
|
}
|
|
146
271
|
existingPrintSummaryFn();
|
|
147
272
|
};
|
|
148
|
-
if (!
|
|
273
|
+
if (!dryRun) {
|
|
149
274
|
await (0, github_1.createOrUpdateGithubRelease)(githubRequestConfig, {
|
|
150
|
-
version: releaseVersion,
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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?:
|
|
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: '
|
|
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';
|