nx 21.0.0-canary.20250429-cf4a1f3 → 21.0.0-canary.20250501-8f50358

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 (63) hide show
  1. package/migrations.json +16 -1
  2. package/package.json +11 -11
  3. package/release/changelog-renderer/index.d.ts +7 -7
  4. package/release/changelog-renderer/index.js +12 -31
  5. package/schemas/nx-schema.json +8 -3
  6. package/src/command-line/migrate/migrate-ui-api.d.ts +2 -1
  7. package/src/command-line/migrate/migrate-ui-api.js +4 -3
  8. package/src/command-line/migrate/migrate.d.ts +12 -6
  9. package/src/command-line/migrate/migrate.js +31 -9
  10. package/src/command-line/release/changelog.d.ts +3 -2
  11. package/src/command-line/release/changelog.js +57 -70
  12. package/src/command-line/release/command-object.d.ts +1 -1
  13. package/src/command-line/release/config/config.d.ts +8 -1
  14. package/src/command-line/release/config/config.js +18 -11
  15. package/src/command-line/release/release.js +30 -18
  16. package/src/command-line/release/utils/git.d.ts +1 -0
  17. package/src/command-line/release/utils/git.js +27 -8
  18. package/src/command-line/release/utils/remote-release-clients/github.d.ts +57 -0
  19. package/src/command-line/release/utils/remote-release-clients/github.js +309 -0
  20. package/src/command-line/release/utils/remote-release-clients/gitlab.d.ts +62 -0
  21. package/src/command-line/release/utils/remote-release-clients/gitlab.js +271 -0
  22. package/src/command-line/release/utils/remote-release-clients/remote-release-client.d.ts +111 -0
  23. package/src/command-line/release/utils/remote-release-clients/remote-release-client.js +136 -0
  24. package/src/command-line/repair/repair.js +8 -2
  25. package/src/command-line/report/report.js +1 -1
  26. package/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  27. package/src/command-line/yargs-utils/shared-options.js +22 -3
  28. package/src/config/misc-interfaces.d.ts +9 -1
  29. package/src/config/nx-json.d.ts +8 -5
  30. package/src/core/graph/main.js +1 -1
  31. package/src/core/graph/styles.css +1 -1
  32. package/src/devkit-exports.d.ts +1 -1
  33. package/src/migrations/update-21-0-0/release-changelog-config-changes.d.ts +2 -0
  34. package/src/migrations/update-21-0-0/release-changelog-config-changes.js +38 -0
  35. package/src/migrations/update-21-0-0/remove-custom-tasks-runner.d.ts +2 -0
  36. package/src/migrations/update-21-0-0/remove-custom-tasks-runner.js +38 -0
  37. package/src/migrations/update-21-0-0/remove-legacy-cache.d.ts +2 -0
  38. package/src/migrations/update-21-0-0/remove-legacy-cache.js +17 -0
  39. package/src/native/index.d.ts +6 -1
  40. package/src/native/native-bindings.js +1 -0
  41. package/src/native/native-file-cache-location.js +2 -1
  42. package/src/native/nx.wasm32-wasi.wasm +0 -0
  43. package/src/project-graph/plugins/get-plugins.js +19 -14
  44. package/src/tasks-runner/batch/run-batch.js +1 -1
  45. package/src/tasks-runner/cache.d.ts +1 -2
  46. package/src/tasks-runner/cache.js +2 -18
  47. package/src/tasks-runner/is-tui-enabled.d.ts +16 -1
  48. package/src/tasks-runner/is-tui-enabled.js +40 -28
  49. package/src/tasks-runner/life-cycles/tui-summary-life-cycle.js +8 -7
  50. package/src/tasks-runner/pseudo-terminal.d.ts +1 -0
  51. package/src/tasks-runner/pseudo-terminal.js +11 -1
  52. package/src/tasks-runner/run-command.js +5 -27
  53. package/src/tasks-runner/running-tasks/node-child-process.d.ts +1 -0
  54. package/src/tasks-runner/running-tasks/node-child-process.js +7 -0
  55. package/src/tasks-runner/task-graph-utils.d.ts +3 -0
  56. package/src/tasks-runner/task-graph-utils.js +31 -2
  57. package/src/tasks-runner/task-orchestrator.js +16 -4
  58. package/src/utils/is-ci.d.ts +1 -1
  59. package/src/utils/is-ci.js +4 -1
  60. package/src/utils/package-manager.d.ts +1 -0
  61. package/src/utils/package-manager.js +29 -16
  62. package/src/command-line/release/utils/github.d.ts +0 -32
  63. package/src/command-line/release/utils/github.js +0 -326
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GithubRemoteReleaseClient = exports.defaultCreateReleaseProvider = void 0;
4
+ const chalk = require("chalk");
5
+ const enquirer_1 = require("enquirer");
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const output_1 = require("../../../../utils/output");
10
+ const path_1 = require("../../../../utils/path");
11
+ const remote_release_client_1 = require("./remote-release-client");
12
+ // axios types and values don't seem to match
13
+ const _axios = require("axios");
14
+ const axios = _axios;
15
+ exports.defaultCreateReleaseProvider = {
16
+ provider: 'github',
17
+ hostname: 'github.com',
18
+ apiBaseUrl: 'https://api.github.com',
19
+ };
20
+ class GithubRemoteReleaseClient extends remote_release_client_1.RemoteReleaseClient {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.remoteReleaseProviderName = 'GitHub';
24
+ }
25
+ /**
26
+ * Get GitHub repository data from git remote
27
+ */
28
+ static resolveRepoData(createReleaseConfig, remoteName = 'origin') {
29
+ try {
30
+ const remoteUrl = (0, node_child_process_1.execSync)(`git remote get-url ${remoteName}`, {
31
+ encoding: 'utf8',
32
+ stdio: 'pipe',
33
+ }).trim();
34
+ // Use the default provider if custom one is not specified or releases are disabled
35
+ let hostname = exports.defaultCreateReleaseProvider.hostname;
36
+ let apiBaseUrl = exports.defaultCreateReleaseProvider.apiBaseUrl;
37
+ if (createReleaseConfig !== false &&
38
+ typeof createReleaseConfig !== 'string') {
39
+ hostname = createReleaseConfig.hostname;
40
+ apiBaseUrl = createReleaseConfig.apiBaseUrl;
41
+ }
42
+ // Extract the 'user/repo' part from the URL
43
+ const escapedHostname = hostname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
44
+ const regexString = `${escapedHostname}[/:]([\\w.-]+/[\\w.-]+)(\\.git)?`;
45
+ const regex = new RegExp(regexString);
46
+ const match = remoteUrl.match(regex);
47
+ if (match && match[1]) {
48
+ return {
49
+ hostname,
50
+ apiBaseUrl,
51
+ // Ensure any trailing .git is stripped
52
+ slug: match[1].replace(/\.git$/, ''),
53
+ };
54
+ }
55
+ else {
56
+ throw new Error(`Could not extract "user/repo" data from the resolved remote URL: ${remoteUrl}`);
57
+ }
58
+ }
59
+ catch (error) {
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * Resolve a GitHub token from environment variables or gh CLI
65
+ */
66
+ static async resolveTokenData(hostname) {
67
+ // Try and resolve from the environment
68
+ const tokenFromEnv = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
69
+ if (tokenFromEnv) {
70
+ return { token: tokenFromEnv, headerName: 'Authorization' };
71
+ }
72
+ // Try and resolve from gh CLI installation
73
+ const ghCLIPath = (0, path_1.joinPathFragments)(process.env.XDG_CONFIG_HOME || (0, path_1.joinPathFragments)((0, node_os_1.homedir)(), '.config'), 'gh', 'hosts.yml');
74
+ if ((0, node_fs_1.existsSync)(ghCLIPath)) {
75
+ const yamlContents = await node_fs_1.promises.readFile(ghCLIPath, 'utf8');
76
+ const { load } = require('@zkochan/js-yaml');
77
+ const ghCLIConfig = load(yamlContents);
78
+ if (ghCLIConfig[hostname]) {
79
+ // Web based session (the token is already embedded in the config)
80
+ if (ghCLIConfig[hostname].oauth_token) {
81
+ return ghCLIConfig[hostname].oauth_token;
82
+ }
83
+ // SSH based session (we need to dynamically resolve a token using the CLI)
84
+ if (ghCLIConfig[hostname].user &&
85
+ ghCLIConfig[hostname].git_protocol === 'ssh') {
86
+ const token = (0, node_child_process_1.execSync)(`gh auth token`, {
87
+ encoding: 'utf8',
88
+ stdio: 'pipe',
89
+ windowsHide: false,
90
+ }).trim();
91
+ return { token, headerName: 'Authorization' };
92
+ }
93
+ }
94
+ }
95
+ if (hostname !== 'github.com') {
96
+ console.log(`Warning: It was not possible to automatically resolve a GitHub token from your environment for hostname ${hostname}. If you set the GITHUB_TOKEN or GH_TOKEN environment variable, that will be used for GitHub API requests.`);
97
+ }
98
+ return null;
99
+ }
100
+ createPostGitTask(releaseVersion, changelogContents, dryRun) {
101
+ return async (latestCommit) => {
102
+ output_1.output.logSingleLine(`Creating GitHub Release`);
103
+ await this.createOrUpdateRelease(releaseVersion, changelogContents, latestCommit, { dryRun });
104
+ };
105
+ }
106
+ async applyUsernameToAuthors(authors) {
107
+ await Promise.all([...authors.keys()].map(async (authorName) => {
108
+ const meta = authors.get(authorName);
109
+ for (const email of meta.email) {
110
+ if (email.endsWith('@users.noreply.github.com')) {
111
+ const match = email.match(/^(\d+\+)?([^@]+)@users\.noreply\.github\.com$/);
112
+ if (match && match[2]) {
113
+ meta.username = match[2];
114
+ break;
115
+ }
116
+ }
117
+ const { data } = await axios
118
+ .get(`https://ungh.cc/users/find/${email}`)
119
+ .catch(() => ({ data: { user: null } }));
120
+ if (data?.user) {
121
+ meta.username = data.user.username;
122
+ break;
123
+ }
124
+ }
125
+ }));
126
+ }
127
+ /**
128
+ * Get a release by tag
129
+ */
130
+ async getReleaseByTag(tag) {
131
+ const githubRepoData = this.getRequiredRemoteRepoData();
132
+ return await this.makeRequest(`/repos/${githubRepoData.slug}/releases/tags/${tag}`);
133
+ }
134
+ /**
135
+ * Create a new release
136
+ */
137
+ async createRelease(remoteRelease) {
138
+ const githubRepoData = this.getRequiredRemoteRepoData();
139
+ return await this.makeRequest(`/repos/${githubRepoData.slug}/releases`, {
140
+ method: 'POST',
141
+ data: remoteRelease,
142
+ });
143
+ }
144
+ async updateRelease(id, remoteRelease) {
145
+ const githubRepoData = this.getRequiredRemoteRepoData();
146
+ return await this.makeRequest(`/repos/${githubRepoData.slug}/releases/${id}`, {
147
+ method: 'PATCH',
148
+ data: remoteRelease,
149
+ });
150
+ }
151
+ getManualRemoteReleaseURL(remoteReleaseOptions) {
152
+ const githubRepoData = this.getRequiredRemoteRepoData();
153
+ // Parameters taken from https://github.com/isaacs/github/issues/1410#issuecomment-442240267
154
+ let url = `https://${githubRepoData.hostname}/${githubRepoData.slug}/releases/new?tag=${remoteReleaseOptions.version}&title=${remoteReleaseOptions.version}&body=${encodeURIComponent(remoteReleaseOptions.body)}&target=${remoteReleaseOptions.commit}`;
155
+ if (remoteReleaseOptions.prerelease) {
156
+ url += '&prerelease=true';
157
+ }
158
+ return url;
159
+ }
160
+ handleAuthError() {
161
+ output_1.output.error({
162
+ title: `Unable to resolve data via the GitHub API. You can use any of the following options to resolve this:`,
163
+ bodyLines: [
164
+ '- Set the `GITHUB_TOKEN` or `GH_TOKEN` environment variable to a valid GitHub token with `repo` scope',
165
+ '- Have an active session via the official gh CLI tool (https://cli.github.com) in your current terminal',
166
+ ],
167
+ });
168
+ }
169
+ logReleaseAction(existingRelease, gitTag, dryRun) {
170
+ const githubRepoData = this.getRequiredRemoteRepoData();
171
+ const logTitle = `https://${githubRepoData.hostname}/${githubRepoData.slug}/releases/tag/${gitTag}`;
172
+ if (existingRelease) {
173
+ console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
174
+ }
175
+ else {
176
+ console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
177
+ }
178
+ }
179
+ async handleError(error, result) {
180
+ if (error) {
181
+ process.exitCode = 1;
182
+ if (error.response?.data) {
183
+ // There's a nicely formatted error from GitHub we can display to the user
184
+ output_1.output.error({
185
+ title: `A GitHub API Error occurred when creating/updating the release`,
186
+ bodyLines: [
187
+ `GitHub Error: ${JSON.stringify(error.response.data)}`,
188
+ `---`,
189
+ `Request Data:`,
190
+ `Repo: ${this.getRemoteRepoData()?.slug}`,
191
+ `Token Header Data: ${this.tokenHeader}`,
192
+ `Body: ${JSON.stringify(result.requestData)}`,
193
+ ],
194
+ });
195
+ }
196
+ else {
197
+ console.log(error);
198
+ console.error(`An unknown error occurred while trying to create a release on GitHub, please report this on https://github.com/nrwl/nx (NOTE: make sure to redact your GitHub token from the error message!)`);
199
+ }
200
+ }
201
+ const shouldContinueInGitHub = await this.promptForContinueInGitHub();
202
+ if (!shouldContinueInGitHub) {
203
+ return;
204
+ }
205
+ const open = require('open');
206
+ await open(result.url)
207
+ .then(() => {
208
+ console.info(`\nFollow up in the browser to manually create the release:\n\n` +
209
+ chalk.underline(chalk.cyan(result.url)) +
210
+ `\n`);
211
+ })
212
+ .catch(() => {
213
+ console.info(`Open this link to manually create a release: \n` +
214
+ chalk.underline(chalk.cyan(result.url)) +
215
+ '\n');
216
+ });
217
+ }
218
+ async promptForContinueInGitHub() {
219
+ try {
220
+ const reply = await (0, enquirer_1.prompt)([
221
+ {
222
+ name: 'open',
223
+ message: 'Do you want to finish creating the release manually in your browser?',
224
+ type: 'autocomplete',
225
+ choices: [
226
+ {
227
+ name: 'Yes',
228
+ hint: 'It will pre-populate the form for you',
229
+ },
230
+ {
231
+ name: 'No',
232
+ },
233
+ ],
234
+ initial: 0,
235
+ },
236
+ ]);
237
+ return reply.open === 'Yes';
238
+ }
239
+ catch {
240
+ // Ensure the cursor is always restored before exiting
241
+ process.stdout.write('\u001b[?25h');
242
+ // Handle the case where the user exits the prompt with ctrl+c
243
+ process.exit(1);
244
+ }
245
+ }
246
+ /**
247
+ * Format references for the release (e.g., PRs, issues)
248
+ */
249
+ formatReferences(references) {
250
+ const githubRepoData = this.getRequiredRemoteRepoData();
251
+ const providerToRefSpec = {
252
+ github: { 'pull-request': 'pull', hash: 'commit', issue: 'issues' },
253
+ };
254
+ const refSpec = providerToRefSpec.github;
255
+ const formatSingleReference = (ref) => {
256
+ return `[${ref.value}](https://${githubRepoData.hostname}/${githubRepoData.slug}/${refSpec[ref.type]}/${ref.value.replace(/^#/, '')})`;
257
+ };
258
+ const pr = references.filter((ref) => ref.type === 'pull-request');
259
+ const issue = references.filter((ref) => ref.type === 'issue');
260
+ if (pr.length > 0 || issue.length > 0) {
261
+ return (' (' +
262
+ [...pr, ...issue].map((ref) => formatSingleReference(ref)).join(', ') +
263
+ ')');
264
+ }
265
+ if (references.length > 0) {
266
+ return ' (' + formatSingleReference(references[0]) + ')';
267
+ }
268
+ return '';
269
+ }
270
+ async syncRelease(remoteReleaseOptions, existingRelease) {
271
+ const githubReleaseData = {
272
+ tag_name: remoteReleaseOptions.version,
273
+ name: remoteReleaseOptions.version,
274
+ body: remoteReleaseOptions.body,
275
+ prerelease: remoteReleaseOptions.prerelease,
276
+ // legacy specifies that the latest release should be determined based on the release creation date and higher semantic version.
277
+ make_latest: 'legacy',
278
+ };
279
+ try {
280
+ const newGhRelease = await (existingRelease
281
+ ? this.updateRelease(existingRelease.id, githubReleaseData)
282
+ : this.createRelease({
283
+ ...githubReleaseData,
284
+ target_commitish: remoteReleaseOptions.commit,
285
+ }));
286
+ return {
287
+ status: existingRelease ? 'updated' : 'created',
288
+ id: newGhRelease.id,
289
+ url: newGhRelease.html_url,
290
+ };
291
+ }
292
+ catch (error) {
293
+ return {
294
+ status: 'manual',
295
+ error,
296
+ url: this.getManualRemoteReleaseURL(remoteReleaseOptions),
297
+ requestData: githubReleaseData,
298
+ };
299
+ }
300
+ }
301
+ getRequiredRemoteRepoData() {
302
+ const githubRepoData = this.getRemoteRepoData();
303
+ if (!githubRepoData) {
304
+ throw new Error(`No remote repo data could be resolved for the current workspace`);
305
+ }
306
+ return githubRepoData;
307
+ }
308
+ }
309
+ exports.GithubRemoteReleaseClient = GithubRemoteReleaseClient;
@@ -0,0 +1,62 @@
1
+ import type { PostGitTask } from '../../changelog';
2
+ import type { ResolvedCreateRemoteReleaseProvider } from '../../config/config';
3
+ import type { Reference } from '../git';
4
+ import { ReleaseVersion } from '../shared';
5
+ import { RemoteReleaseClient, RemoteReleaseOptions, RemoteReleaseResult, RemoteRepoData } from './remote-release-client';
6
+ export interface GitLabRepoData extends RemoteRepoData {
7
+ projectId: string;
8
+ }
9
+ export interface GitLabRelease {
10
+ id?: string;
11
+ name?: string;
12
+ tag_name: string;
13
+ ref: string;
14
+ assets?: {
15
+ links?: {
16
+ name: string;
17
+ url: string;
18
+ direct_asset_path?: string;
19
+ link_type?: string;
20
+ }[];
21
+ };
22
+ released_at?: string;
23
+ description?: string;
24
+ milestones?: string[];
25
+ prerelease?: boolean;
26
+ }
27
+ export declare const defaultCreateReleaseProvider: ResolvedCreateRemoteReleaseProvider;
28
+ export declare class GitLabRemoteReleaseClient extends RemoteReleaseClient<GitLabRelease> {
29
+ remoteReleaseProviderName: string;
30
+ /**
31
+ * Get GitLab repository data from git remote
32
+ */
33
+ static resolveRepoData(createReleaseConfig: false | ResolvedCreateRemoteReleaseProvider, remoteName?: string): GitLabRepoData | null;
34
+ /**
35
+ * Resolve a GitLab token from various environment variables
36
+ */
37
+ static resolveTokenData(hostname: string): Promise<{
38
+ token: string;
39
+ headerName: string;
40
+ } | null>;
41
+ createPostGitTask(releaseVersion: ReleaseVersion, changelogContents: string, dryRun: boolean): PostGitTask;
42
+ applyUsernameToAuthors(): Promise<void>;
43
+ protected getReleaseByTag(tag: string): Promise<GitLabRelease>;
44
+ protected createRelease(remoteRelease: GitLabRelease): Promise<any>;
45
+ protected updateRelease(_id: string, remoteRelease: GitLabRelease): Promise<any>;
46
+ /**
47
+ * Generate a URL for manual release creation on GitLab. Sadly, unlike GitHub, GitLab does not
48
+ * seem to respect query string parameters for setting the UI form fields, so the user has to
49
+ * start from scratch.
50
+ */
51
+ protected getManualRemoteReleaseURL(_remoteReleaseOptions: RemoteReleaseOptions): string;
52
+ protected handleAuthError(): void;
53
+ protected logReleaseAction(existingRelease: GitLabRelease | undefined, gitTag: string, dryRun: boolean): void;
54
+ protected handleError(error: any, result: RemoteReleaseResult): Promise<void>;
55
+ private promptForContinueInGitLab;
56
+ /**
57
+ * Format references for the release (e.g., MRs, issues)
58
+ */
59
+ formatReferences(references: Reference[]): string;
60
+ protected syncRelease(remoteReleaseOptions: RemoteReleaseOptions, existingRelease?: GitLabRelease): Promise<RemoteReleaseResult>;
61
+ private getRequiredRemoteRepoData;
62
+ }
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitLabRemoteReleaseClient = exports.defaultCreateReleaseProvider = void 0;
4
+ const chalk = require("chalk");
5
+ const enquirer_1 = require("enquirer");
6
+ const node_child_process_1 = require("node:child_process");
7
+ const output_1 = require("../../../../utils/output");
8
+ const remote_release_client_1 = require("./remote-release-client");
9
+ exports.defaultCreateReleaseProvider = {
10
+ provider: 'gitlab',
11
+ hostname: 'gitlab.com',
12
+ apiBaseUrl: 'https://gitlab.com/api/v4',
13
+ };
14
+ class GitLabRemoteReleaseClient extends remote_release_client_1.RemoteReleaseClient {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.remoteReleaseProviderName = 'GitLab';
18
+ }
19
+ /**
20
+ * Get GitLab repository data from git remote
21
+ */
22
+ static resolveRepoData(createReleaseConfig, remoteName = 'origin') {
23
+ try {
24
+ const remoteUrl = (0, node_child_process_1.execSync)(`git remote get-url ${remoteName}`, {
25
+ encoding: 'utf8',
26
+ stdio: 'pipe',
27
+ }).trim();
28
+ // Use the default provider if custom one is not specified or releases are disabled
29
+ let hostname = exports.defaultCreateReleaseProvider.hostname;
30
+ let apiBaseUrl = exports.defaultCreateReleaseProvider.apiBaseUrl;
31
+ if (createReleaseConfig !== false &&
32
+ typeof createReleaseConfig !== 'string') {
33
+ hostname = createReleaseConfig.hostname || hostname;
34
+ apiBaseUrl = createReleaseConfig.apiBaseUrl || apiBaseUrl;
35
+ }
36
+ // Extract the project path from the URL
37
+ const escapedHostname = hostname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
38
+ const regexString = `${escapedHostname}[/:]([\\w.-]+/[\\w.-]+(?:/[\\w.-]+)*)(\\.git)?`;
39
+ const regex = new RegExp(regexString);
40
+ const match = remoteUrl.match(regex);
41
+ if (match && match[1]) {
42
+ // Remove trailing .git if present
43
+ const slug = match[1].replace(/\.git$/, '');
44
+ // Encode the project path for use in API URLs
45
+ const projectId = encodeURIComponent(slug);
46
+ return {
47
+ hostname,
48
+ apiBaseUrl,
49
+ slug,
50
+ projectId,
51
+ };
52
+ }
53
+ else {
54
+ throw new Error(`Could not extract project path data from the resolved remote URL: ${remoteUrl}`);
55
+ }
56
+ }
57
+ catch (err) {
58
+ if (process.env.NX_VERBOSE_LOGGING === 'true') {
59
+ console.error(err);
60
+ }
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Resolve a GitLab token from various environment variables
66
+ */
67
+ static async resolveTokenData(hostname) {
68
+ // Try and resolve from the environment
69
+ const tokenFromEnv = process.env.GITLAB_TOKEN || process.env.GL_TOKEN;
70
+ if (tokenFromEnv) {
71
+ return { token: tokenFromEnv, headerName: 'PRIVATE-TOKEN' };
72
+ }
73
+ // Try and resolve from a CI environment
74
+ if (process.env.CI_JOB_TOKEN) {
75
+ return { token: process.env.CI_JOB_TOKEN, headerName: 'JOB-TOKEN' };
76
+ }
77
+ if (hostname !== 'gitlab.com') {
78
+ console.log(`Warning: It was not possible to automatically resolve a GitLab token from your environment for hostname ${hostname}. If you set the GITLAB_TOKEN or GL_TOKEN environment variable (or you are in GitLab CI where CI_JOB_TOKEN is set automatically), that will be used for GitLab API requests.`);
79
+ }
80
+ return null;
81
+ }
82
+ createPostGitTask(releaseVersion, changelogContents, dryRun) {
83
+ return async (latestCommit) => {
84
+ output_1.output.logSingleLine(`Creating GitLab Release`);
85
+ await this.createOrUpdateRelease(releaseVersion, changelogContents, latestCommit, { dryRun });
86
+ };
87
+ }
88
+ // Not implemented for GitLab yet, the changelog renderer should not call this method
89
+ async applyUsernameToAuthors() {
90
+ throw new Error('applyUsernameToAuthors is not implemented for GitLab yet');
91
+ }
92
+ async getReleaseByTag(tag) {
93
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
94
+ return await this.makeRequest(`/projects/${gitlabRepoData.projectId}/releases/${encodeURIComponent(tag)}`);
95
+ }
96
+ async createRelease(remoteRelease) {
97
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
98
+ return await this.makeRequest(`/projects/${gitlabRepoData.projectId}/releases`, {
99
+ method: 'POST',
100
+ data: remoteRelease,
101
+ });
102
+ }
103
+ async updateRelease(_id, remoteRelease) {
104
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
105
+ return await this.makeRequest(`/projects/${gitlabRepoData.projectId}/releases/${encodeURIComponent(remoteRelease.tag_name)}`, {
106
+ method: 'PUT',
107
+ data: remoteRelease,
108
+ });
109
+ }
110
+ /**
111
+ * Generate a URL for manual release creation on GitLab. Sadly, unlike GitHub, GitLab does not
112
+ * seem to respect query string parameters for setting the UI form fields, so the user has to
113
+ * start from scratch.
114
+ */
115
+ getManualRemoteReleaseURL(_remoteReleaseOptions) {
116
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
117
+ return `https://${gitlabRepoData.hostname}/${gitlabRepoData.slug}/-/releases/new`;
118
+ }
119
+ handleAuthError() {
120
+ output_1.output.error({
121
+ title: `Unable to resolve data via the GitLab API.`,
122
+ bodyLines: [
123
+ '- Set the `GITLAB_TOKEN` or `GL_TOKEN` environment variable to a valid GitLab token with `api` scope',
124
+ '- If running in GitLab CI, the automatically provisioned CI_JOB_TOKEN can also be used',
125
+ ],
126
+ });
127
+ }
128
+ logReleaseAction(existingRelease, gitTag, dryRun) {
129
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
130
+ const logTitle = `https://${gitlabRepoData.hostname}/${gitlabRepoData.slug}/-/releases/${encodeURIComponent(gitTag)}`;
131
+ if (existingRelease) {
132
+ console.error(`${chalk.white('UPDATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
133
+ }
134
+ else {
135
+ console.error(`${chalk.green('CREATE')} ${logTitle}${dryRun ? chalk.keyword('orange')(' [dry-run]') : ''}`);
136
+ }
137
+ }
138
+ async handleError(error, result) {
139
+ if (error) {
140
+ process.exitCode = 1;
141
+ if (error.response?.data) {
142
+ output_1.output.error({
143
+ title: `A GitLab API Error occurred when creating/updating the release`,
144
+ bodyLines: [
145
+ `GitLab Error: ${JSON.stringify(error.response.data)}`,
146
+ `---`,
147
+ `Request Data:`,
148
+ `Repo: ${this.getRemoteRepoData()?.slug}`,
149
+ `Token Header Data: ${this.tokenHeader}`,
150
+ `Body: ${JSON.stringify(result.requestData)}`,
151
+ ],
152
+ });
153
+ }
154
+ else {
155
+ console.log(error);
156
+ console.error(`An unknown error occurred while trying to create a release on GitLab, please report this on https://github.com/nrwl/nx (NOTE: make sure to redact your GitLab token from the error message!)`);
157
+ }
158
+ }
159
+ const shouldContinueInGitLab = await this.promptForContinueInGitLab();
160
+ if (!shouldContinueInGitLab) {
161
+ return;
162
+ }
163
+ const open = require('open');
164
+ await open(result.url)
165
+ .then(() => {
166
+ console.info(`\nFollow up in the browser to manually create the release:\n\n` +
167
+ chalk.underline(chalk.cyan(result.url)) +
168
+ `\n`);
169
+ })
170
+ .catch(() => {
171
+ console.info(`Open this link to manually create a release: \n` +
172
+ chalk.underline(chalk.cyan(result.url)) +
173
+ '\n');
174
+ });
175
+ }
176
+ async promptForContinueInGitLab() {
177
+ try {
178
+ const reply = await (0, enquirer_1.prompt)([
179
+ {
180
+ name: 'open',
181
+ message: 'Do you want to create the release manually in your browser?',
182
+ type: 'autocomplete',
183
+ choices: [
184
+ {
185
+ name: 'Yes',
186
+ hint: 'It will open the GitLab release page for you',
187
+ },
188
+ {
189
+ name: 'No',
190
+ },
191
+ ],
192
+ initial: 0,
193
+ },
194
+ ]);
195
+ return reply.open === 'Yes';
196
+ }
197
+ catch {
198
+ // Ensure the cursor is always restored before exiting
199
+ process.stdout.write('\u001b[?25h');
200
+ // Handle the case where the user exits the prompt with ctrl+c
201
+ process.exit(1);
202
+ }
203
+ }
204
+ /**
205
+ * Format references for the release (e.g., MRs, issues)
206
+ */
207
+ formatReferences(references) {
208
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
209
+ const providerToRefSpec = {
210
+ gitlab: {
211
+ 'pull-request': 'merge_requests',
212
+ hash: 'commit',
213
+ issue: 'issues',
214
+ },
215
+ };
216
+ const refSpec = providerToRefSpec.gitlab;
217
+ const formatSingleReference = (ref) => {
218
+ return `https://${gitlabRepoData.hostname}/${gitlabRepoData.slug}/-/${refSpec[ref.type]}/${ref.value.replace(/^[#!]/, '')}`;
219
+ };
220
+ const mr = references.filter((ref) => ref.type === 'pull-request');
221
+ const issue = references.filter((ref) => ref.type === 'issue');
222
+ if (mr.length > 0 || issue.length > 0) {
223
+ return (' (' +
224
+ [...mr, ...issue].map((ref) => formatSingleReference(ref)).join(', ') +
225
+ ')');
226
+ }
227
+ if (references.length > 0) {
228
+ return ' (' + formatSingleReference(references[0]) + ')';
229
+ }
230
+ return '';
231
+ }
232
+ async syncRelease(remoteReleaseOptions, existingRelease) {
233
+ const gitlabReleaseData = {
234
+ tag_name: remoteReleaseOptions.version,
235
+ name: remoteReleaseOptions.version,
236
+ description: remoteReleaseOptions.body,
237
+ prerelease: remoteReleaseOptions.prerelease,
238
+ ref: remoteReleaseOptions.commit,
239
+ released_at: new Date().toISOString(),
240
+ assets: { links: [] },
241
+ milestones: [],
242
+ };
243
+ try {
244
+ const newGlRelease = await (existingRelease
245
+ ? this.updateRelease(existingRelease.id, gitlabReleaseData)
246
+ : this.createRelease(gitlabReleaseData));
247
+ const gitlabRepoData = this.getRequiredRemoteRepoData();
248
+ return {
249
+ status: existingRelease ? 'updated' : 'created',
250
+ id: newGlRelease.tag_name,
251
+ url: `https://${gitlabRepoData.hostname}/${gitlabRepoData.slug}/-/tags/${encodeURIComponent(remoteReleaseOptions.version)}`,
252
+ };
253
+ }
254
+ catch (error) {
255
+ return {
256
+ status: 'manual',
257
+ error,
258
+ url: this.getManualRemoteReleaseURL(remoteReleaseOptions),
259
+ requestData: gitlabReleaseData,
260
+ };
261
+ }
262
+ }
263
+ getRequiredRemoteRepoData() {
264
+ const gitlabRepoData = this.getRemoteRepoData();
265
+ if (!gitlabRepoData) {
266
+ throw new Error(`No remote repo data could be resolved for the current workspace`);
267
+ }
268
+ return gitlabRepoData;
269
+ }
270
+ }
271
+ exports.GitLabRemoteReleaseClient = GitLabRemoteReleaseClient;