feat(release): allow projects shorthand for single release group (#20560)
This commit is contained in:
parent
57cef5d979
commit
16dfccc0de
@ -11,7 +11,7 @@ import {
|
||||
} from '@nx/devkit';
|
||||
import * as chalk from 'chalk';
|
||||
import { exec } from 'child_process';
|
||||
import { CATCH_ALL_RELEASE_GROUP } from 'nx/src/command-line/release/config/config';
|
||||
import { IMPLICIT_DEFAULT_RELEASE_GROUP } from 'nx/src/command-line/release/config/config';
|
||||
import { getLatestGitTagForPattern } from 'nx/src/command-line/release/utils/git';
|
||||
import {
|
||||
resolveSemverSpecifierFromConventionalCommits,
|
||||
@ -271,7 +271,9 @@ To fix this you will either need to add a package.json file at that location, or
|
||||
case 'prompt': {
|
||||
// Only add the release group name to the log if it is one set by the user, otherwise it is useless noise
|
||||
const maybeLogReleaseGroup = (log: string): string => {
|
||||
if (options.releaseGroup.name === CATCH_ALL_RELEASE_GROUP) {
|
||||
if (
|
||||
options.releaseGroup.name === IMPLICIT_DEFAULT_RELEASE_GROUP
|
||||
) {
|
||||
return log;
|
||||
}
|
||||
return `${log} within release group "${options.releaseGroup.name}"`;
|
||||
|
||||
@ -671,7 +671,7 @@ describe('createNxReleaseConfig()', () => {
|
||||
});
|
||||
|
||||
describe('user config -> top level version', () => {
|
||||
it('should respect modifying version at the top level and it should be inherited by the catch all group', async () => {
|
||||
it('should respect modifying version at the top level and it should be inherited by the implicit default group', async () => {
|
||||
const res = await createNxReleaseConfig(projectGraph, {
|
||||
version: {
|
||||
// only modifying options, use default generator
|
||||
@ -906,8 +906,99 @@ describe('createNxReleaseConfig()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('user config -> top level projects', () => {
|
||||
it('should return an error when both "projects" and "groups" are specified', async () => {
|
||||
const res = await createNxReleaseConfig(projectGraph, {
|
||||
projects: ['lib-a'],
|
||||
groups: {
|
||||
'group-1': {
|
||||
projects: ['lib-a'],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": {
|
||||
"code": "PROJECTS_AND_GROUPS_DEFINED",
|
||||
"data": {},
|
||||
},
|
||||
"nxReleaseConfig": null,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should influence the projects configured for the implicit default group', async () => {
|
||||
const res = await createNxReleaseConfig(projectGraph, {
|
||||
projects: ['lib-a'],
|
||||
});
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": null,
|
||||
"nxReleaseConfig": {
|
||||
"changelog": {
|
||||
"git": {
|
||||
"commit": false,
|
||||
"commitArgs": "",
|
||||
"commitMessage": "",
|
||||
"tag": false,
|
||||
"tagArgs": "",
|
||||
"tagMessage": "",
|
||||
},
|
||||
"projectChangelogs": false,
|
||||
"workspaceChangelog": {
|
||||
"createRelease": false,
|
||||
"entryWhenNoChanges": "This was a version bump only, there were no code changes.",
|
||||
"file": "{workspaceRoot}/CHANGELOG.md",
|
||||
"renderOptions": {
|
||||
"includeAuthors": true,
|
||||
},
|
||||
"renderer": "nx/changelog-renderer",
|
||||
},
|
||||
},
|
||||
"git": {
|
||||
"commit": false,
|
||||
"commitArgs": "",
|
||||
"commitMessage": "",
|
||||
"tag": false,
|
||||
"tagArgs": "",
|
||||
"tagMessage": "",
|
||||
},
|
||||
"groups": {
|
||||
"__default__": {
|
||||
"changelog": false,
|
||||
"projects": [
|
||||
"lib-a",
|
||||
],
|
||||
"projectsRelationship": "fixed",
|
||||
"releaseTagPattern": "v{version}",
|
||||
"version": {
|
||||
"generator": "@nx/js:release-version",
|
||||
"generatorOptions": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
"projectsRelationship": "fixed",
|
||||
"releaseTagPattern": "v{version}",
|
||||
"version": {
|
||||
"generator": "@nx/js:release-version",
|
||||
"generatorOptions": {},
|
||||
"git": {
|
||||
"commit": false,
|
||||
"commitArgs": "",
|
||||
"commitMessage": "",
|
||||
"tag": false,
|
||||
"tagArgs": "",
|
||||
"tagMessage": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('user config -> top level releaseTagPattern', () => {
|
||||
it('should respect modifying releaseTagPattern at the top level and it should be inherited by the catch all group', async () => {
|
||||
it('should respect modifying releaseTagPattern at the top level and it should be inherited by the implicit default group', async () => {
|
||||
const res = await createNxReleaseConfig(projectGraph, {
|
||||
releaseTagPattern: '{projectName}__{version}',
|
||||
});
|
||||
|
||||
@ -11,10 +11,7 @@
|
||||
* defaults and user overrides, as well as handling common errors, up front to produce a single, consistent,
|
||||
* and easy to consume config object for all the `nx release` command implementations.
|
||||
*/
|
||||
import {
|
||||
NxJsonConfiguration,
|
||||
NxReleaseChangelogConfiguration,
|
||||
} from '../../../config/nx-json';
|
||||
import { NxJsonConfiguration } from '../../../config/nx-json';
|
||||
import { output, type ProjectGraph } from '../../../devkit-exports';
|
||||
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
||||
import { projectHasTarget } from '../../../utils/project-graph-utils';
|
||||
@ -38,7 +35,7 @@ type RemoveTrueFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
|
||||
[U in keyof T]: RemoveTrueFromProperties<T[U], K>;
|
||||
};
|
||||
|
||||
export const CATCH_ALL_RELEASE_GROUP = '__default__';
|
||||
export const IMPLICIT_DEFAULT_RELEASE_GROUP = '__default__';
|
||||
|
||||
/**
|
||||
* Our source of truth is a deeply required variant of the user-facing config interface, so that command
|
||||
@ -49,25 +46,30 @@ export const CATCH_ALL_RELEASE_GROUP = '__default__';
|
||||
* it easier to work with (the user could be specifying a single string, and they can also use any valid matcher
|
||||
* pattern such as directories and globs).
|
||||
*/
|
||||
export type NxReleaseConfig = DeepRequired<
|
||||
NxJsonConfiguration['release'] & {
|
||||
groups: DeepRequired<
|
||||
RemoveTrueFromPropertiesOnEach<
|
||||
EnsureProjectsArray<NxJsonConfiguration['release']['groups']>,
|
||||
'changelog'
|
||||
>
|
||||
>;
|
||||
// Remove the true shorthand from the changelog config types, it will be normalized to a default object
|
||||
changelog: RemoveTrueFromProperties<
|
||||
DeepRequired<NxJsonConfiguration['release']['changelog']>,
|
||||
'workspaceChangelog' | 'projectChangelogs'
|
||||
>;
|
||||
}
|
||||
export type NxReleaseConfig = Omit<
|
||||
DeepRequired<
|
||||
NxJsonConfiguration['release'] & {
|
||||
groups: DeepRequired<
|
||||
RemoveTrueFromPropertiesOnEach<
|
||||
EnsureProjectsArray<NxJsonConfiguration['release']['groups']>,
|
||||
'changelog'
|
||||
>
|
||||
>;
|
||||
// Remove the true shorthand from the changelog config types, it will be normalized to a default object
|
||||
changelog: RemoveTrueFromProperties<
|
||||
DeepRequired<NxJsonConfiguration['release']['changelog']>,
|
||||
'workspaceChangelog' | 'projectChangelogs'
|
||||
>;
|
||||
}
|
||||
>,
|
||||
// projects is just a shorthand for the default group's projects configuration, it does not exist in the final config
|
||||
'projects'
|
||||
>;
|
||||
|
||||
// We explicitly handle some possible errors in order to provide the best possible DX
|
||||
export interface CreateNxReleaseConfigError {
|
||||
code:
|
||||
| 'PROJECTS_AND_GROUPS_DEFINED'
|
||||
| 'RELEASE_GROUP_MATCHES_NO_PROJECTS'
|
||||
| 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE'
|
||||
| 'PROJECT_MATCHES_MULTIPLE_GROUPS'
|
||||
@ -85,6 +87,16 @@ export async function createNxReleaseConfig(
|
||||
error: null | CreateNxReleaseConfigError;
|
||||
nxReleaseConfig: NxReleaseConfig | null;
|
||||
}> {
|
||||
if (userConfig.projects && userConfig.groups) {
|
||||
return {
|
||||
error: {
|
||||
code: 'PROJECTS_AND_GROUPS_DEFINED',
|
||||
data: {},
|
||||
},
|
||||
nxReleaseConfig: null,
|
||||
};
|
||||
}
|
||||
|
||||
const gitDefaults = {
|
||||
commit: false,
|
||||
commitMessage: '',
|
||||
@ -212,23 +224,26 @@ export async function createNxReleaseConfig(
|
||||
const rootVersionWithoutGit = { ...rootVersionConfig };
|
||||
delete rootVersionWithoutGit.git;
|
||||
|
||||
const allProjects = findMatchingProjects(['*'], projectGraph.nodes).filter(
|
||||
// only include libs by default when the user has no groups config,
|
||||
// because the default implementation assumes npm js packages
|
||||
// and these will usually be libs
|
||||
(project) => projectGraph.nodes[project].type === 'lib'
|
||||
);
|
||||
const groups: NxReleaseConfig['groups'] =
|
||||
userConfig.groups && Object.keys(userConfig.groups).length
|
||||
? ensureProjectsConfigIsArray(userConfig.groups)
|
||||
: /**
|
||||
* No user specified release groups, so we treat all projects as being in one release group
|
||||
* together in which all projects are released in lock step.
|
||||
* No user specified release groups, so we treat all projects (or any any user-defined subset via the top level "projects" property)
|
||||
* as being in one release group together in which the projects are released in lock step.
|
||||
*/
|
||||
{
|
||||
[CATCH_ALL_RELEASE_GROUP]: {
|
||||
[IMPLICIT_DEFAULT_RELEASE_GROUP]: {
|
||||
projectsRelationship: GROUP_DEFAULTS.projectsRelationship,
|
||||
projects: allProjects,
|
||||
projects: userConfig.projects
|
||||
? // user-defined top level "projects" config takes priority if set
|
||||
findMatchingProjects(
|
||||
ensureArray(userConfig.projects),
|
||||
projectGraph.nodes
|
||||
)
|
||||
: // default to all library projects in the workspace
|
||||
findMatchingProjects(['*'], projectGraph.nodes).filter(
|
||||
(project) => projectGraph.nodes[project].type === 'lib'
|
||||
),
|
||||
/**
|
||||
* For properties which are overriding config at the root, we use the root level config as the
|
||||
* default values to merge with so that the group that matches a specific project will always
|
||||
@ -238,7 +253,7 @@ export async function createNxReleaseConfig(
|
||||
[GROUP_DEFAULTS.version],
|
||||
rootVersionWithoutGit
|
||||
),
|
||||
// If the user has set something custom for releaseTagPattern at the top level, respect it for the catch all default group
|
||||
// If the user has set something custom for releaseTagPattern at the top level, respect it for the implicit default group
|
||||
releaseTagPattern:
|
||||
userConfig.releaseTagPattern || GROUP_DEFAULTS.releaseTagPattern,
|
||||
// Directly inherit the root level config for projectChangelogs, if set
|
||||
@ -390,6 +405,18 @@ export async function handleNxReleaseConfigError(
|
||||
error: CreateNxReleaseConfigError
|
||||
): Promise<never> {
|
||||
switch (error.code) {
|
||||
case 'PROJECTS_AND_GROUPS_DEFINED':
|
||||
{
|
||||
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||
'release',
|
||||
'projects',
|
||||
]);
|
||||
output.error({
|
||||
title: `"projects" is not valid when explicitly defining release groups, and everything should be expressed within "groups" in that case. If you are using "groups" then you should remove the "projects" property`,
|
||||
bodyLines: [nxJsonMessage],
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'RELEASE_GROUP_MATCHES_NO_PROJECTS':
|
||||
{
|
||||
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||
@ -465,14 +492,16 @@ function ensureProjectsConfigIsArray(
|
||||
for (const [groupName, groupConfig] of Object.entries(groups)) {
|
||||
result[groupName] = {
|
||||
...groupConfig,
|
||||
projects: Array.isArray(groupConfig.projects)
|
||||
? groupConfig.projects
|
||||
: [groupConfig.projects],
|
||||
projects: ensureArray(groupConfig.projects),
|
||||
};
|
||||
}
|
||||
return result as NxReleaseConfig['groups'];
|
||||
}
|
||||
|
||||
function ensureArray(value: string | string[]): string[] {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
function ensureProjectsHaveTarget(
|
||||
projects: string[],
|
||||
projectGraph: ProjectGraph,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type ProjectGraph } from '../../../devkit-exports';
|
||||
import { CATCH_ALL_RELEASE_GROUP, NxReleaseConfig } from './config';
|
||||
import { IMPLICIT_DEFAULT_RELEASE_GROUP, NxReleaseConfig } from './config';
|
||||
import { filterReleaseGroups } from './filter-release-groups';
|
||||
|
||||
describe('filterReleaseGroups()', () => {
|
||||
@ -201,9 +201,9 @@ describe('filterReleaseGroups()', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should produce an appropriately formatted error for the CATCH_ALL_RELEASE_GROUP', () => {
|
||||
it('should produce an appropriately formatted error for the IMPLICIT_DEFAULT_RELEASE_GROUP', () => {
|
||||
nxReleaseConfig.groups = {
|
||||
[CATCH_ALL_RELEASE_GROUP]: {
|
||||
[IMPLICIT_DEFAULT_RELEASE_GROUP]: {
|
||||
projectsRelationship: 'fixed',
|
||||
projects: ['lib-a', 'lib-a'],
|
||||
changelog: false,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ProjectGraph } from '../../../config/project-graph';
|
||||
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
||||
import { output } from '../../../utils/output';
|
||||
import { CATCH_ALL_RELEASE_GROUP, NxReleaseConfig } from './config';
|
||||
import { IMPLICIT_DEFAULT_RELEASE_GROUP, NxReleaseConfig } from './config';
|
||||
|
||||
export type ReleaseGroupWithName = NxReleaseConfig['groups'][string] & {
|
||||
name: string;
|
||||
@ -112,10 +112,11 @@ export function filterReleaseGroups(
|
||||
(rg) => rg.projectsRelationship !== 'independent'
|
||||
);
|
||||
if (releaseGroupsThatAreNotIndependent.length) {
|
||||
// Special handling for CATCH_ALL_RELEASE_GROUP (which the user did not explicitly configure)
|
||||
// Special handling for IMPLICIT_DEFAULT_RELEASE_GROUP
|
||||
if (
|
||||
releaseGroupsThatAreNotIndependent.length === 1 &&
|
||||
releaseGroupsThatAreNotIndependent[0].name === CATCH_ALL_RELEASE_GROUP
|
||||
releaseGroupsThatAreNotIndependent[0].name ===
|
||||
IMPLICIT_DEFAULT_RELEASE_GROUP
|
||||
) {
|
||||
return {
|
||||
error: {
|
||||
@ -143,7 +144,7 @@ export function filterReleaseGroups(
|
||||
title: `Your filter "${projectsFilter}" matched the following projects:`,
|
||||
bodyLines: matchingProjectsForFilter.map((p) => {
|
||||
const releaseGroupForProject = filteredProjectToReleaseGroup.get(p);
|
||||
if (releaseGroupForProject.name === CATCH_ALL_RELEASE_GROUP) {
|
||||
if (releaseGroupForProject.name === IMPLICIT_DEFAULT_RELEASE_GROUP) {
|
||||
return `- ${p}`;
|
||||
}
|
||||
return `- ${p} (release group "${releaseGroupForProject.name}")`;
|
||||
|
||||
@ -139,7 +139,12 @@ export interface NxReleaseGitConfiguration {
|
||||
*/
|
||||
interface NxReleaseConfiguration {
|
||||
/**
|
||||
* @note: When no groups are configured at all (the default), all projects in the workspace are treated as
|
||||
* Shorthand for amending the projects which will be included in the implicit default release group (all projects by default).
|
||||
* @note Only one of `projects` or `groups` can be specified, the cannot be used together.
|
||||
*/
|
||||
projects?: string[] | string;
|
||||
/**
|
||||
* @note When no projects or groups are configured at all (the default), all projects in the workspace are treated as
|
||||
* if they were in a release group together with a fixed relationship.
|
||||
*/
|
||||
groups?: Record<
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user