feat(release): support github enterprise server (#26482)
This commit is contained in:
parent
0d37ef98da
commit
21d1696ef5
@ -4,6 +4,7 @@ import { NxReleaseConfig } from '../../src/command-line/release/config/config';
|
|||||||
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from '../../src/command-line/release/config/conventional-commits';
|
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from '../../src/command-line/release/config/conventional-commits';
|
||||||
import { GitCommit } from '../../src/command-line/release/utils/git';
|
import { GitCommit } from '../../src/command-line/release/utils/git';
|
||||||
import {
|
import {
|
||||||
|
GithubRepoData,
|
||||||
RepoSlug,
|
RepoSlug,
|
||||||
formatReferences,
|
formatReferences,
|
||||||
} from '../../src/command-line/release/utils/github';
|
} from '../../src/command-line/release/utils/github';
|
||||||
@ -42,6 +43,7 @@ export type DependencyBump = {
|
|||||||
* @param {string | false} config.entryWhenNoChanges The (already interpolated) string to use as the changelog entry when there are no changes, or `false` if no entry should be generated
|
* @param {string | false} config.entryWhenNoChanges The (already interpolated) string to use as the changelog entry when there are no changes, or `false` if no entry should be generated
|
||||||
* @param {ChangelogRenderOptions} config.changelogRenderOptions The options specific to the ChangelogRenderer implementation
|
* @param {ChangelogRenderOptions} config.changelogRenderOptions The options specific to the ChangelogRenderer implementation
|
||||||
* @param {DependencyBump[]} config.dependencyBumps Optional list of additional dependency bumps that occurred as part of the release, outside of the commit data
|
* @param {DependencyBump[]} config.dependencyBumps Optional list of additional dependency bumps that occurred as part of the release, outside of the commit data
|
||||||
|
* @param {GithubRepoData} config.repoData Resolved data for the current GitHub repository
|
||||||
*/
|
*/
|
||||||
export type ChangelogRenderer = (config: {
|
export type ChangelogRenderer = (config: {
|
||||||
projectGraph: ProjectGraph;
|
projectGraph: ProjectGraph;
|
||||||
@ -53,7 +55,9 @@ export type ChangelogRenderer = (config: {
|
|||||||
entryWhenNoChanges: string | false;
|
entryWhenNoChanges: string | false;
|
||||||
changelogRenderOptions: DefaultChangelogRenderOptions;
|
changelogRenderOptions: DefaultChangelogRenderOptions;
|
||||||
dependencyBumps?: DependencyBump[];
|
dependencyBumps?: DependencyBump[];
|
||||||
|
// TODO(v20): remove repoSlug in favour of repoData
|
||||||
repoSlug?: RepoSlug;
|
repoSlug?: RepoSlug;
|
||||||
|
repoData?: GithubRepoData;
|
||||||
// TODO(v20): Evaluate if there is a cleaner way to configure this when breaking changes are allowed
|
// TODO(v20): Evaluate if there is a cleaner way to configure this when breaking changes are allowed
|
||||||
// null if version plans are being used to generate the changelog
|
// null if version plans are being used to generate the changelog
|
||||||
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] | null;
|
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] | null;
|
||||||
@ -101,6 +105,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
|
|||||||
dependencyBumps,
|
dependencyBumps,
|
||||||
repoSlug,
|
repoSlug,
|
||||||
conventionalCommitsConfig,
|
conventionalCommitsConfig,
|
||||||
|
repoData,
|
||||||
}): Promise<string> => {
|
}): Promise<string> => {
|
||||||
const markdownLines: string[] = [];
|
const markdownLines: string[] = [];
|
||||||
|
|
||||||
@ -148,7 +153,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
|
|||||||
change,
|
change,
|
||||||
changelogRenderOptions,
|
changelogRenderOptions,
|
||||||
isVersionPlans,
|
isVersionPlans,
|
||||||
repoSlug
|
repoData
|
||||||
);
|
);
|
||||||
breakingChanges.push(line);
|
breakingChanges.push(line);
|
||||||
relevantChanges.splice(i, 1);
|
relevantChanges.splice(i, 1);
|
||||||
@ -222,7 +227,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
|
|||||||
change,
|
change,
|
||||||
changelogRenderOptions,
|
changelogRenderOptions,
|
||||||
isVersionPlans,
|
isVersionPlans,
|
||||||
repoSlug
|
repoData
|
||||||
);
|
);
|
||||||
markdownLines.push(line);
|
markdownLines.push(line);
|
||||||
if (change.isBreaking) {
|
if (change.isBreaking) {
|
||||||
@ -295,7 +300,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
|
|||||||
change,
|
change,
|
||||||
changelogRenderOptions,
|
changelogRenderOptions,
|
||||||
isVersionPlans,
|
isVersionPlans,
|
||||||
repoSlug
|
repoData
|
||||||
);
|
);
|
||||||
markdownLines.push(line + '\n');
|
markdownLines.push(line + '\n');
|
||||||
if (change.isBreaking) {
|
if (change.isBreaking) {
|
||||||
@ -350,7 +355,7 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to map authors to github usernames
|
// Try to map authors to github usernames
|
||||||
if (repoSlug && changelogRenderOptions.mapAuthorsToGitHubUsernames) {
|
if (repoData && changelogRenderOptions.mapAuthorsToGitHubUsernames) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
[..._authors.keys()].map(async (authorName) => {
|
[..._authors.keys()].map(async (authorName) => {
|
||||||
const meta = _authors.get(authorName);
|
const meta = _authors.get(authorName);
|
||||||
@ -455,7 +460,7 @@ function formatChange(
|
|||||||
change: ChangelogChange,
|
change: ChangelogChange,
|
||||||
changelogRenderOptions: DefaultChangelogRenderOptions,
|
changelogRenderOptions: DefaultChangelogRenderOptions,
|
||||||
isVersionPlans: boolean,
|
isVersionPlans: boolean,
|
||||||
repoSlug?: RepoSlug
|
repoData?: GithubRepoData
|
||||||
): string {
|
): string {
|
||||||
let description = change.description;
|
let description = change.description;
|
||||||
let extraLines = [];
|
let extraLines = [];
|
||||||
@ -480,8 +485,8 @@ function formatChange(
|
|||||||
(!isVersionPlans && change.isBreaking ? '⚠️ ' : '') +
|
(!isVersionPlans && change.isBreaking ? '⚠️ ' : '') +
|
||||||
(!isVersionPlans && change.scope ? `**${change.scope.trim()}:** ` : '') +
|
(!isVersionPlans && change.scope ? `**${change.scope.trim()}:** ` : '') +
|
||||||
description;
|
description;
|
||||||
if (repoSlug && changelogRenderOptions.commitReferences) {
|
if (repoData && changelogRenderOptions.commitReferences) {
|
||||||
changeLine += formatReferences(change.githubReferences, repoSlug);
|
changeLine += formatReferences(change.githubReferences, repoData);
|
||||||
}
|
}
|
||||||
if (extraLinesStr) {
|
if (extraLinesStr) {
|
||||||
changeLine += '\n\n' + extraLinesStr;
|
changeLine += '\n\n' + extraLinesStr;
|
||||||
|
|||||||
@ -691,6 +691,9 @@
|
|||||||
{
|
{
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"enum": [false]
|
"enum": [false]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/CreateReleaseProviderConfiguration"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -724,6 +727,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CreateReleaseProviderConfiguration": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"provider": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["github-enterprise-server"]
|
||||||
|
},
|
||||||
|
"hostname": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The hostname of the VCS provider instance, e.g. github.example.com"
|
||||||
|
},
|
||||||
|
"apiBaseUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The base URL for the relevant VCS provider API. If not set, this will default to `https://${hostname}/api/v3`"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["provider", "hostname"]
|
||||||
|
},
|
||||||
"NxReleaseVersionPlansConfiguration": {
|
"NxReleaseVersionPlansConfiguration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { ChangelogOptions } from './command-object';
|
|||||||
import {
|
import {
|
||||||
NxReleaseConfig,
|
NxReleaseConfig,
|
||||||
createNxReleaseConfig,
|
createNxReleaseConfig,
|
||||||
|
defaultCreateReleaseProvider,
|
||||||
handleNxReleaseConfigError,
|
handleNxReleaseConfigError,
|
||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { deepMergeJson } from './config/deep-merge-json';
|
import { deepMergeJson } from './config/deep-merge-json';
|
||||||
@ -58,7 +59,7 @@ import {
|
|||||||
parseCommits,
|
parseCommits,
|
||||||
parseGitCommit,
|
parseGitCommit,
|
||||||
} from './utils/git';
|
} from './utils/git';
|
||||||
import { createOrUpdateGithubRelease, getGitHubRepoSlug } from './utils/github';
|
import { createOrUpdateGithubRelease, getGitHubRepoData } from './utils/github';
|
||||||
import { launchEditor } from './utils/launch-editor';
|
import { launchEditor } from './utils/launch-editor';
|
||||||
import { parseChangelogMarkdown } from './utils/markdown';
|
import { parseChangelogMarkdown } from './utils/markdown';
|
||||||
import { printAndFlushChanges } from './utils/print-changes';
|
import { printAndFlushChanges } from './utils/print-changes';
|
||||||
@ -411,6 +412,9 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
output.logSingleLine(`Creating GitHub Release`);
|
output.logSingleLine(`Creating GitHub Release`);
|
||||||
|
|
||||||
await createOrUpdateGithubRelease(
|
await createOrUpdateGithubRelease(
|
||||||
|
nxReleaseConfig.changelog.workspaceChangelog
|
||||||
|
? nxReleaseConfig.changelog.workspaceChangelog.createRelease
|
||||||
|
: defaultCreateReleaseProvider,
|
||||||
workspaceChangelog.releaseVersion,
|
workspaceChangelog.releaseVersion,
|
||||||
workspaceChangelog.contents,
|
workspaceChangelog.contents,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
@ -644,6 +648,9 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
output.logSingleLine(`Creating GitHub Release`);
|
output.logSingleLine(`Creating GitHub Release`);
|
||||||
|
|
||||||
await createOrUpdateGithubRelease(
|
await createOrUpdateGithubRelease(
|
||||||
|
releaseGroup.changelog
|
||||||
|
? releaseGroup.changelog.createRelease
|
||||||
|
: defaultCreateReleaseProvider,
|
||||||
projectChangelog.releaseVersion,
|
projectChangelog.releaseVersion,
|
||||||
projectChangelog.contents,
|
projectChangelog.contents,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
@ -797,6 +804,9 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
output.logSingleLine(`Creating GitHub Release`);
|
output.logSingleLine(`Creating GitHub Release`);
|
||||||
|
|
||||||
await createOrUpdateGithubRelease(
|
await createOrUpdateGithubRelease(
|
||||||
|
releaseGroup.changelog
|
||||||
|
? releaseGroup.changelog.createRelease
|
||||||
|
: defaultCreateReleaseProvider,
|
||||||
projectChangelog.releaseVersion,
|
projectChangelog.releaseVersion,
|
||||||
projectChangelog.contents,
|
projectChangelog.contents,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
@ -1110,7 +1120,7 @@ async function generateChangelogForWorkspace({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const githubRepoSlug = getGitHubRepoSlug(gitRemote);
|
const githubRepoData = getGitHubRepoData(gitRemote, config.createRelease);
|
||||||
|
|
||||||
let contents = await changelogRenderer({
|
let contents = await changelogRenderer({
|
||||||
projectGraph,
|
projectGraph,
|
||||||
@ -1118,7 +1128,8 @@ async function generateChangelogForWorkspace({
|
|||||||
commits,
|
commits,
|
||||||
releaseVersion: releaseVersion.rawVersion,
|
releaseVersion: releaseVersion.rawVersion,
|
||||||
project: null,
|
project: null,
|
||||||
repoSlug: githubRepoSlug,
|
repoSlug: githubRepoData?.slug,
|
||||||
|
repoData: githubRepoData,
|
||||||
entryWhenNoChanges: config.entryWhenNoChanges,
|
entryWhenNoChanges: config.entryWhenNoChanges,
|
||||||
changelogRenderOptions: config.renderOptions,
|
changelogRenderOptions: config.renderOptions,
|
||||||
conventionalCommitsConfig: nxReleaseConfig.conventionalCommits,
|
conventionalCommitsConfig: nxReleaseConfig.conventionalCommits,
|
||||||
@ -1250,10 +1261,7 @@ async function generateChangelogForProjects({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const githubRepoSlug =
|
const githubRepoData = getGitHubRepoData(gitRemote, config.createRelease);
|
||||||
config.createRelease === 'github'
|
|
||||||
? getGitHubRepoSlug(gitRemote)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
let contents = await changelogRenderer({
|
let contents = await changelogRenderer({
|
||||||
projectGraph,
|
projectGraph,
|
||||||
@ -1261,7 +1269,8 @@ async function generateChangelogForProjects({
|
|||||||
commits,
|
commits,
|
||||||
releaseVersion: releaseVersion.rawVersion,
|
releaseVersion: releaseVersion.rawVersion,
|
||||||
project: project.name,
|
project: project.name,
|
||||||
repoSlug: githubRepoSlug,
|
repoSlug: githubRepoData?.slug,
|
||||||
|
repoData: githubRepoData,
|
||||||
entryWhenNoChanges:
|
entryWhenNoChanges:
|
||||||
typeof config.entryWhenNoChanges === 'string'
|
typeof config.entryWhenNoChanges === 'string'
|
||||||
? interpolate(config.entryWhenNoChanges, {
|
? interpolate(config.entryWhenNoChanges, {
|
||||||
@ -1409,7 +1418,7 @@ export function shouldCreateGitHubRelease(
|
|||||||
return createReleaseArg === 'github';
|
return createReleaseArg === 'github';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (changelogConfig || {}).createRelease === 'github';
|
return (changelogConfig || {}).createRelease !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptForGitHubRelease(): Promise<boolean> {
|
async function promptForGitHubRelease(): Promise<boolean> {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,11 @@
|
|||||||
* and easy to consume config object for all the `nx release` command implementations.
|
* and easy to consume config object for all the `nx release` command implementations.
|
||||||
*/
|
*/
|
||||||
import { join, relative } from 'node:path';
|
import { join, relative } from 'node:path';
|
||||||
import { NxJsonConfiguration } from '../../../config/nx-json';
|
import { URL } from 'node:url';
|
||||||
|
import {
|
||||||
|
NxJsonConfiguration,
|
||||||
|
NxReleaseChangelogConfiguration,
|
||||||
|
} from '../../../config/nx-json';
|
||||||
import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph';
|
import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph';
|
||||||
import { readJsonFile } from '../../../utils/fileutils';
|
import { readJsonFile } from '../../../utils/fileutils';
|
||||||
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
||||||
@ -41,15 +45,6 @@ type RemoveTrueFromProperties<T, K extends keyof T> = {
|
|||||||
type RemoveTrueFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
|
type RemoveTrueFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
|
||||||
[U in keyof T]: RemoveTrueFromProperties<T[U], K>;
|
[U in keyof T]: RemoveTrueFromProperties<T[U], K>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type RemoveFalseFromType<T> = T extends false ? never : T;
|
|
||||||
type RemoveFalseFromProperties<T, K extends keyof T> = {
|
|
||||||
[P in keyof T]: P extends K ? RemoveFalseFromType<T[P]> : T[P];
|
|
||||||
};
|
|
||||||
type RemoveFalseFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
|
|
||||||
[U in keyof T]: RemoveFalseFromProperties<T[U], K>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RemoveBooleanFromType<T> = T extends boolean ? never : T;
|
type RemoveBooleanFromType<T> = T extends boolean ? never : T;
|
||||||
type RemoveBooleanFromProperties<T, K extends keyof T> = {
|
type RemoveBooleanFromProperties<T, K extends keyof T> = {
|
||||||
[P in keyof T]: P extends K ? RemoveBooleanFromType<T[P]> : T[P];
|
[P in keyof T]: P extends K ? RemoveBooleanFromType<T[P]> : T[P];
|
||||||
@ -111,7 +106,11 @@ export interface CreateNxReleaseConfigError {
|
|||||||
| 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE'
|
| 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE'
|
||||||
| 'PROJECT_MATCHES_MULTIPLE_GROUPS'
|
| 'PROJECT_MATCHES_MULTIPLE_GROUPS'
|
||||||
| 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS'
|
| 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS'
|
||||||
| 'GLOBAL_GIT_CONFIG_MIXED_WITH_GRANULAR_GIT_CONFIG';
|
| 'GLOBAL_GIT_CONFIG_MIXED_WITH_GRANULAR_GIT_CONFIG'
|
||||||
|
| 'CANNOT_RESOLVE_CHANGELOG_RENDERER'
|
||||||
|
| 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER'
|
||||||
|
| 'INVALID_CHANGELOG_CREATE_RELEASE_HOSTNAME'
|
||||||
|
| 'INVALID_CHANGELOG_CREATE_RELEASE_API_BASE_URL';
|
||||||
data: Record<string, string | string[]>;
|
data: Record<string, string | string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +565,16 @@ export async function createNxReleaseConfig(
|
|||||||
releaseGroups[releaseGroupName] = finalReleaseGroup;
|
releaseGroups[releaseGroupName] = finalReleaseGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureChangelogRenderersAreResolvable(releaseGroups, rootChangelogConfig);
|
const configError = validateChangelogConfig(
|
||||||
|
releaseGroups,
|
||||||
|
rootChangelogConfig
|
||||||
|
);
|
||||||
|
if (configError) {
|
||||||
|
return {
|
||||||
|
error: configError,
|
||||||
|
nxReleaseConfig: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
@ -766,6 +774,52 @@ export async function handleNxReleaseConfigError(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'CANNOT_RESOLVE_CHANGELOG_RENDERER': {
|
||||||
|
const nxJsonMessage = await resolveNxJsonConfigErrorMessage(['release']);
|
||||||
|
output.error({
|
||||||
|
title: `There was an error when resolving the configured changelog renderer at path: ${error.data.workspaceRelativePath}`,
|
||||||
|
bodyLines: [nxJsonMessage],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER':
|
||||||
|
{
|
||||||
|
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||||
|
'release',
|
||||||
|
]);
|
||||||
|
output.error({
|
||||||
|
title: `Your "changelog.createRelease" config specifies an unsupported provider "${
|
||||||
|
error.data.provider
|
||||||
|
}". The supported providers are ${(
|
||||||
|
error.data.supportedProviders as string[]
|
||||||
|
)
|
||||||
|
.map((p) => `"${p}"`)
|
||||||
|
.join(', ')}`,
|
||||||
|
bodyLines: [nxJsonMessage],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'INVALID_CHANGELOG_CREATE_RELEASE_HOSTNAME':
|
||||||
|
{
|
||||||
|
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||||
|
'release',
|
||||||
|
]);
|
||||||
|
output.error({
|
||||||
|
title: `Your "changelog.createRelease" config specifies an invalid hostname "${error.data.hostname}". Please ensure you provide a valid hostname value, such as "example.com"`,
|
||||||
|
bodyLines: [nxJsonMessage],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'INVALID_CHANGELOG_CREATE_RELEASE_API_BASE_URL':
|
||||||
|
{
|
||||||
|
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||||
|
'release',
|
||||||
|
]);
|
||||||
|
output.error({
|
||||||
|
title: `Your "changelog.createRelease" config specifies an invalid apiBaseUrl "${error.data.apiBaseUrl}". Please ensure you provide a valid URL value, such as "https://example.com"`,
|
||||||
|
bodyLines: [nxJsonMessage],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled error code: ${error.code}`);
|
throw new Error(`Unhandled error code: ${error.code}`);
|
||||||
}
|
}
|
||||||
@ -950,10 +1004,16 @@ function isProjectPublic(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureChangelogRenderersAreResolvable(
|
/**
|
||||||
|
* We need to ensure that changelog renderers are resolvable up front so that we do not end up erroring after performing
|
||||||
|
* actions later, and we also make sure that any configured createRelease options are valid.
|
||||||
|
*
|
||||||
|
* For the createRelease config, we also set a default apiBaseUrl if applicable.
|
||||||
|
*/
|
||||||
|
function validateChangelogConfig(
|
||||||
releaseGroups: NxReleaseConfig['groups'],
|
releaseGroups: NxReleaseConfig['groups'],
|
||||||
rootChangelogConfig: NxReleaseConfig['changelog']
|
rootChangelogConfig: NxReleaseConfig['changelog']
|
||||||
) {
|
): CreateNxReleaseConfigError | null {
|
||||||
/**
|
/**
|
||||||
* If any form of changelog config is enabled, ensure that any provided changelog renderers are resolvable
|
* If any form of changelog config is enabled, ensure that any provided changelog renderers are resolvable
|
||||||
* up front so that we do not end up erroring only after the versioning step has been completed.
|
* up front so that we do not end up erroring only after the versioning step has been completed.
|
||||||
@ -962,42 +1022,148 @@ function ensureChangelogRenderersAreResolvable(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
rootChangelogConfig.workspaceChangelog &&
|
rootChangelogConfig.workspaceChangelog &&
|
||||||
typeof rootChangelogConfig.workspaceChangelog !== 'boolean' &&
|
typeof rootChangelogConfig.workspaceChangelog !== 'boolean'
|
||||||
rootChangelogConfig.workspaceChangelog.renderer?.length
|
|
||||||
) {
|
) {
|
||||||
uniqueRendererPaths.add(rootChangelogConfig.workspaceChangelog.renderer);
|
if (rootChangelogConfig.workspaceChangelog.renderer?.length) {
|
||||||
|
uniqueRendererPaths.add(rootChangelogConfig.workspaceChangelog.renderer);
|
||||||
|
}
|
||||||
|
const createReleaseError = validateCreateReleaseConfig(
|
||||||
|
rootChangelogConfig.workspaceChangelog
|
||||||
|
);
|
||||||
|
if (createReleaseError) {
|
||||||
|
return createReleaseError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
rootChangelogConfig.projectChangelogs &&
|
rootChangelogConfig.projectChangelogs &&
|
||||||
typeof rootChangelogConfig.projectChangelogs !== 'boolean' &&
|
typeof rootChangelogConfig.projectChangelogs !== 'boolean'
|
||||||
rootChangelogConfig.projectChangelogs.renderer?.length
|
|
||||||
) {
|
) {
|
||||||
uniqueRendererPaths.add(rootChangelogConfig.projectChangelogs.renderer);
|
if (rootChangelogConfig.projectChangelogs.renderer?.length) {
|
||||||
|
uniqueRendererPaths.add(rootChangelogConfig.projectChangelogs.renderer);
|
||||||
|
}
|
||||||
|
const createReleaseError = validateCreateReleaseConfig(
|
||||||
|
rootChangelogConfig.projectChangelogs
|
||||||
|
);
|
||||||
|
if (createReleaseError) {
|
||||||
|
return createReleaseError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const group of Object.values(releaseGroups)) {
|
for (const group of Object.values(releaseGroups)) {
|
||||||
if (
|
if (group.changelog && typeof group.changelog !== 'boolean') {
|
||||||
group.changelog &&
|
if (group.changelog.renderer?.length) {
|
||||||
typeof group.changelog !== 'boolean' &&
|
uniqueRendererPaths.add(group.changelog.renderer);
|
||||||
group.changelog.renderer?.length
|
}
|
||||||
) {
|
const createReleaseError = validateCreateReleaseConfig(group.changelog);
|
||||||
uniqueRendererPaths.add(group.changelog.renderer);
|
if (createReleaseError) {
|
||||||
|
return createReleaseError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!uniqueRendererPaths.size) {
|
if (!uniqueRendererPaths.size) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const rendererPath of uniqueRendererPaths) {
|
for (const rendererPath of uniqueRendererPaths) {
|
||||||
try {
|
try {
|
||||||
resolveChangelogRenderer(rendererPath);
|
resolveChangelogRenderer(rendererPath);
|
||||||
} catch (e) {
|
} catch {
|
||||||
const workspaceRelativePath = relative(workspaceRoot, rendererPath);
|
return {
|
||||||
output.error({
|
code: 'CANNOT_RESOLVE_CHANGELOG_RENDERER',
|
||||||
title: `There was an error when resolving the configured changelog renderer at path: ${workspaceRelativePath}`,
|
data: {
|
||||||
});
|
workspaceRelativePath: relative(workspaceRoot, rendererPath),
|
||||||
throw e;
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportedCreateReleaseProviders = [
|
||||||
|
{
|
||||||
|
name: 'github-enterprise-server',
|
||||||
|
defaultApiBaseUrl: 'https://__hostname__/api/v3',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// User opts into the default by specifying the string value 'github'
|
||||||
|
export const defaultCreateReleaseProvider = {
|
||||||
|
provider: 'github',
|
||||||
|
hostname: 'github.com',
|
||||||
|
apiBaseUrl: 'https://api.github.com',
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
function validateCreateReleaseConfig(
|
||||||
|
changelogConfig: NxReleaseChangelogConfiguration
|
||||||
|
): CreateNxReleaseConfigError | null {
|
||||||
|
const createRelease = changelogConfig.createRelease;
|
||||||
|
// Disabled: valid
|
||||||
|
if (!createRelease) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// GitHub shorthand, expand to full object form, mark as valid
|
||||||
|
if (createRelease === 'github') {
|
||||||
|
changelogConfig.createRelease = defaultCreateReleaseProvider;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Object config, ensure that properties are valid
|
||||||
|
const supportedProvider = supportedCreateReleaseProviders.find(
|
||||||
|
(p) => p.name === createRelease.provider
|
||||||
|
);
|
||||||
|
if (!supportedProvider) {
|
||||||
|
return {
|
||||||
|
code: 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER',
|
||||||
|
data: {
|
||||||
|
provider: createRelease.provider,
|
||||||
|
supportedProviders: supportedCreateReleaseProviders.map((p) => p.name),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!isValidHostname(createRelease.hostname)) {
|
||||||
|
return {
|
||||||
|
code: 'INVALID_CHANGELOG_CREATE_RELEASE_HOSTNAME',
|
||||||
|
data: {
|
||||||
|
hostname: createRelease.hostname,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// user provided a custom apiBaseUrl, ensure it is valid (accounting for empty string case)
|
||||||
|
if (
|
||||||
|
createRelease.apiBaseUrl ||
|
||||||
|
typeof createRelease.apiBaseUrl === 'string'
|
||||||
|
) {
|
||||||
|
if (!isValidUrl(createRelease.apiBaseUrl)) {
|
||||||
|
return {
|
||||||
|
code: 'INVALID_CHANGELOG_CREATE_RELEASE_API_BASE_URL',
|
||||||
|
data: {
|
||||||
|
apiBaseUrl: createRelease.apiBaseUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set default apiBaseUrl when not provided by the user
|
||||||
|
createRelease.apiBaseUrl = supportedProvider.defaultApiBaseUrl.replace(
|
||||||
|
'__hostname__',
|
||||||
|
createRelease.hostname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidHostname(hostname) {
|
||||||
|
// Regular expression to match a valid hostname
|
||||||
|
const hostnameRegex =
|
||||||
|
/^(?!:\/\/)(?=.{1,255}$)(?!.*\.$)(?!.*?\.\.)(?!.*?-$)(?!^-)([a-zA-Z0-9-]{1,63}\.?)+[a-zA-Z]{2,}$/;
|
||||||
|
return hostnameRegex.test(hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidUrl(str: string): boolean {
|
||||||
|
try {
|
||||||
|
new URL(str);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -252,6 +252,9 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
|
|
||||||
latestCommit = await getCommitHash('HEAD');
|
latestCommit = await getCommitHash('HEAD');
|
||||||
await createOrUpdateGithubRelease(
|
await createOrUpdateGithubRelease(
|
||||||
|
nxReleaseConfig.changelog.workspaceChangelog
|
||||||
|
? nxReleaseConfig.changelog.workspaceChangelog.createRelease
|
||||||
|
: false,
|
||||||
changelogResult.workspaceChangelog.releaseVersion,
|
changelogResult.workspaceChangelog.releaseVersion,
|
||||||
changelogResult.workspaceChangelog.contents,
|
changelogResult.workspaceChangelog.contents,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
@ -297,6 +300,9 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await createOrUpdateGithubRelease(
|
await createOrUpdateGithubRelease(
|
||||||
|
releaseGroup.changelog
|
||||||
|
? releaseGroup.changelog.createRelease
|
||||||
|
: false,
|
||||||
changelog.releaseVersion,
|
changelog.releaseVersion,
|
||||||
changelog.contents,
|
changelog.contents,
|
||||||
latestCommit,
|
latestCommit,
|
||||||
|
|||||||
@ -8,8 +8,10 @@ import { prompt } from 'enquirer';
|
|||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { existsSync, promises as fsp } from 'node:fs';
|
import { existsSync, promises as fsp } from 'node:fs';
|
||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
|
import { NxReleaseChangelogConfiguration } from '../../../config/nx-json';
|
||||||
import { output } from '../../../utils/output';
|
import { output } from '../../../utils/output';
|
||||||
import { joinPathFragments } from '../../../utils/path';
|
import { joinPathFragments } from '../../../utils/path';
|
||||||
|
import { defaultCreateReleaseProvider } from '../config/config';
|
||||||
import { Reference } from './git';
|
import { Reference } from './git';
|
||||||
import { printDiff } from './print-changes';
|
import { printDiff } from './print-changes';
|
||||||
import { ReleaseVersion, noDiffInChangelogMessage } from './shared';
|
import { ReleaseVersion, noDiffInChangelogMessage } from './shared';
|
||||||
@ -20,12 +22,14 @@ const axios = _axios as any as (typeof _axios)['default'];
|
|||||||
|
|
||||||
export type RepoSlug = `${string}/${string}`;
|
export type RepoSlug = `${string}/${string}`;
|
||||||
|
|
||||||
export interface GithubRequestConfig {
|
interface GithubRequestConfig {
|
||||||
repo: string;
|
repo: string;
|
||||||
|
hostname: string;
|
||||||
|
apiBaseUrl: string;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GithubRelease {
|
interface GithubRelease {
|
||||||
id?: string;
|
id?: string;
|
||||||
tag_name: string;
|
tag_name: string;
|
||||||
target_commitish?: string;
|
target_commitish?: string;
|
||||||
@ -35,19 +39,46 @@ export interface GithubRelease {
|
|||||||
prerelease?: boolean;
|
prerelease?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGitHubRepoSlug(remoteName = 'origin'): RepoSlug {
|
export interface GithubRepoData {
|
||||||
|
hostname: string;
|
||||||
|
slug: RepoSlug;
|
||||||
|
apiBaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGitHubRepoData(
|
||||||
|
remoteName = 'origin',
|
||||||
|
createReleaseConfig: NxReleaseChangelogConfiguration['createRelease']
|
||||||
|
): GithubRepoData | null {
|
||||||
try {
|
try {
|
||||||
const remoteUrl = execSync(`git remote get-url ${remoteName}`, {
|
const remoteUrl = execSync(`git remote get-url ${remoteName}`, {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
}).trim();
|
}).trim();
|
||||||
|
|
||||||
|
// Use the default provider (github.com) if custom one is not specified or releases are disabled
|
||||||
|
let hostname = defaultCreateReleaseProvider.hostname;
|
||||||
|
let apiBaseUrl = defaultCreateReleaseProvider.apiBaseUrl;
|
||||||
|
if (
|
||||||
|
createReleaseConfig !== false &&
|
||||||
|
typeof createReleaseConfig !== 'string'
|
||||||
|
) {
|
||||||
|
hostname = createReleaseConfig.hostname;
|
||||||
|
apiBaseUrl = createReleaseConfig.apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the 'user/repo' part from the URL
|
// Extract the 'user/repo' part from the URL
|
||||||
const regex = /github\.com[/:]([\w-]+\/[\w-]+)/;
|
const escapedHostname = hostname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
const regexString = `${escapedHostname}[/:]([\\w.-]+/[\\w.-]+)(\\.git)?`;
|
||||||
|
const regex = new RegExp(regexString);
|
||||||
const match = remoteUrl.match(regex);
|
const match = remoteUrl.match(regex);
|
||||||
|
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
return match[1] as RepoSlug;
|
return {
|
||||||
|
hostname,
|
||||||
|
apiBaseUrl,
|
||||||
|
// Ensure any trailing .git is stripped
|
||||||
|
slug: match[1].replace(/\.git$/, '') as RepoSlug,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not extract "user/repo" data from the resolved remote URL: ${remoteUrl}`
|
`Could not extract "user/repo" data from the resolved remote URL: ${remoteUrl}`
|
||||||
@ -59,13 +90,14 @@ export function getGitHubRepoSlug(remoteName = 'origin'): RepoSlug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createOrUpdateGithubRelease(
|
export async function createOrUpdateGithubRelease(
|
||||||
|
createReleaseConfig: NxReleaseChangelogConfiguration['createRelease'],
|
||||||
releaseVersion: ReleaseVersion,
|
releaseVersion: ReleaseVersion,
|
||||||
changelogContents: string,
|
changelogContents: string,
|
||||||
latestCommit: string,
|
latestCommit: string,
|
||||||
{ dryRun }: { dryRun: boolean }
|
{ dryRun }: { dryRun: boolean }
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const githubRepoSlug = getGitHubRepoSlug();
|
const githubRepoData = getGitHubRepoData(undefined, createReleaseConfig);
|
||||||
if (!githubRepoSlug) {
|
if (!githubRepoData) {
|
||||||
output.error({
|
output.error({
|
||||||
title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`,
|
title: `Unable to create a GitHub release because the GitHub repo slug could not be determined.`,
|
||||||
bodyLines: [
|
bodyLines: [
|
||||||
@ -75,9 +107,11 @@ export async function createOrUpdateGithubRelease(
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await resolveGithubToken();
|
const token = await resolveGithubToken(githubRepoData.hostname);
|
||||||
const githubRequestConfig: GithubRequestConfig = {
|
const githubRequestConfig: GithubRequestConfig = {
|
||||||
repo: githubRepoSlug,
|
repo: githubRepoData.slug,
|
||||||
|
hostname: githubRepoData.hostname,
|
||||||
|
apiBaseUrl: githubRepoData.apiBaseUrl,
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,7 +140,7 @@ export async function createOrUpdateGithubRelease(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logTitle = `https://github.com/${githubRepoSlug}/releases/tag/${releaseVersion.gitTag}`;
|
const logTitle = `https://${githubRepoData.hostname}/${githubRepoData.slug}/releases/tag/${releaseVersion.gitTag}`;
|
||||||
if (existingGithubReleaseForVersion) {
|
if (existingGithubReleaseForVersion) {
|
||||||
console.error(
|
console.error(
|
||||||
`${chalk.white('UPDATE')} ${logTitle}${
|
`${chalk.white('UPDATE')} ${logTitle}${
|
||||||
@ -304,7 +338,7 @@ async function syncGithubRelease(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveGithubToken(): Promise<string | null> {
|
async function resolveGithubToken(hostname: string): Promise<string | null> {
|
||||||
// Try and resolve from the environment
|
// Try and resolve from the environment
|
||||||
const tokenFromEnv = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
const tokenFromEnv = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
||||||
if (tokenFromEnv) {
|
if (tokenFromEnv) {
|
||||||
@ -320,15 +354,15 @@ export async function resolveGithubToken(): Promise<string | null> {
|
|||||||
const yamlContents = await fsp.readFile(ghCLIPath, 'utf8');
|
const yamlContents = await fsp.readFile(ghCLIPath, 'utf8');
|
||||||
const { load } = require('@zkochan/js-yaml');
|
const { load } = require('@zkochan/js-yaml');
|
||||||
const ghCLIConfig = load(yamlContents);
|
const ghCLIConfig = load(yamlContents);
|
||||||
if (ghCLIConfig['github.com']) {
|
if (ghCLIConfig[hostname]) {
|
||||||
// Web based session (the token is already embedded in the config)
|
// Web based session (the token is already embedded in the config)
|
||||||
if (ghCLIConfig['github.com'].oauth_token) {
|
if (ghCLIConfig[hostname].oauth_token) {
|
||||||
return ghCLIConfig['github.com'].oauth_token;
|
return ghCLIConfig[hostname].oauth_token;
|
||||||
}
|
}
|
||||||
// SSH based session (we need to dynamically resolve a token using the CLI)
|
// SSH based session (we need to dynamically resolve a token using the CLI)
|
||||||
if (
|
if (
|
||||||
ghCLIConfig['github.com'].user &&
|
ghCLIConfig[hostname].user &&
|
||||||
ghCLIConfig['github.com'].git_protocol === 'ssh'
|
ghCLIConfig[hostname].git_protocol === 'ssh'
|
||||||
) {
|
) {
|
||||||
return execSync(`gh auth token`, {
|
return execSync(`gh auth token`, {
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
@ -337,6 +371,11 @@ export async function resolveGithubToken(): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (hostname !== 'github.com') {
|
||||||
|
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.`
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +398,7 @@ async function makeGithubRequest(
|
|||||||
return (
|
return (
|
||||||
await axios<any, any>(url, {
|
await axios<any, any>(url, {
|
||||||
...opts,
|
...opts,
|
||||||
baseURL: 'https://api.github.com',
|
baseURL: config.apiBaseUrl,
|
||||||
headers: {
|
headers: {
|
||||||
...(opts.headers as any),
|
...(opts.headers as any),
|
||||||
Authorization: config.token ? `Bearer ${config.token}` : undefined,
|
Authorization: config.token ? `Bearer ${config.token}` : undefined,
|
||||||
@ -395,11 +434,18 @@ async function updateGithubRelease(
|
|||||||
|
|
||||||
function githubNewReleaseURL(
|
function githubNewReleaseURL(
|
||||||
config: GithubRequestConfig,
|
config: GithubRequestConfig,
|
||||||
release: { version: string; body: string }
|
release: GithubReleaseOptions
|
||||||
) {
|
) {
|
||||||
return `https://github.com/${config.repo}/releases/new?tag=${
|
// Parameters taken from https://github.com/isaacs/github/issues/1410#issuecomment-442240267
|
||||||
|
let url = `https://${config.hostname}/${config.repo}/releases/new?tag=${
|
||||||
release.version
|
release.version
|
||||||
}&title=${release.version}&body=${encodeURIComponent(release.body)}`;
|
}&title=${release.version}&body=${encodeURIComponent(release.body)}&target=${
|
||||||
|
release.commit
|
||||||
|
}`;
|
||||||
|
if (release.prerelease) {
|
||||||
|
url += '&prerelease=true';
|
||||||
|
}
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RepoProvider = 'github';
|
type RepoProvider = 'github';
|
||||||
@ -411,27 +457,30 @@ const providerToRefSpec: Record<
|
|||||||
github: { 'pull-request': 'pull', hash: 'commit', issue: 'issues' },
|
github: { 'pull-request': 'pull', hash: 'commit', issue: 'issues' },
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatReference(ref: Reference, repoSlug: `${string}/${string}`) {
|
function formatReference(ref: Reference, repoData: GithubRepoData) {
|
||||||
const refSpec = providerToRefSpec['github'];
|
const refSpec = providerToRefSpec['github'];
|
||||||
return `[${ref.value}](https://github.com/${repoSlug}/${
|
return `[${ref.value}](https://${repoData.hostname}/${repoData.slug}/${
|
||||||
refSpec[ref.type]
|
refSpec[ref.type]
|
||||||
}/${ref.value.replace(/^#/, '')})`;
|
}/${ref.value.replace(/^#/, '')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatReferences(references: Reference[], repoSlug: RepoSlug) {
|
export function formatReferences(
|
||||||
|
references: Reference[],
|
||||||
|
repoData: GithubRepoData
|
||||||
|
) {
|
||||||
const pr = references.filter((ref) => ref.type === 'pull-request');
|
const pr = references.filter((ref) => ref.type === 'pull-request');
|
||||||
const issue = references.filter((ref) => ref.type === 'issue');
|
const issue = references.filter((ref) => ref.type === 'issue');
|
||||||
if (pr.length > 0 || issue.length > 0) {
|
if (pr.length > 0 || issue.length > 0) {
|
||||||
return (
|
return (
|
||||||
' (' +
|
' (' +
|
||||||
[...pr, ...issue]
|
[...pr, ...issue]
|
||||||
.map((ref) => formatReference(ref, repoSlug))
|
.map((ref) => formatReference(ref, repoData))
|
||||||
.join(', ') +
|
.join(', ') +
|
||||||
')'
|
')'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (references.length > 0) {
|
if (references.length > 0) {
|
||||||
return ' (' + formatReference(references[0], repoSlug) + ')';
|
return ' (' + formatReference(references[0], repoData) + ')';
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,7 +79,17 @@ export interface NxReleaseChangelogConfiguration {
|
|||||||
* NOTE: if createRelease is set on a group of projects, it will cause the default releaseTagPattern of
|
* NOTE: if createRelease is set on a group of projects, it will cause the default releaseTagPattern of
|
||||||
* "{projectName}@{version}" to be used for those projects, even when versioning everything together.
|
* "{projectName}@{version}" to be used for those projects, even when versioning everything together.
|
||||||
*/
|
*/
|
||||||
createRelease?: 'github' | false;
|
createRelease?:
|
||||||
|
| false
|
||||||
|
| 'github'
|
||||||
|
| {
|
||||||
|
provider: 'github-enterprise-server';
|
||||||
|
hostname: string;
|
||||||
|
/**
|
||||||
|
* If not set, this will default to `https://${hostname}/api/v3`
|
||||||
|
*/
|
||||||
|
apiBaseUrl?: string;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* This can either be set to a string value that will be written to the changelog file(s)
|
* This can either be set to a string value that will be written to the changelog file(s)
|
||||||
* at the workspace root and/or within project directories, or set to `false` to specify
|
* at the workspace root and/or within project directories, or set to `false` to specify
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user