From 8a66733ffe3b38e22f57ac2b9130a45973d15504 Mon Sep 17 00:00:00 2001 From: Austin Fahsl Date: Mon, 15 Jan 2024 22:20:24 -0700 Subject: [PATCH 1/2] feat(release): add fallbackCurrentVersionResolver option --- e2e/release/src/release.test.ts | 104 ++++++++++++++++++ .../release-version/release-version.ts | 99 +++++++++++------ .../nx/src/command-line/release/version.ts | 1 + 3 files changed, 173 insertions(+), 31 deletions(-) diff --git a/e2e/release/src/release.test.ts b/e2e/release/src/release.test.ts index ce976e4ed7069..02a0a5a21dd7a 100644 --- a/e2e/release/src/release.test.ts +++ b/e2e/release/src/release.test.ts @@ -1058,5 +1058,109 @@ ${JSON.stringify( new RegExp(`Successfully ran target nx-release-publish for`, 'g') ).length ).toEqual(2); + + // change the releaseTagPattern to something that doesn't exist in order to test fallbackCurrentVersionResolver + updateJson('nx.json', (nxJson) => { + nxJson.release = { + groups: { + group1: { + projects: [pkg1, pkg2, pkg3], + releaseTagPattern: '>{version}', + }, + }, + git: { + commit: true, + tag: true, + }, + version: { + generatorOptions: { + currentVersionResolver: 'git-tag', + }, + }, + }; + return nxJson; + }); + + const releaseOutput4 = runCLI(`release patch --skip-publish`, { + silenceError: true, + }); + + expect(releaseOutput4).toMatchInlineSnapshot(` + + > NX Running release version for project: {project-name} + + {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json + + > NX No git tags matching pattern ">{version}" for project "{project-name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can set the "version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found. + + + `); + + updateJson('nx.json', (nxJson) => { + nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = + 'disk'; + return nxJson; + }); + + const releaseOutput5 = runCLI(`release patch --skip-publish --verbose`); + + expect(releaseOutput5).toMatch( + `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.0` + ); + expect( + releaseOutput5.match( + new RegExp( + `📄 Using the current version 1400\\.1\\.0 already resolved from disk fallback\\.`, + 'g' + ) + ).length + ).toEqual(2); + + updateJson('nx.json', (nxJson) => { + nxJson.release.version.generatorOptions.currentVersionResolver = + 'registry'; + nxJson.release.version.generatorOptions.currentVersionResolverMetadata = { + tag: 'other', + }; + delete nxJson.release.version.generatorOptions + .fallbackCurrentVersionResolver; + return nxJson; + }); + + const releaseOutput6 = runCLI(`release patch --skip-publish`, { + silenceError: true, + }); + + expect(releaseOutput6).toMatchInlineSnapshot(` + + > NX Running release version for project: {project-name} + + {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json + + > NX Unable to resolve the current version from the registry ${e2eRegistryUrl}. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can set the "version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails. + + - Resolving the current version for tag "other" on ${e2eRegistryUrl} + + `); + + updateJson('nx.json', (nxJson) => { + nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = + 'disk'; + return nxJson; + }); + + const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`); + + expect(releaseOutput7).toMatch( + `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.1` + ); + expect( + releaseOutput7.match( + new RegExp( + `📄 Using the current version 1400\\.1\\.1 already resolved from disk fallback\\.`, + 'g' + ) + ).length + ).toEqual(2); }, 500000); }); diff --git a/packages/js/src/generators/release-version/release-version.ts b/packages/js/src/generators/release-version/release-version.ts index 5c3042b03109a..3b34a71f8286e 100644 --- a/packages/js/src/generators/release-version/release-version.ts +++ b/packages/js/src/generators/release-version/release-version.ts @@ -75,6 +75,7 @@ export async function releaseVersionGenerator( } let currentVersion: string; + let currentVersionResolvedFromFallback: boolean = false; // only used for options.currentVersionResolver === 'git-tag', but // must be declared here in order to reuse it for additional projects @@ -146,31 +147,53 @@ To fix this you will either need to add a package.json file at that location, or color.spinnerColor as typeof colors[number]['spinnerColor']; spinner.start(); - // Must be non-blocking async to allow spinner to render - currentVersion = await new Promise((resolve, reject) => { - exec( - `npm view ${packageName} version --registry=${registry} --tag=${tag}`, - (error, stdout, stderr) => { - if (error) { - return reject(error); + try { + // Must be non-blocking async to allow spinner to render + currentVersion = await new Promise((resolve, reject) => { + exec( + `npm view ${packageName} version --registry=${registry} --tag=${tag}`, + (error, stdout, stderr) => { + if (error) { + return reject(error); + } + if (stderr) { + return reject(stderr); + } + return resolve(stdout.trim()); } - if (stderr) { - return reject(stderr); - } - return resolve(stdout.trim()); - } - ); - }); + ); + }); - spinner.stop(); + spinner.stop(); - log( - `📄 Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${registry}` - ); + log( + `📄 Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${registry}` + ); + } catch (e) { + spinner.stop(); + + if (options.fallbackCurrentVersionResolver === 'disk') { + log( + `📄 Unable to resolve the current version from the registry ${registry}. Falling back to the version on disk of ${currentVersionFromDisk}` + ); + currentVersion = currentVersionFromDisk; + currentVersionResolvedFromFallback = true; + } else { + throw new Error( + `Unable to resolve the current version from the registry ${registry}. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can set the "version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.` + ); + } + } } else { - log( - `📄 Using the current version ${currentVersion} already resolved from the registry ${registry}` - ); + if (currentVersionResolvedFromFallback) { + log( + `📄 Using the current version ${currentVersion} already resolved from disk fallback.` + ); + } else { + log( + `📄 Using the current version ${currentVersion} already resolved from the registry ${registry}` + ); + } } break; } @@ -194,19 +217,33 @@ To fix this you will either need to add a package.json file at that location, or } ); if (!latestMatchingGitTag) { - throw new Error( - `No git tags matching pattern "${releaseTagPattern}" for project "${project.name}" were found. You will need to create an initial matching tag to use as a base for determining the next version.` + if (options.fallbackCurrentVersionResolver === 'disk') { + log( + `📄 Unable to resolve the current version from git tag using pattern "${releaseTagPattern}". Falling back to the version on disk of ${currentVersionFromDisk}` + ); + currentVersion = currentVersionFromDisk; + currentVersionResolvedFromFallback = true; + } else { + throw new Error( + `No git tags matching pattern "${releaseTagPattern}" for project "${project.name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can set the "version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found.` + ); + } + } else { + currentVersion = latestMatchingGitTag.extractedVersion; + log( + `📄 Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}".` ); } - - currentVersion = latestMatchingGitTag.extractedVersion; - log( - `📄 Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}".` - ); } else { - log( - `📄 Using the current version ${currentVersion} already resolved from git tag "${latestMatchingGitTag.tag}".` - ); + if (currentVersionResolvedFromFallback) { + log( + `📄 Using the current version ${currentVersion} already resolved from disk fallback.` + ); + } else { + log( + `📄 Using the current version ${currentVersion} already resolved from git tag "${latestMatchingGitTag.tag}".` + ); + } } break; } diff --git a/packages/nx/src/command-line/release/version.ts b/packages/nx/src/command-line/release/version.ts index b5797e3c3b075..709626455a67e 100644 --- a/packages/nx/src/command-line/release/version.ts +++ b/packages/nx/src/command-line/release/version.ts @@ -56,6 +56,7 @@ export interface ReleaseVersionGeneratorSchema { packageRoot?: string; currentVersionResolver?: 'registry' | 'disk' | 'git-tag'; currentVersionResolverMetadata?: Record; + fallbackCurrentVersionResolver?: 'disk'; } export interface NxReleaseVersionResult { From f3205290c6a7643c5374c074b38b7f572405b9cd Mon Sep 17 00:00:00 2001 From: Austin Fahsl Date: Tue, 16 Jan 2024 13:01:59 -0700 Subject: [PATCH 2/2] feat(release): add --first-release option as alias for fallback config --- docs/generated/cli/release.md | 12 ++++ .../packages/nx/documents/release.md | 12 ++++ e2e/release/src/release.test.ts | 60 +++++++++++++++---- .../release-version/release-version.ts | 5 ++ .../command-line/release/command-object.ts | 12 ++++ .../nx/src/command-line/release/version.ts | 2 + 6 files changed, 92 insertions(+), 11 deletions(-) diff --git a/docs/generated/cli/release.md b/docs/generated/cli/release.md index 07f6494daa379..9a192cce28af1 100644 --- a/docs/generated/cli/release.md +++ b/docs/generated/cli/release.md @@ -67,6 +67,12 @@ nx release [specifier] #### Options +##### first-release + +Type: `boolean` + +Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release. + ##### help Type: `boolean` @@ -107,6 +113,12 @@ nx release version [specifier] #### Options +##### first-release + +Type: `boolean` + +Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release. + ##### git-commit Type: `boolean` diff --git a/docs/generated/packages/nx/documents/release.md b/docs/generated/packages/nx/documents/release.md index 07f6494daa379..9a192cce28af1 100644 --- a/docs/generated/packages/nx/documents/release.md +++ b/docs/generated/packages/nx/documents/release.md @@ -67,6 +67,12 @@ nx release [specifier] #### Options +##### first-release + +Type: `boolean` + +Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release. + ##### help Type: `boolean` @@ -107,6 +113,12 @@ nx release version [specifier] #### Options +##### first-release + +Type: `boolean` + +Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release. + ##### git-commit Type: `boolean` diff --git a/e2e/release/src/release.test.ts b/e2e/release/src/release.test.ts index 1f719ba0c6624..b8bca8e5f9f91 100644 --- a/e2e/release/src/release.test.ts +++ b/e2e/release/src/release.test.ts @@ -1065,8 +1065,8 @@ ${JSON.stringify( }, }, git: { - commit: true, - tag: true, + commit: false, + tag: false, }, version: { generatorOptions: { @@ -1077,11 +1077,11 @@ ${JSON.stringify( return nxJson; }); - const releaseOutput4 = runCLI(`release patch --skip-publish`, { + const releaseOutput4a = runCLI(`release patch --skip-publish`, { silenceError: true, }); - expect(releaseOutput4).toMatchInlineSnapshot(` + expect(releaseOutput4a).toMatchInlineSnapshot(` > NX Running release version for project: {project-name} @@ -1092,21 +1092,40 @@ ${JSON.stringify( `); + const releaseOutput4b = runCLI( + `release patch --skip-publish --first-release`, + { + silenceError: true, + } + ); + + expect(releaseOutput4b).toMatch( + `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.0` + ); + expect( + releaseOutput4b.match( + new RegExp( + `📄 Using the current version 1400\\.1\\.0 already resolved from disk fallback\\.`, + 'g' + ) + ).length + ).toEqual(2); + updateJson('nx.json', (nxJson) => { nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = 'disk'; return nxJson; }); - const releaseOutput5 = runCLI(`release patch --skip-publish --verbose`); + const releaseOutput5 = runCLI(`release patch --skip-publish`); expect(releaseOutput5).toMatch( - `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.0` + `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.1` ); expect( releaseOutput5.match( new RegExp( - `📄 Using the current version 1400\\.1\\.0 already resolved from disk fallback\\.`, + `📄 Using the current version 1400\\.1\\.1 already resolved from disk fallback\\.`, 'g' ) ).length @@ -1123,11 +1142,11 @@ ${JSON.stringify( return nxJson; }); - const releaseOutput6 = runCLI(`release patch --skip-publish`, { + const releaseOutput6a = runCLI(`release patch --skip-publish`, { silenceError: true, }); - expect(releaseOutput6).toMatchInlineSnapshot(` + expect(releaseOutput6a).toMatchInlineSnapshot(` > NX Running release version for project: {project-name} @@ -1139,6 +1158,25 @@ ${JSON.stringify( `); + const releaseOutput6b = runCLI( + `release patch --skip-publish --first-release`, + { + silenceError: true, + } + ); + + expect(releaseOutput6b).toMatch( + `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.2` + ); + expect( + releaseOutput6b.match( + new RegExp( + `📄 Using the current version 1400\\.1\\.2 already resolved from disk fallback\\.`, + 'g' + ) + ).length + ).toEqual(2); + updateJson('nx.json', (nxJson) => { nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = 'disk'; @@ -1148,12 +1186,12 @@ ${JSON.stringify( const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`); expect(releaseOutput7).toMatch( - `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.1` + `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.3` ); expect( releaseOutput7.match( new RegExp( - `📄 Using the current version 1400\\.1\\.1 already resolved from disk fallback\\.`, + `📄 Using the current version 1400\\.1\\.3 already resolved from disk fallback\\.`, 'g' ) ).length diff --git a/packages/js/src/generators/release-version/release-version.ts b/packages/js/src/generators/release-version/release-version.ts index 3b34a71f8286e..8107bb3a0ea72 100644 --- a/packages/js/src/generators/release-version/release-version.ts +++ b/packages/js/src/generators/release-version/release-version.ts @@ -47,6 +47,11 @@ export async function releaseVersionGenerator( options.specifier = options.specifier.replace(/^v/, ''); } + if (options.firstRelease) { + // always use disk as a fallback for the first release + options.fallbackCurrentVersionResolver = 'disk'; + } + const projects = options.projects; const createResolvePackageRoot = diff --git a/packages/nx/src/command-line/release/command-object.ts b/packages/nx/src/command-line/release/command-object.ts index a6a7acd2a4caf..d4d8778e01c01 100644 --- a/packages/nx/src/command-line/release/command-object.ts +++ b/packages/nx/src/command-line/release/command-object.ts @@ -31,6 +31,7 @@ export type VersionOptions = NxReleaseArgs & specifier?: string; preid?: string; stageChanges?: boolean; + firstRelease?: boolean; }; export type ChangelogOptions = NxReleaseArgs & @@ -55,6 +56,7 @@ export type PublishOptions = NxReleaseArgs & export type ReleaseOptions = NxReleaseArgs & { yes?: boolean; skipPublish?: boolean; + firstRelease?: boolean; }; export const yargsReleaseCommand: CommandModule< @@ -145,6 +147,11 @@ const releaseCommand: CommandModule = { description: 'Skip publishing by automatically answering no to the confirmation prompt for publishing', }) + .option('first-release', { + type: 'boolean', + description: + 'Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release.', + }) .check((argv) => { if (argv.yes !== undefined && argv.skipPublish !== undefined) { throw new Error( @@ -188,6 +195,11 @@ const versionCommand: CommandModule = { describe: 'Whether or not to stage the changes made by this command. Useful when combining this command with changelog generation.', }) + .option('first-release', { + type: 'boolean', + description: + 'Indicates that this is the first release for the selected release group. If the current version cannot be determined as usual, the version on disk will be used as a fallback. This is useful when using git or the registry to determine the current version of packages, since those sources are only available after the first release.', + }) ), handler: (args) => import('./version') diff --git a/packages/nx/src/command-line/release/version.ts b/packages/nx/src/command-line/release/version.ts index 709626455a67e..b86a3b3ab117f 100644 --- a/packages/nx/src/command-line/release/version.ts +++ b/packages/nx/src/command-line/release/version.ts @@ -57,6 +57,7 @@ export interface ReleaseVersionGeneratorSchema { currentVersionResolver?: 'registry' | 'disk' | 'git-tag'; currentVersionResolverMetadata?: Record; fallbackCurrentVersionResolver?: 'disk'; + firstRelease?: boolean; } export interface NxReleaseVersionResult { @@ -371,6 +372,7 @@ async function runVersionOnProjects( projects: projectNames.map((p) => projectGraph.nodes[p]), projectGraph, releaseGroup, + firstRelease: args.firstRelease ?? false, }; // Apply generator defaults from schema.json file etc