nx
Version:
184 lines (180 loc) • 10.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.VersionActions = void 0;
exports.resolveVersionActionsForProject = resolveVersionActionsForProject;
const node_path_1 = require("node:path");
const register_1 = require("../../../plugins/js/utils/register");
const typescript_1 = require("../../../plugins/js/utils/typescript");
const utils_1 = require("../../../tasks-runner/utils");
const workspace_root_1 = require("../../../utils/workspace-root");
const config_1 = require("../config/config");
const semver_1 = require("../utils/semver");
const release_group_processor_1 = require("./release-group-processor");
function resolveVersionActionsPath(path, projectGraphNode) {
try {
return require.resolve(path);
}
catch {
try {
return require.resolve((0, node_path_1.join)(workspace_root_1.workspaceRoot, path));
}
catch {
if (path === config_1.DEFAULT_VERSION_ACTIONS_PATH) {
throw new Error(`Unable to resolve the default "versionActions" implementation for project "${projectGraphNode.name}" at path: "${path}"
- If this is a JavaScript/TypeScript project, it is likely that you simply need to install the "@nx/js" plugin.
- If this not a JavaScript/TypeScript project, you can should provide an alternative "versionActions" implementation path via the "release.version.versionActions" configuration option.
`);
}
throw new Error(`Unable to resolve the "versionActions" implementation for project "${projectGraphNode.name}" at the configured path: "${path}"`);
}
}
}
const versionActionsResolutionCache = new Map();
async function resolveVersionActionsForProject(tree, releaseGroup, projectGraphNode, finalConfigForProject) {
// Project level release version config takes priority, if set
const projectVersionConfig = projectGraphNode.data.release?.version;
const releaseGroupVersionConfig = releaseGroup.version;
const versionActionsPathConfig = projectVersionConfig?.versionActions ??
releaseGroupVersionConfig?.versionActions ??
null;
if (!versionActionsPathConfig) {
// Should be an impossible state, as we should have defaulted to the JS implementation during config processing
throw new Error(`No versionActions implementation found for project "${projectGraphNode.name}", please report this on https://github.com/nrwl/nx/issues`);
}
let cachedData = versionActionsResolutionCache.get(versionActionsPathConfig);
const versionActionsPath = resolveVersionActionsPath(versionActionsPathConfig, projectGraphNode);
let VersionActionsClass;
let afterAllProjectsVersioned;
if (cachedData) {
VersionActionsClass = cachedData.VersionActionsClass;
afterAllProjectsVersioned = cachedData.afterAllProjectsVersioned;
}
else {
let cleanupTranspiler;
if (versionActionsPath.endsWith('.ts')) {
cleanupTranspiler = (0, register_1.registerTsProject)((0, typescript_1.getRootTsConfigPath)());
}
const loaded = require(versionActionsPath);
cleanupTranspiler?.();
VersionActionsClass = loaded.default ?? loaded;
if (!VersionActionsClass) {
throw new Error(`For project "${projectGraphNode.name}" it was not possible to resolve the VersionActions implementation from: "${versionActionsPath}"`);
}
afterAllProjectsVersioned =
loaded.afterAllProjectsVersioned ??
// no-op fallback for ecosystems/use-cases where it is not applicable
(() => Promise.resolve({
changedFiles: [],
deletedFiles: [],
}));
versionActionsResolutionCache.set(versionActionsPath, {
VersionActionsClass,
afterAllProjectsVersioned,
});
}
const versionActions = new VersionActionsClass(releaseGroup, projectGraphNode, finalConfigForProject);
// Initialize the version actions with all the required manifest paths etc
await versionActions.init(tree);
return {
versionActionsPath,
versionActions,
afterAllProjectsVersioned,
};
}
class VersionActions {
constructor(releaseGroup, projectGraphNode, finalConfigForProject) {
this.releaseGroup = releaseGroup;
this.projectGraphNode = projectGraphNode;
this.finalConfigForProject = finalConfigForProject;
/**
* The interpolated manifest paths to update, if applicable based on the user's configuration, when new
* versions and dependencies are determined. If no manifest files should be updated based on the user's
* configuration, this will be an empty array.
*/
this.manifestsToUpdate = [];
}
/**
* Asynchronous initialization of the version actions and validation of certain configuration options.
*/
async init(tree) {
// Default to the first available source manifest root, if applicable, if no custom manifest roots are provided
if (this.validManifestFilenames?.length &&
this.finalConfigForProject.manifestRootsToUpdate.length === 0) {
for (const manifestFilename of this.validManifestFilenames) {
if (tree.exists((0, node_path_1.join)(this.projectGraphNode.data.root, manifestFilename))) {
this.finalConfigForProject.manifestRootsToUpdate.push(this.projectGraphNode.data.root);
break;
}
}
}
const interpolatedManifestRoots = this.finalConfigForProject.manifestRootsToUpdate.map((manifestRoot) => {
return (0, utils_1.interpolate)(manifestRoot, {
workspaceRoot: '',
projectRoot: this.projectGraphNode.data.root,
projectName: this.projectGraphNode.name,
});
});
for (const interpolatedManifestRoot of interpolatedManifestRoots) {
let hasValidManifest = false;
for (const manifestFilename of this.validManifestFilenames) {
const manifestPath = (0, node_path_1.join)(interpolatedManifestRoot, manifestFilename);
if (tree.exists(manifestPath)) {
this.manifestsToUpdate.push(manifestPath);
hasValidManifest = true;
break;
}
}
if (!hasValidManifest) {
const validManifestFilenames = this.validManifestFilenames?.join(' or ');
throw new Error(`The project "${this.projectGraphNode.name}" does not have a ${validManifestFilenames} file available in ./${interpolatedManifestRoot}.
To fix this you will either need to add a ${validManifestFilenames} file at that location, or configure "release" within your nx.json to exclude "${this.projectGraphNode.name}" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.`);
}
}
}
/**
* The default implementation will calculate the new version based on semver. If semver is not applicable to a
* particular versioning use-case, this method should be overridden with custom logic.
*
* @param {string | null} currentVersion - The current version of the project, or null if the current version resolver is set to 'none'
* @param {string} newVersionInput - The new version input provided by the user, such as a semver relative bump type, or an explicit version
* @param {string} newVersionInputReason - The reason for the new version input used to inform the log message to show to the user
* @param {Record<string, unknown>} newVersionInputReasonData - The data to interpolate into the new version input reason
* @param {string} preid - The preid to use for the new version, if applicable
*/
async calculateNewVersion(currentVersion, newVersionInput, newVersionInputReason, newVersionInputReasonData, preid) {
const isSemverRelativeBump = (0, semver_1.isRelativeVersionKeyword)(newVersionInput);
const newVersionReasonText = release_group_processor_1.BUMP_TYPE_REASON_TEXT[newVersionInputReason];
if (!newVersionReasonText) {
throw new Error(`Unhandled bump type reason for ${this.projectGraphNode.name} with newVersionInput ${newVersionInput} and newVersionInputReason ${newVersionInputReason}, please report this as a bug on https://github.com/nrwl/nx/issues`);
}
const interpolatedNewVersionInputReasonText = (0, utils_1.interpolate)(newVersionReasonText, newVersionInputReasonData);
if (isSemverRelativeBump && !currentVersion) {
throw new Error(`There was no current version resolved for project "${this.projectGraphNode.name}", but it was configured to be bumped via a semver relative keyword "${newVersionInput}"${interpolatedNewVersionInputReasonText}. This is not a valid combination, please review your release configuration and CLI arguments`);
}
const newVersion = (0, semver_1.deriveNewSemverVersion)(currentVersion, newVersionInput, preid);
const newVersionInputText = (0, semver_1.isRelativeVersionKeyword)(newVersionInput)
? `semver relative bump "${newVersionInput}"`
: `explicit semver value "${newVersionInput}"`;
return {
newVersion,
logText: `❓ Applied ${newVersionInputText}${interpolatedNewVersionInputReasonText}to get new version ${newVersion}`,
};
}
/**
* Implementation details of resolving the dependencies of a project.
*
* The default implementation will read dependencies from the Nx project graph. In many cases this will be sufficient,
* because the project graph will have been constructed using plugins from relevant ecosystems that should have applied
* any and all relevant metadata to the project nodes and dependency edges.
*
* If, however, the project graph cannot be used as the source of truth for whatever reason, then this default method
* can simply be overridden in the final version actions implementation.
*/
async readDependencies(tree, projectGraph) {
return (projectGraph.dependencies[this.projectGraphNode.name] ?? []).filter(
// Skip implicit dependencies for now to match legacy versioning behavior
// TODO: holistically figure out how to handle implicit dependencies with nx release
(dep) => dep.type !== 'implicit');
}
}
exports.VersionActions = VersionActions;