nx 21.0.0-beta.11 → 21.0.0-beta.12

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 (45) hide show
  1. package/migrations.json +10 -5
  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 +3 -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/release/changelog.d.ts +3 -2
  9. package/src/command-line/release/changelog.js +57 -70
  10. package/src/command-line/release/command-object.d.ts +1 -1
  11. package/src/command-line/release/config/config.d.ts +8 -1
  12. package/src/command-line/release/config/config.js +18 -11
  13. package/src/command-line/release/release.js +30 -18
  14. package/src/command-line/release/utils/git.d.ts +1 -0
  15. package/src/command-line/release/utils/git.js +27 -8
  16. package/src/command-line/release/utils/remote-release-clients/github.d.ts +57 -0
  17. package/src/command-line/release/utils/remote-release-clients/github.js +309 -0
  18. package/src/command-line/release/utils/remote-release-clients/gitlab.d.ts +62 -0
  19. package/src/command-line/release/utils/remote-release-clients/gitlab.js +271 -0
  20. package/src/command-line/release/utils/remote-release-clients/remote-release-client.d.ts +111 -0
  21. package/src/command-line/release/utils/remote-release-clients/remote-release-client.js +136 -0
  22. package/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  23. package/src/command-line/yargs-utils/shared-options.js +22 -3
  24. package/src/config/nx-json.d.ts +8 -1
  25. package/src/core/graph/main.js +1 -1
  26. package/src/core/graph/styles.css +1 -1
  27. package/src/migrations/update-21-0-0/release-changelog-config-changes.d.ts +2 -0
  28. package/src/migrations/update-21-0-0/release-changelog-config-changes.js +38 -0
  29. package/src/native/index.d.ts +6 -1
  30. package/src/native/native-bindings.js +1 -0
  31. package/src/native/native-file-cache-location.js +2 -1
  32. package/src/native/nx.wasm32-wasi.wasm +0 -0
  33. package/src/project-graph/plugins/get-plugins.js +19 -14
  34. package/src/tasks-runner/is-tui-enabled.d.ts +16 -1
  35. package/src/tasks-runner/is-tui-enabled.js +40 -28
  36. package/src/tasks-runner/run-command.js +3 -3
  37. package/src/tasks-runner/running-tasks/node-child-process.d.ts +1 -0
  38. package/src/tasks-runner/running-tasks/node-child-process.js +7 -0
  39. package/src/tasks-runner/task-orchestrator.js +6 -3
  40. package/src/utils/is-ci.d.ts +1 -1
  41. package/src/utils/is-ci.js +4 -1
  42. package/src/utils/package-manager.d.ts +1 -0
  43. package/src/utils/package-manager.js +29 -16
  44. package/src/command-line/release/utils/github.d.ts +0 -32
  45. package/src/command-line/release/utils/github.js +0 -326
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultCreateReleaseProvider = exports.DEFAULT_VERSION_ACTIONS_PATH = exports.IMPLICIT_DEFAULT_RELEASE_GROUP = void 0;
3
+ exports.DEFAULT_VERSION_ACTIONS_PATH = exports.IMPLICIT_DEFAULT_RELEASE_GROUP = void 0;
4
4
  exports.createNxReleaseConfig = createNxReleaseConfig;
5
5
  exports.handleNxReleaseConfigError = handleNxReleaseConfigError;
