UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

242 lines (240 loc) 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveCurrentVersion = resolveCurrentVersion; exports.resolveCurrentVersionFromDisk = resolveCurrentVersionFromDisk; exports.resolveCurrentVersionFromRegistry = resolveCurrentVersionFromRegistry; exports.resolveCurrentVersionFromGitTag = resolveCurrentVersionFromGitTag; const chalk = require("chalk"); const enquirer_1 = require("enquirer"); const ora = require("ora"); async function resolveCurrentVersion(tree, projectGraphNode, releaseGroup, versionActions, logger, cachedCurrentVersionsPerFixedReleaseGroup, finalConfigForProject, releaseTagPattern, latestMatchingGitTag) { switch (finalConfigForProject.currentVersionResolver) { case 'none': return null; case 'disk': { return resolveCurrentVersionFromDisk(tree, projectGraphNode, versionActions, logger); } case 'registry': { return resolveCurrentVersionFromRegistry(tree, projectGraphNode, releaseGroup, versionActions, logger, cachedCurrentVersionsPerFixedReleaseGroup, finalConfigForProject); } case 'git-tag': { return resolveCurrentVersionFromGitTag(tree, projectGraphNode, releaseGroup, versionActions, logger, cachedCurrentVersionsPerFixedReleaseGroup, finalConfigForProject, releaseTagPattern, latestMatchingGitTag); } default: throw new Error(`Invalid value for "currentVersionResolver": ${finalConfigForProject.currentVersionResolver}`); } } /** * Attempt to resolve the current version from the manifest file on disk. * * Not all VersionActions implementations support a manifest file, in which case the logic will handle either thrown errors * or null values being returned from the readCurrentVersionFromSourceManifest method and throw a clear user-facing error. */ async function resolveCurrentVersionFromDisk(tree, projectGraphNode, versionActions, logger) { if (!versionActions.validManifestFilenames?.length) { throw new Error(`For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "disk" but it is using "versionActions" of type "${versionActions.constructor.name}". This is invalid because "${versionActions.constructor.name}" does not support a manifest file. You should use a different "currentVersionResolver" or use a different "versionActions" implementation that supports a manifest file`); } const nullVersionError = new Error(`For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "disk" and it is using "versionActions" of type "${versionActions.constructor.name}" which failed to resolve the current version from the manifest file on disk`); try { const res = await versionActions.readCurrentVersionFromSourceManifest(tree); if (!res) { throw nullVersionError; } const { currentVersion, manifestPath } = res; logger.buffer(`📄 Resolved the current version as ${currentVersion} from manifest: ${manifestPath}`); return currentVersion; } catch (err) { if (err === nullVersionError) { throw err; } throw new Error(`The project "${projectGraphNode.name}" does not have a ${versionActions.validManifestFilenames.join(' or ')} file available in ./${projectGraphNode.data.root}. To fix this you will either need to add a ${versionActions.validManifestFilenames.join(' or ')} file at that location, or configure "release" within your nx.json to use a different "currentVersionResolver" or "versionActions" implementation that supports this setup`); } } async function resolveCurrentVersionFromRegistry(tree, projectGraphNode, releaseGroup, versionActions, logger, cachedCurrentVersionsPerFixedReleaseGroup, finalConfigForProject) { /** * In the case of fixed release groups that are configured to resolve the current version from a registry, * it would be a waste of time and resources to make requests to the registry, or resolve one of the fallbacks, * for each individual project, therefore we maintain a cache of the current version for each applicable release group here. */ const cached = cachedCurrentVersionsPerFixedReleaseGroup.get(releaseGroup.name); if (cached) { const logText = cached.logText ? ` ${cached.logText}` : ''; logger.buffer(`🔄 Reusing the current version ${cached.currentVersion} already resolved for ${cached.originatingProjectName}${logText}`); return cached.currentVersion; } let registryTxt = ''; const spinner = ora(`Resolving the current version for ${projectGraphNode.name} from the configured registry...`); spinner.color = 'cyan'; spinner.start(); try { const res = await versionActions.readCurrentVersionFromRegistry(tree, finalConfigForProject.currentVersionResolverMetadata); if (!res) { // Not a user-facing error throw new Error('Registry not applicable for this version actions implementation'); } const { currentVersion, logText } = res; spinner.stop(); registryTxt = logText?.length > 0 ? `: ${logText}` : ''; if (!currentVersion) { // Not a user-facing error throw new Error('No version found in the registry'); } logger.buffer(`🔍 Resolved the current version as ${currentVersion} from the remote registry${registryTxt}`); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion, originatingProjectName: projectGraphNode.name, logText: `from the registry${registryTxt}`, }); } return currentVersion; } catch { spinner.stop(); if (finalConfigForProject.fallbackCurrentVersionResolver === 'disk') { if (!versionActions.validManifestFilenames?.length) { throw new Error(`For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "registry" with a "fallbackCurrentVersionResolver" of "disk" but it is using "versionActions" of type "${versionActions.constructor.name}". This is invalid because "${versionActions.constructor.name}" does not support a manifest file. You should use a different "fallbackCurrentVersionResolver" or use a different "versionActions" implementation that supports a manifest file`); } const fromDiskRes = await versionActions.readCurrentVersionFromSourceManifest(tree); // Fallback on disk is available, return it directly if (fromDiskRes && fromDiskRes.currentVersion) { logger.buffer(`⚠️ Unable to resolve the current version from the registry${registryTxt}. Falling back to the version ${fromDiskRes.currentVersion} in manifest: ${fromDiskRes.manifestPath}`); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion: fromDiskRes.currentVersion, originatingProjectName: projectGraphNode.name, logText: `from the disk fallback`, }); } return fromDiskRes.currentVersion; } } // At this point the fallback on disk is also not available/configured, allow one final interactive fallback, but only when using version-plans or conventional-commits if (finalConfigForProject.specifierSource === 'prompt') { throw new Error(`Unable to resolve the current version from the registry${registryTxt}. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.`); } const currentVersionFromPromptFallback = await handleNoAvailableDiskFallback({ logger, projectName: projectGraphNode.name, versionActions, specifierSource: finalConfigForProject.specifierSource, currentVersionSourceMessage: `from the registry${registryTxt}`, resolutionSuggestion: `you should publish an initial version to the registry`, }); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion: currentVersionFromPromptFallback, originatingProjectName: projectGraphNode.name, logText: `from the prompt fallback`, }); } return currentVersionFromPromptFallback; } } async function resolveCurrentVersionFromGitTag(tree, projectGraphNode, releaseGroup, versionActions, logger, cachedCurrentVersionsPerFixedReleaseGroup, finalConfigForProject, releaseTagPattern, latestMatchingGitTag) { /** * In the case of fixed release groups that are configured to resolve the current version from a git tag, * it would be a waste of time and resources to figure out the git tag, or resolve one of the fallbacks, * for each individual project, therefore we maintain a cache of the current version for each applicable release group here. */ const cached = cachedCurrentVersionsPerFixedReleaseGroup.get(releaseGroup.name); if (cached) { const logText = cached.logText ? ` ${cached.logText}` : ''; logger.buffer(`🔄 Reusing the current version ${cached.currentVersion} already resolved for ${cached.originatingProjectName}${logText}`); return cached.currentVersion; } // The latest matching git tag was found in release-group-processor and has an extracted version, return it directly if (latestMatchingGitTag && latestMatchingGitTag.extractedVersion) { const currentVersion = latestMatchingGitTag.extractedVersion; logger.buffer(`🏷️ Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}", based on releaseTagPattern "${releaseTagPattern}"`); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion, originatingProjectName: projectGraphNode.name, logText: `from git tag "${latestMatchingGitTag.tag}"`, }); } return currentVersion; } const noMatchingGitTagsError = new Error(`No git tags matching pattern "${releaseTagPattern}" for project "${projectGraphNode.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 use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found.`); if (finalConfigForProject.fallbackCurrentVersionResolver !== 'disk') { throw noMatchingGitTagsError; } const fromDiskRes = await versionActions.readCurrentVersionFromSourceManifest(tree); // Fallback on disk is available, return it directly if (fromDiskRes && fromDiskRes.currentVersion) { logger.buffer(`⚠️ Unable to resolve the current version from git tags using pattern "${releaseTagPattern}". Falling back to the version ${fromDiskRes.currentVersion} in manifest: ${fromDiskRes.manifestPath}`); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion: fromDiskRes.currentVersion, originatingProjectName: projectGraphNode.name, logText: `from the disk fallback`, }); } return fromDiskRes.currentVersion; } // At this point the fallback on disk is also not available/configured, allow one final interactive fallback, but only when using version-plans or conventional-commits if (finalConfigForProject.specifierSource === 'prompt') { throw noMatchingGitTagsError; } const currentVersionFromPromptFallback = await handleNoAvailableDiskFallback({ logger, projectName: projectGraphNode.name, versionActions, specifierSource: finalConfigForProject.specifierSource, currentVersionSourceMessage: `from git tag using pattern "${releaseTagPattern}"`, resolutionSuggestion: `you should set an initial git tag on a relevant commit`, }); // Write to the cache if the release group is fixed if (releaseGroup.projectsRelationship === 'fixed') { cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { currentVersion: currentVersionFromPromptFallback, originatingProjectName: projectGraphNode.name, logText: `from the prompt fallback`, }); } return currentVersionFromPromptFallback; } /** * Allow users to be unblocked when locally running releases for the very first time with certain combinations that require an initial * version in order to function (e.g. a relative semver bump derived via conventional-commits or version-plans) by providing an interactive * prompt to let them opt into using 0.0.0 as the implied current version. */ async function handleNoAvailableDiskFallback({ logger, projectName, versionActions, specifierSource, currentVersionSourceMessage, resolutionSuggestion, }) { if (!versionActions.validManifestFilenames?.length) { throw new Error(`Unable to resolve the current version ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version. To resolve this, ${resolutionSuggestion}, or set use a versionActions implementation that supports a manifest file`); } const validManifestFilenames = versionActions.validManifestFilenames.join(' or '); const unresolvableCurrentVersionError = new Error(`Unable to resolve the current version ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version. To resolve this, ${resolutionSuggestion}, or set an appropriate version in a supported manifest file such as ${validManifestFilenames}`); if (process.env.CI === 'true') { // We can't prompt in CI, so error immediately throw unresolvableCurrentVersionError; } try { const reply = await (0, enquirer_1.prompt)([ { name: 'useZero', message: `\n${chalk.yellow(`Warning: Unable to resolve the current version for "${projectName}" ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version.\n\nTo resolve this, ${resolutionSuggestion}, or set an appropriate version in a supported manifest file such as ${validManifestFilenames}`)}. \n\nAlternatively, would you like to continue now by using 0.0.0 as the current version?`, type: 'confirm', initial: false, }, ]); if (!reply.useZero) { // Throw any error to skip the fallback to 0.0.0, may as well use the one we already have throw unresolvableCurrentVersionError; } const currentVersion = '0.0.0'; logger.buffer(`⚠ Forcibly resolved the current version as "${currentVersion}" based on your response to the prompt above`); return currentVersion; } catch { throw unresolvableCurrentVersionError; } }