UNPKG

nx

Version:

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

221 lines (220 loc) • 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.readRawVersionPlans = readRawVersionPlans; exports.setResolvedVersionPlansOnGroups = setResolvedVersionPlansOnGroups; exports.getVersionPlansAbsolutePath = getVersionPlansAbsolutePath; const node_child_process_1 = require("node:child_process"); const node_fs_1 = require("node:fs"); const promises_1 = require("node:fs/promises"); const path_1 = require("path"); const semver_1 = require("semver"); const workspace_root_1 = require("../../../utils/workspace-root"); const config_1 = require("./config"); const fm = require('front-matter'); const versionPlansDirectory = (0, path_1.join)('.nx', 'version-plans'); async function readRawVersionPlans() { const versionPlansPath = getVersionPlansAbsolutePath(); if (!(0, node_fs_1.existsSync)(versionPlansPath)) { return []; } const versionPlans = []; const versionPlanFiles = (0, node_fs_1.readdirSync)(versionPlansPath); for (const versionPlanFile of versionPlanFiles) { const filePath = (0, path_1.join)(versionPlansPath, versionPlanFile); const versionPlanContent = (0, node_fs_1.readFileSync)(filePath).toString(); const versionPlanStats = await (0, promises_1.stat)(filePath); const parsedContent = fm(versionPlanContent); versionPlans.push({ absolutePath: filePath, relativePath: (0, path_1.join)(versionPlansDirectory, versionPlanFile), fileName: versionPlanFile, content: parsedContent.attributes, message: parsedContent.body, createdOnMs: versionPlanStats.birthtimeMs, }); } return versionPlans; } async function setResolvedVersionPlansOnGroups(rawVersionPlans, releaseGroups, allProjectNamesInWorkspace, isVerbose) { const groupsByName = releaseGroups.reduce((acc, group) => acc.set(group.name, group), new Map()); const isDefaultGroup = isDefault(releaseGroups); for (const rawVersionPlan of rawVersionPlans) { if (!rawVersionPlan.message) { throw new Error(`Please add a changelog message to version plan: '${rawVersionPlan.fileName}'`); } for (const [key, value] of Object.entries(rawVersionPlan.content)) { if (groupsByName.has(key)) { const group = groupsByName.get(key); if (!group.resolvedVersionPlans) { if (isDefaultGroup) { throw new Error(`Found a version bump in '${rawVersionPlan.fileName}' but version plans are not enabled.`); } else { throw new Error(`Found a version bump for group '${key}' in '${rawVersionPlan.fileName}' but the group does not have version plans enabled.`); } } if (group.projectsRelationship === 'independent') { if (isDefaultGroup) { throw new Error(`Found a version bump in '${rawVersionPlan.fileName}' but projects are configured to be independently versioned. Individual projects should be bumped instead.`); } else { throw new Error(`Found a version bump for group '${key}' in '${rawVersionPlan.fileName}' but the group's projects are independently versioned. Individual projects of '${key}' should be bumped instead.`); } } if (!isReleaseType(value)) { if (isDefaultGroup) { throw new Error(`Found a version bump in '${rawVersionPlan.fileName}' with an invalid release type. Please specify one of ${semver_1.RELEASE_TYPES.join(', ')}.`); } else { throw new Error(`Found a version bump for group '${key}' in '${rawVersionPlan.fileName}' with an invalid release type. Please specify one of ${semver_1.RELEASE_TYPES.join(', ')}.`); } } const existingPlan = (group.resolvedVersionPlans.find((plan) => plan.fileName === rawVersionPlan.fileName)); if (existingPlan) { if (existingPlan.groupVersionBump !== value) { if (isDefaultGroup) { throw new Error(`Found a version bump in '${rawVersionPlan.fileName}' that conflicts with another version bump. When in fixed versioning mode, all version bumps must match.`); } else { throw new Error(`Found a version bump for group '${key}' in '${rawVersionPlan.fileName}' that conflicts with another version bump for this group. When the group is in fixed versioning mode, all groups' version bumps within the same version plan must match.`); } } } else { group.resolvedVersionPlans.push({ absolutePath: rawVersionPlan.absolutePath, relativePath: rawVersionPlan.relativePath, fileName: rawVersionPlan.fileName, createdOnMs: rawVersionPlan.createdOnMs, message: rawVersionPlan.message, groupVersionBump: value, commit: await getCommitForVersionPlanFile(rawVersionPlan, isVerbose), }); } } else { const groupForProject = releaseGroups.find((group) => group.projects.includes(key)); if (!groupForProject) { if (!allProjectNamesInWorkspace.includes(key)) { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' but the project does not exist in the workspace.`); } if (isDefaultGroup) { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' but the project is not configured for release. Ensure it is included by the 'release.projects' globs in nx.json.`); } else { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' but the project is not in any configured release groups.`); } } if (!groupForProject.resolvedVersionPlans) { if (isDefaultGroup) { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' but version plans are not enabled.`); } else { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' but the project's group '${groupForProject.name}' does not have version plans enabled.`); } } if (!isReleaseType(value)) { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' with an invalid release type. Please specify one of ${semver_1.RELEASE_TYPES.join(', ')}.`); } if (groupForProject.projectsRelationship === 'independent') { const existingPlan = (groupForProject.resolvedVersionPlans.find((plan) => plan.fileName === rawVersionPlan.fileName)); if (existingPlan) { existingPlan.projectVersionBumps[key] = value; } else { groupForProject.resolvedVersionPlans.push({ absolutePath: rawVersionPlan.absolutePath, relativePath: rawVersionPlan.relativePath, fileName: rawVersionPlan.fileName, createdOnMs: rawVersionPlan.createdOnMs, message: rawVersionPlan.message, projectVersionBumps: { [key]: value, }, commit: await getCommitForVersionPlanFile(rawVersionPlan, isVerbose), }); } } else { const existingPlan = (groupForProject.resolvedVersionPlans.find((plan) => plan.fileName === rawVersionPlan.fileName)); // This can occur if the same fixed release group has multiple entries for different projects within // the same version plan file. This will be the case when users are using the default release group. if (existingPlan) { if (existingPlan.groupVersionBump !== value) { if (isDefaultGroup) { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' that conflicts with another version bump. When in fixed versioning mode, all version bumps must match.`); } else { throw new Error(`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' that conflicts with another project's version bump in the same release group '${groupForProject.name}'. When the group is in fixed versioning mode, all projects' version bumps within the same group must match.`); } } else { existingPlan.triggeredByProjects.push(key); } } else { groupForProject.resolvedVersionPlans.push({ absolutePath: rawVersionPlan.absolutePath, relativePath: rawVersionPlan.relativePath, fileName: rawVersionPlan.fileName, createdOnMs: rawVersionPlan.createdOnMs, message: rawVersionPlan.message, // This is a fixed group, so the version bump is for the group, even if a project within it was specified // but we track the projects that triggered the version bump so that we can accurately produce changelog entries. groupVersionBump: value, triggeredByProjects: [key], commit: await getCommitForVersionPlanFile(rawVersionPlan, isVerbose), }); } } } } } // Order the plans from newest to oldest releaseGroups.forEach((group) => { if (group.resolvedVersionPlans) { group.resolvedVersionPlans.sort((a, b) => b.createdOnMs - a.createdOnMs); } }); return releaseGroups; } function isDefault(releaseGroups) { return (releaseGroups.length === 1 && releaseGroups.some((group) => group.name === config_1.IMPLICIT_DEFAULT_RELEASE_GROUP)); } function getVersionPlansAbsolutePath() { return (0, path_1.join)(workspace_root_1.workspaceRoot, versionPlansDirectory); } function isReleaseType(value) { return semver_1.RELEASE_TYPES.includes(value); } async function getCommitForVersionPlanFile(rawVersionPlan, isVerbose) { return new Promise((resolve) => { (0, node_child_process_1.exec)(`git log --diff-filter=A --pretty=format:"%s|%h|%an|%ae|%b" -n 1 -- ${rawVersionPlan.absolutePath}`, { windowsHide: false, }, (error, stdout, stderr) => { if (error) { if (isVerbose) { console.error(`Error executing git command for ${rawVersionPlan.relativePath}: ${error.message}`); } return resolve(null); } if (stderr) { if (isVerbose) { console.error(`Git command stderr for ${rawVersionPlan.relativePath}: ${stderr}`); } return resolve(null); } const [message, shortHash, authorName, authorEmail, ...body] = stdout .trim() .split('|'); const commitDetails = { message: message || '', shortHash: shortHash || '', author: { name: authorName || '', email: authorEmail || '' }, body: body.join('|') || '', // Handle case where body might be empty or contain multiple '|' }; return resolve(commitDetails); }); }); }