6
6
  /**
@@ -23,6 +23,8 @@ const find_matching_projects_1 = require("../../../utils/find-matching-projects"
23
23
  const output_1 = require("../../../utils/output");
24
24
  const path_1 = require("../../../utils/path");
25
25
  const workspace_root_1 = require("../../../utils/workspace-root");
26
+ const github_1 = require("../utils/remote-release-clients/github");
27
+ const gitlab_1 = require("../utils/remote-release-clients/gitlab");
26
28
  const resolve_changelog_renderer_1 = require("../utils/resolve-changelog-renderer");
27
29
  const resolve_nx_json_error_message_1 = require("../utils/resolve-nx-json-error-message");
28
30
  const conventional_commits_1 = require("./conventional-commits");
@@ -170,7 +172,7 @@ async function createNxReleaseConfig(projectGraph, projectFileMap, userConfig =
170
172
  renderer: defaultRendererPath,
171
173
  renderOptions: {
172
174
  authors: true,
173
- mapAuthorsToGitHubUsernames: true,
175
+ applyUsernameToAuthors: true,
174
176
  commitReferences: true,
175
177
  versionTitleDate: true,
176
178
  },
@@ -184,7 +186,7 @@ async function createNxReleaseConfig(projectGraph, projectFileMap, userConfig =
184
186
  renderer: defaultRendererPath,
185
187
  renderOptions: {
186
188
  authors: true,
187
- mapAuthorsToGitHubUsernames: true,
189
+ applyUsernameToAuthors: true,
188
190
  commitReferences: true,
189
191
  versionTitleDate: true,
190
192
  },
@@ -225,7 +227,7 @@ async function createNxReleaseConfig(projectGraph, projectFileMap, userConfig =
225
227
  renderer: defaultRendererPath,
226
228
  renderOptions: {
227
229
  authors: true,
228
- mapAuthorsToGitHubUsernames: true,
230
+ applyUsernameToAuthors: true,
229
231
  commitReferences: true,
230
232
  versionTitleDate: true,
231
233
  },
@@ -930,13 +932,11 @@ const supportedCreateReleaseProviders = [
930
932
  name: 'github-enterprise-server',
931
933
  defaultApiBaseUrl: 'https://__hostname__/api/v3',
932
934
  },
935
+ {
936
+ name: 'gitlab',
937
+ defaultApiBaseUrl: 'https://__hostname__/api/v4',
938
+ },
933
939
  ];
934
- // User opts into the default by specifying the string value 'github'
935
- exports.defaultCreateReleaseProvider = {
936
- provider: 'github',
937
- hostname: 'github.com',
938
- apiBaseUrl: 'https://api.github.com',
939
- };
940
940
  function validateCreateReleaseConfig(changelogConfig) {
941
941
  const createRelease = changelogConfig.createRelease;
942
942
  // Disabled: valid
@@ -945,7 +945,14 @@ function validateCreateReleaseConfig(changelogConfig) {
945
945
  }
946
946
  // GitHub shorthand, expand to full object form, mark as valid
947
947
  if (createRelease === 'github') {
948
- changelogConfig.createRelease = exports.defaultCreateReleaseProvider;
948
+ changelogConfig.createRelease =
949
+ github_1.defaultCreateReleaseProvider;
950
+ return null;
951
+ }
952
+ // Gitlab shorthand, expand to full object form, mark as valid
953
+ if (createRelease === 'gitlab') {
954
+ changelogConfig.createRelease =
955
+ gitlab_1.defaultCreateReleaseProvider;
949
956
  return null;
950
957
  }
951
958
  // Object config, ensure that properties are valid
@@ -17,8 +17,8 @@ const use_legacy_versioning_1 = require("./config/use-legacy-versioning");
17
17
  const version_plans_1 = require("./config/version-plans");
18
18
  const publish_1 = require("./publish");
19
19
  const git_1 = require("./utils/git");
20
- const github_1 = require("./utils/github");
21
20
  const print_config_1 = require("./utils/print-config");
21
+ const remote_release_client_1 = require("./utils/remote-release-clients/remote-release-client");
22
22
  const resolve_nx_json_error_message_1 = require("./utils/resolve-nx-json-error-message");
23
23
  const shared_1 = require("./utils/shared");
24
24
  const version_1 = require("./version");
@@ -84,10 +84,10 @@ function createAPI(overrideReleaseConfig) {
84
84
  const shouldCommit = userProvidedReleaseConfig.git?.commit ?? true;
85
85
  const shouldStage = (shouldCommit || userProvidedReleaseConfig.git?.stageChanges) ?? false;
86
86
  const shouldTag = userProvidedReleaseConfig.git?.tag ?? true;
87
- const shouldCreateWorkspaceRelease = (0, changelog_1.shouldCreateGitHubRelease)(nxReleaseConfig.changelog.workspaceChangelog);
88
- // If the workspace or any of the release groups specify that a github release should be created, we need to push the changes to the remote
89
- const shouldPush = (shouldCreateWorkspaceRelease ||
90
- releaseGroups.some((group) => (0, changelog_1.shouldCreateGitHubRelease)(group.changelog))) ??
87
+ const shouldCreateWorkspaceRemoteRelease = shouldCreateRemoteRelease(nxReleaseConfig.changelog.workspaceChangelog);
88
+ // If the workspace or any of the release groups specify that a remote release should be created, we need to push the changes to the remote
89
+ const shouldPush = (shouldCreateWorkspaceRemoteRelease ||
90
+ releaseGroups.some((group) => shouldCreateRemoteRelease(group.changelog))) ??
91
91
  false;
92
92
  const versionResult = await releaseVersion({
93
93
  ...args,
@@ -179,19 +179,27 @@ function createAPI(overrideReleaseConfig) {
179
179
  hasPushedChanges = true;
180
180
  }
181
181
  let latestCommit;
182
- if (shouldCreateWorkspaceRelease && changelogResult.workspaceChangelog) {
182
+ if (shouldCreateWorkspaceRemoteRelease &&
183
+ changelogResult.workspaceChangelog) {
184
+ const remoteReleaseClient = await (0, remote_release_client_1.createRemoteReleaseClient)(
185
+ // shouldCreateWorkspaceRemoteRelease() ensures that the createRelease property exists and is not false
186
+ nxReleaseConfig.changelog.workspaceChangelog
187
+ .createRelease);
183
188
  if (!hasPushedChanges) {
184
- throw new Error('It is not possible to create a github release for the workspace without pushing the changes to the remote, please ensure that you have not disabled git push in your nx release config');
189
+ throw new Error(`It is not possible to create a ${remoteReleaseClient.remoteReleaseProviderName} release for the workspace without pushing the changes to the remote, please ensure that you have not disabled git push in your nx release config`);
185
190
  }
186
- output_1.output.logSingleLine(`Creating GitHub Release`);
191
+ output_1.output.logSingleLine(`Creating ${remoteReleaseClient.remoteReleaseProviderName} Release`);
187
192
  latestCommit = await (0, git_1.getCommitHash)('HEAD');
188
- await (0, github_1.createOrUpdateGithubRelease)(nxReleaseConfig.changelog.workspaceChangelog
189
- ? nxReleaseConfig.changelog.workspaceChangelog.createRelease
190
- : false, changelogResult.workspaceChangelog.releaseVersion, changelogResult.workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
193
+ await remoteReleaseClient.createOrUpdateRelease(changelogResult.workspaceChangelog.releaseVersion, changelogResult.workspaceChangelog.contents, latestCommit, { dryRun: args.dryRun });
191
194
  }
192
195
  for (const releaseGroup of releaseGroups) {
193
- const shouldCreateProjectReleases = (0, changelog_1.shouldCreateGitHubRelease)(releaseGroup.changelog);
194
- if (shouldCreateProjectReleases && changelogResult.projectChangelogs) {
196
+ const shouldCreateProjectRemoteReleases = shouldCreateRemoteRelease(releaseGroup.changelog);
197
+ if (shouldCreateProjectRemoteReleases &&
198
+ changelogResult.projectChangelogs) {
199
+ const remoteReleaseClient = await (0, remote_release_client_1.createRemoteReleaseClient)(
200
+ // shouldCreateProjectRemoteReleases() ensures that the createRelease property exists and is not false
201
+ releaseGroup.changelog
202
+ .createRelease);
195
203
  const projects = args.projects?.length
196
204
  ? // If the user has passed a list of projects, we need to use the filtered list of projects within the release group
197
205
  Array.from(releaseGroupToFilteredProjects.get(releaseGroup))
@@ -204,15 +212,13 @@ function createAPI(overrideReleaseConfig) {
204
212
  continue;
205
213
  }
206
214
  if (!hasPushedChanges) {
207
- throw new Error('It is not possible to create a github release for the project without pushing the changes to the remote, please ensure that you have not disabled git push in your nx release config');
215
+ throw new Error(`It is not possible to create a ${remoteReleaseClient.remoteReleaseProviderName} release for the project without pushing the changes to the remote, please ensure that you have not disabled git push in your nx release config`);
208
216
  }
209
- output_1.output.logSingleLine(`Creating GitHub Release`);
217
+ output_1.output.logSingleLine(`Creating ${remoteReleaseClient.remoteReleaseProviderName} Release`);
210
218
  if (!latestCommit) {
211
219
  latestCommit = await (0, git_1.getCommitHash)('HEAD');
212
220
  }
213
- await (0, github_1.createOrUpdateGithubRelease)(releaseGroup.changelog
214
- ? releaseGroup.changelog.createRelease
215
- : false, changelog.releaseVersion, changelog.contents, latestCommit, { dryRun: args.dryRun });
221
+ await remoteReleaseClient.createOrUpdateRelease(changelog.releaseVersion, changelog.contents, latestCommit, { dryRun: args.dryRun });
216
222
  }
217
223
  }
218
224
  }
@@ -253,3 +259,9 @@ async function promptForPublish() {
253
259
  return false;
254
260
  }
255
261
  }
262
+ function shouldCreateRemoteRelease(changelogConfig) {
263
+ if (changelogConfig === false) {
264
+ return false;
265
+ }
266
+ return changelogConfig.createRelease !== false;
267
+ }
@@ -63,6 +63,7 @@ export declare function parseConventionalCommitsMessage(message: string): {
63
63
  description: string;
64
64
  breaking: boolean;
65
65
  } | null;
66
+ export declare function extractReferencesFromCommit(commit: RawGitCommit): Reference[];
66
67
  export declare function parseGitCommit(commit: RawGitCommit, isVersionPlanCommit?: boolean): GitCommit | null;
67
68
  export declare function getCommitHash(ref: string): Promise<string>;
68
69
  export declare function getFirstGitCommit(): Promise<string>;
@@ -8,6 +8,7 @@ exports.gitTag = gitTag;
8
8
  exports.gitPush = gitPush;
9
9
  exports.parseCommits = parseCommits;
10
10
  exports.parseConventionalCommitsMessage = parseConventionalCommitsMessage;
11
+ exports.extractReferencesFromCommit = extractReferencesFromCommit;
11
12
  exports.parseGitCommit = parseGitCommit;
12
13
  exports.getCommitHash = getCommitHash;
13
14
  exports.getFirstGitCommit = getFirstGitCommit;
@@ -362,17 +363,32 @@ function parseConventionalCommitsMessage(message) {
362
363
  breaking: Boolean(match.groups.breaking),
363
364
  };
364
365
  }
365
- function extractReferencesFromCommitMessage(message, shortHash) {
366
+ function extractReferencesFromCommit(commit) {
366
367
  const references = [];
367
- for (const m of message.matchAll(PullRequestRE)) {
368
+ // Extract GitHub style PR references from commit message
369
+ for (const m of commit.message.matchAll(PullRequestRE)) {
368
370
  references.push({ type: 'pull-request', value: m[1] });
369
371
  }
370
- for (const m of message.matchAll(IssueRE)) {
372
+ // Extract GitLab style merge request references from commit body
373
+ for (const m of commit.body.matchAll(GitLabMergeRequestRE)) {
374
+ if (m[1]) {
375
+ references.push({ type: 'pull-request', value: m[1] });
376
+ }
377
+ }
378
+ // Extract issue references from commit message
379
+ for (const m of commit.message.matchAll(IssueRE)) {
380
+ if (!references.some((i) => i.value === m[1])) {
381
+ references.push({ type: 'issue', value: m[1] });
382
+ }
383
+ }
384
+ // Extract issue references from commit body
385
+ for (const m of commit.body.matchAll(IssueRE)) {
371
386
  if (!references.some((i) => i.value === m[1])) {
372
387
  references.push({ type: 'issue', value: m[1] });
373
388
  }
374
389
  }
375
- references.push({ value: shortHash, type: 'hash' });
390
+ // Add commit hash reference
391
+ references.push({ value: commit.shortHash, type: 'hash' });
376
392
  return references;
377
393
  }
378
394
  function getAllAuthorsForCommit(commit) {
@@ -390,7 +406,10 @@ function getAllAuthorsForCommit(commit) {
390
406
  // https://regex101.com/r/FSfNvA/1
391
407
  const ConventionalCommitRegex = /(?<type>[a-z]+)(\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
392
408
  const CoAuthoredByRegex = /co-authored-by:\s*(?<name>.+)(<(?<email>.+)>)/gim;
409
+ // GitHub style PR references
393
410
  const PullRequestRE = /\([ a-z]*(#\d+)\s*\)/gm;
411
+ // GitLab style merge request references
412
+ const GitLabMergeRequestRE = /See merge request (?:[a-z0-9/-]+)?(![\d]+)/gim;
394
413
  const IssueRE = /(#\d+)/gm;
395
414
  const ChangedFileRegex = /(A|M|D|R\d*|C\d*)\t([^\t\n]*)\t?(.*)?/gm;
396
415
  const RevertHashRE = /This reverts commit (?<hash>[\da-f]{40})./gm;
@@ -402,7 +421,7 @@ function parseGitCommit(commit, isVersionPlanCommit = false) {
402
421
  description: commit.message,
403
422
  type: '',
404
423
  scope: '',
405
- references: extractReferencesFromCommitMessage(commit.message, commit.shortHash),
424
+ references: extractReferencesFromCommit(commit),
406
425
  // The commit message is not the source of truth for a breaking (major) change in version plans, so the value is not relevant
407
426
  // TODO(v22): Make the current GitCommit interface more clearly tied to conventional commits
408
427
  isBreaking: false,
@@ -420,9 +439,9 @@ function parseGitCommit(commit, isVersionPlanCommit = false) {
420
439
  const scope = parsedMessage.scope;
421
440
  const isBreaking = parsedMessage.breaking || commit.body.includes('BREAKING CHANGE:');
422
441
  let description = parsedMessage.description;
423
- // Extract references from message
424
- const references = extractReferencesFromCommitMessage(description, commit.shortHash);
425
- // Remove references and normalize
442
+ // Extract issue and PR references from the commit
443
+ const references = extractReferencesFromCommit(commit);
444
+ // Remove GitHub style references from description (NOTE: GitLab style references only seem to appear in the body, so we don't need to remove them here)
426
445
  description = description.replace(PullRequestRE, '').trim();
427
446
  let type = parsedMessage.type;
428
447
  // Extract any reverted hashes, if applicable
@@ -0,0 +1,57 @@
1
+ import type { PostGitTask } from '../../changelog';
2
+ import { type ResolvedCreateRemoteReleaseProvider } from '../../config/config';
3
+ import { Reference } from '../git';
4
+ import { ReleaseVersion } from '../shared';
5
+ import { RemoteReleaseClient, RemoteReleaseOptions, RemoteReleaseResult, RemoteRepoData } from './remote-release-client';
6
+ export interface GithubRepoData extends RemoteRepoData {
7
+ }
8
+ export interface GithubRemoteRelease {
9
+ id?: string;
10
+ body: string;
11
+ tag_name: string;
12
+ target_commitish?: string;
13
+ name?: string;
14
+ draft?: boolean;
15
+ prerelease?: boolean;
16
+ make_latest?: 'legacy' | boolean;
17
+ }
18
+ export declare const defaultCreateReleaseProvider: ResolvedCreateRemoteReleaseProvider;
19
+ export declare class GithubRemoteReleaseClient extends RemoteReleaseClient<GithubRemoteRelease> {
20
+ remoteReleaseProviderName: string;
21
+ /**
22
+ * Get GitHub repository data from git remote
23
+ */
24
+ static resolveRepoData(createReleaseConfig: false | ResolvedCreateRemoteReleaseProvider, remoteName?: string): GithubRepoData | null;
25
+ /**
26
+ * Resolve a GitHub token from environment variables or gh CLI
27
+ */
28
+ static resolveTokenData(hostname: string): Promise<{
29
+ token: string;
30
+ headerName: string;
31
+ } | null>;
32
+ createPostGitTask(releaseVersion: ReleaseVersion, changelogContents: string, dryRun: boolean): PostGitTask;
33
+ applyUsernameToAuthors(authors: Map<string, {
34
+ email: Set<string>;
35
+ username?: string;
36
+ }>): Promise<void>;
37
+ /**
38
+ * Get a release by tag
39
+ */
40
+ protected getReleaseByTag(tag: string): Promise<GithubRemoteRelease>;
41
+ /**
42
+ * Create a new release
43
+ */
44
+ protected createRelease(remoteRelease: GithubRemoteRelease): Promise<any>;
45
+ protected updateRelease(id: string, remoteRelease: GithubRemoteRelease): Promise<any>;
46
+ protected getManualRemoteReleaseURL(remoteReleaseOptions: RemoteReleaseOptions): string;
47
+ protected handleAuthError(): void;
48
+ protected logReleaseAction(existingRelease: GithubRemoteRelease | undefined, gitTag: string, dryRun: boolean): void;
49
+ protected handleError(error: any, result: RemoteReleaseResult): Promise<void>;
50
+ private promptForContinueInGitHub;
51
+ /**
52
+ * Format references for the release (e.g., PRs, issues)
53
+ */
54
+ formatReferences(references: Reference[]): string;
55
+ protected syncRelease(remoteReleaseOptions: RemoteReleaseOptions, existingRelease?: GithubRemoteRelease): Promise<RemoteReleaseResult>;
56
+ private getRequiredRemoteRepoData;
57
+ }
@@ -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;