Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(release): store rawVersionSpec on versionData #22071

Merged
merged 2 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let originalExit = process.exit;
const originalExit = process.exit;
let stubProcessExit = false;

const processExitSpy = jest
Expand All @@ -12,10 +12,10 @@ const processExitSpy = jest

import { ProjectGraph, Tree, output, readJson } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import * as enquirer from 'enquirer';
import { ReleaseGroupWithName } from 'nx/src/command-line/release/config/filter-release-groups';
import { releaseVersionGenerator } from './release-version';
import { createWorkspaceWithPackageDependencies } from './test-utils/create-workspace-with-package-dependencies';
import * as enquirer from 'enquirer';

jest.mock('enquirer');

Expand All @@ -27,7 +27,7 @@ describe('release-version', () => {
let projectGraph: ProjectGraph;

beforeEach(() => {
// @ts-ignore
// @ts-expect-error read-only property
process.exit = processExitSpy;

tree = createTreeWithEmptyWorkspace();
Expand Down Expand Up @@ -95,12 +95,14 @@ describe('release-version', () => {
"dependentProjects": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "0.0.1",
"source": "project-with-dependency-on-my-pkg",
"target": "my-lib",
"type": "static",
},
{
"dependencyCollection": "devDependencies",
"rawVersionSpec": "0.0.1",
"source": "project-with-devDependency-on-my-pkg",
"target": "my-lib",
"type": "static",
Expand Down Expand Up @@ -314,7 +316,7 @@ To fix this you will either need to add a package.json file at that location, or
describe('independent release group', () => {
describe('specifierSource: prompt', () => {
it(`should appropriately prompt for each project independently and apply the version updates across all package.json files`, async () => {
// @ts-ignore
// @ts-expect-error read-only property
enquirer.prompt = jest
.fn()
// First project will be minor
Expand Down Expand Up @@ -920,13 +922,13 @@ To fix this you will either need to add a package.json file at that location, or
specifier: 'major',
currentVersionResolver: 'disk',
releaseGroup: createReleaseGroup('fixed'),
versionPrefix: '$',
versionPrefix: '$' as any,
});

expect(outputSpy).toHaveBeenCalledWith({
title: `Invalid value for version.generatorOptions.versionPrefix: "$"

Valid values are: "auto", "", "~", "^"`,
Valid values are: "auto", "", "~", "^", "="`,
});

outputSpy.mockRestore();
Expand Down
86 changes: 56 additions & 30 deletions packages/js/src/generators/release-version/release-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
writeJson,
} from '@nx/devkit';
import * as chalk from 'chalk';
import { exec } from 'child_process';
import { exec } from 'node:child_process';
import { relative } from 'node:path';
import { IMPLICIT_DEFAULT_RELEASE_GROUP } from 'nx/src/command-line/release/config/config';
import {
getFirstGitCommit,
Expand All @@ -27,10 +28,8 @@ import {
deriveNewSemverVersion,
validReleaseVersionPrefixes,
} from 'nx/src/command-line/release/version';

import { interpolate } from 'nx/src/tasks-runner/utils';
import * as ora from 'ora';
import { relative } from 'path';
import { prerelease } from 'semver';
import { ReleaseVersionGeneratorSchema } from './schema';
import { resolveLocalPackageDependencies } from './utils/resolve-local-package-dependencies';
Expand Down Expand Up @@ -76,20 +75,6 @@ Valid values are: ${validReleaseVersionPrefixes

const projects = options.projects;

const createResolvePackageRoot =
(customPackageRoot?: string) =>
(projectNode: ProjectGraphProjectNode): string => {
// Default to the project root if no custom packageRoot
if (!customPackageRoot) {
return projectNode.data.root;
}
return interpolate(customPackageRoot, {
workspaceRoot: '',
projectRoot: projectNode.data.root,
projectName: projectNode.name,
});
};

const resolvePackageRoot = createResolvePackageRoot(options.packageRoot);

// Resolve any custom package roots for each project upfront as they will need to be reused during dependency resolution
Expand All @@ -101,20 +86,31 @@ Valid values are: ${validReleaseVersionPrefixes
);
}

let currentVersion: string;
let currentVersionResolvedFromFallback: boolean = false;
let currentVersion: string | undefined = undefined;
let currentVersionResolvedFromFallback = false;

// only used for options.currentVersionResolver === 'git-tag', but
// must be declared here in order to reuse it for additional projects
let latestMatchingGitTag: { tag: string; extractedVersion: string };
let latestMatchingGitTag:
| { tag: string; extractedVersion: string }
| null
| undefined = undefined;

// if specifier is undefined, then we haven't resolved it yet
// if specifier is null, then it has been resolved and no changes are necessary
let specifier = options.specifier ? options.specifier : undefined;
let specifier: string | null | undefined = options.specifier
? options.specifier
: undefined;

for (const project of projects) {
const projectName = project.name;
const packageRoot = projectNameToPackageRootMap.get(projectName);
if (!packageRoot) {
throw new Error(
`The project "${projectName}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`
);
}

const packageJsonPath = joinPathFragments(packageRoot, 'package.json');
const workspaceRelativePackageJsonPath = relative(
workspaceRoot,
Expand Down Expand Up @@ -268,7 +264,10 @@ To fix this you will either need to add a package.json file at that location, or
);
} else {
log(
`📄 Using the current version ${currentVersion} already resolved from git tag "${latestMatchingGitTag.tag}".`
// In this code path we know that latestMatchingGitTag is defined, because we are not relying on the fallbackCurrentVersionResolver, so we can safely use the non-null assertion operator
`📄 Using the current version ${currentVersion} already resolved from git tag "${
latestMatchingGitTag!.tag
}".`
);
}
}
Expand Down Expand Up @@ -301,7 +300,7 @@ To fix this you will either need to add a package.json file at that location, or
) {
const specifierSource = options.specifierSource;
switch (specifierSource) {
case 'conventional-commits':
case 'conventional-commits': {
if (options.currentVersionResolver !== 'git-tag') {
throw new Error(
`Invalid currentVersionResolver "${options.currentVersionResolver}" provided for release group "${options.releaseGroup.name}". Must be "git-tag" when "specifierSource" is "conventional-commits"`
Expand Down Expand Up @@ -347,7 +346,7 @@ To fix this you will either need to add a package.json file at that location, or
//
// Always assume that if the current version is a prerelease, then the next version should be a prerelease.
// Users must manually graduate from a prerelease to a release by providing an explicit specifier.
if (prerelease(currentVersion)) {
if (prerelease(currentVersion ?? '')) {
specifier = 'prerelease';
log(
`📄 Resolved the specifier as "${specifier}" since the current version is a prerelease.`
Expand All @@ -358,6 +357,7 @@ To fix this you will either need to add a package.json file at that location, or
);
}
break;
}
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 => {
Expand Down Expand Up @@ -413,9 +413,16 @@ To fix this you will either need to add a package.json file at that location, or
return localPackageDependency.target === project.name;
});

if (!currentVersion) {
throw new Error(
`The current version for project "${project.name}" could not be resolved. Please report this on https://github.com/nrwl/nx`
);
}

versionData[projectName] = {
currentVersion,
dependentProjects,
// @ts-ignore: The types will be updated in a future version of Nx
newVersion: null, // will stay as null in the final result in the case that no changes are detected
};

Expand Down Expand Up @@ -455,12 +462,17 @@ To fix this you will either need to add a package.json file at that location, or
}

for (const dependentProject of dependentProjects) {
const dependentPackageRoot = projectNameToPackageRootMap.get(
dependentProject.source
);
if (!dependentPackageRoot) {
throw new Error(
`The dependent project "${dependentProject.source}" does not have a packageRoot available. Please report this issue on https://github.com/nrwl/nx`
);
}
updateJson(
tree,
joinPathFragments(
projectNameToPackageRootMap.get(dependentProject.source),
'package.json'
),
joinPathFragments(dependentPackageRoot, 'package.json'),
(json) => {
// Auto (i.e.infer existing) by default
let versionPrefix = options.versionPrefix ?? 'auto';
Expand Down Expand Up @@ -490,7 +502,7 @@ To fix this you will either need to add a package.json file at that location, or
}

/**
* Ensure that formatting is applied so that version bump diffs are as mimimal as possible
* Ensure that formatting is applied so that version bump diffs are as minimal as possible
* within the context of the user's workspace.
*/
await formatFiles(tree);
Expand All @@ -504,7 +516,7 @@ To fix this you will either need to add a package.json file at that location, or
return updatedFiles;
},
};
} catch (e) {
} catch (e: any) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
output.error({
title: e.message,
Expand All @@ -522,6 +534,20 @@ To fix this you will either need to add a package.json file at that location, or

export default releaseVersionGenerator;

function createResolvePackageRoot(customPackageRoot?: string) {
return (projectNode: ProjectGraphProjectNode): string => {
// Default to the project root if no custom packageRoot
if (!customPackageRoot) {
return projectNode.data.root;
}
return interpolate(customPackageRoot, {
workspaceRoot: '',
projectRoot: projectNode.data.root,
projectName: projectNode.name,
});
};
}

const colors = [
{ instance: chalk.green, spinnerColor: 'green' },
{ instance: chalk.greenBright, spinnerColor: 'green' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,21 @@ describe('resolveLocalPackageDependencies()', () => {
"projectA": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectB",
"type": "static",
},
{
"dependencyCollection": "devDependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectC",
"type": "static",
},
{
"dependencyCollection": "optionalDependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectD",
"type": "static",
Expand Down Expand Up @@ -220,24 +223,28 @@ describe('resolveLocalPackageDependencies()', () => {
"projectA": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "file:../projectB",
"source": "projectA",
"target": "projectB",
"type": "static",
},
{
"dependencyCollection": "devDependencies",
"rawVersionSpec": "workspace:*",
"source": "projectA",
"target": "projectC",
"type": "static",
},
{
"dependencyCollection": "optionalDependencies",
"rawVersionSpec": "workspace:../projectD",
"source": "projectA",
"target": "projectD",
"type": "static",
},
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "link:../projectE",
"source": "projectA",
"target": "projectE",
"type": "static",
Expand All @@ -246,6 +253,7 @@ describe('resolveLocalPackageDependencies()', () => {
"projectB": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "workspace:1.0.0",
"source": "projectB",
"target": "projectC",
"type": "static",
Expand Down Expand Up @@ -305,6 +313,7 @@ describe('resolveLocalPackageDependencies()', () => {
"projectA": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectB",
"type": "static",
Expand Down Expand Up @@ -394,18 +403,21 @@ describe('resolveLocalPackageDependencies()', () => {
"projectA": [
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectB",
"type": "static",
},
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "1.0.0",
"source": "projectA",
"target": "projectC",
"type": "static",
},
{
"dependencyCollection": "dependencies",
"rawVersionSpec": "file:../../../packages/projectD",
"source": "projectA",
"target": "projectD",
"type": "static",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { Package } from './package';
import { resolveVersionSpec } from './resolve-version-spec';

interface LocalPackageDependency extends ProjectGraphDependency {
/**
* The rawVersionSpec contains the value of the version spec as it was defined in the package.json
* of the dependent project. This can be useful in cases where the version spec is a range, path or
* workspace reference, and it needs to be be reverted to that original value as part of the release.
*/
rawVersionSpec: string;
dependencyCollection:
| 'dependencies'
| 'devDependencies'
Expand Down Expand Up @@ -104,6 +110,7 @@ export function resolveLocalPackageDependencies(
{
...dep,
dependencyCollection: sourceNpmDependency.collection,
rawVersionSpec: sourceNpmDependency.spec,
},
];
}
Expand Down
6 changes: 5 additions & 1 deletion packages/nx/src/command-line/release/utils/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export type ReleaseVersionGeneratorResult = {
export type VersionData = Record<
string,
{
newVersion: string;
/**
* newVersion will be null in the case that no changes are detected for the project,
* e.g. when using conventional commits
*/
newVersion: string | null;
currentVersion: string;
dependentProjects: any[]; // TODO: investigate generic type for this once more ecosystems are explored
}
Expand Down
2 changes: 1 addition & 1 deletion packages/nx/src/command-line/release/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type {
VersionData,
} from './utils/shared';

export const validReleaseVersionPrefixes = ['auto', '', '~', '^'];
export const validReleaseVersionPrefixes = ['auto', '', '~', '^', '='] as const;

export interface ReleaseVersionGeneratorSchema {
// The projects being versioned in the current execution
Expand Down