UNPKG

nx

Version:

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

220 lines (219 loc) • 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReleaseVersion = exports.noDiffInChangelogMessage = void 0; exports.commitChanges = commitChanges; exports.createCommitMessageValues = createCommitMessageValues; exports.createGitTagValues = createGitTagValues; exports.handleDuplicateGitTags = handleDuplicateGitTags; exports.getCommitsRelevantToProjects = getCommitsRelevantToProjects; const chalk = require("chalk"); const semver_1 = require("semver"); const file_map_utils_1 = require("../../../project-graph/file-map-utils"); const utils_1 = require("../../../tasks-runner/utils"); const output_1 = require("../../../utils/output"); const git_1 = require("./git"); exports.noDiffInChangelogMessage = chalk.yellow(`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`); function isPrerelease(version) { // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease return (0, semver_1.prerelease)(version) !== null; } class ReleaseVersion { constructor({ version, // short form version string with no prefixes or patterns, e.g. 1.0.0 releaseTagPattern, // full pattern to interpolate, e.g. "v{version}" or "{projectName}@{version}" projectName, // optional project name to interpolate into the releaseTagPattern }) { this.rawVersion = version; this.gitTag = (0, utils_1.interpolate)(releaseTagPattern, { version, projectName, }); this.isPrerelease = isPrerelease(version); } } exports.ReleaseVersion = ReleaseVersion; async function commitChanges({ changedFiles, deletedFiles, isDryRun, isVerbose, gitCommitMessages, gitCommitArgs, }) { if (!changedFiles?.length && !deletedFiles?.length) { throw new Error('Error: No changed files to commit'); } output_1.output.logSingleLine(`Committing changes with git`); await (0, git_1.gitAdd)({ changedFiles, deletedFiles, dryRun: isDryRun, verbose: isVerbose, }); // The extra logs need something breathing room if (isVerbose) { console.log(''); } await (0, git_1.gitCommit)({ messages: gitCommitMessages, additionalArgs: gitCommitArgs, dryRun: isDryRun, verbose: isVerbose, }); } function createCommitMessageValues(releaseGroups, releaseGroupToFilteredProjects, versionData, commitMessage) { const commitMessageValues = [commitMessage]; if (releaseGroups.length === 0) { return commitMessageValues; } // If we have exactly one release group, with a fixed relationship, then interpolate {version} as the new version for the release group if (releaseGroups.length === 1 && releaseGroups[0].projectsRelationship === 'fixed') { const releaseGroup = releaseGroups[0]; const releaseGroupProjectNames = Array.from(releaseGroupToFilteredProjects.get(releaseGroup)); const projectVersionData = versionData[releaseGroupProjectNames[0]]; // all at the same version, so we can just pick the first one const releaseVersion = new ReleaseVersion({ version: projectVersionData.newVersion, releaseTagPattern: releaseGroup.releaseTagPattern, }); commitMessageValues[0] = (0, utils_1.interpolate)(commitMessageValues[0], { version: releaseVersion.rawVersion, }).trim(); return commitMessageValues; } /** * There is another special case for interpolation: if, after all filtering, we have a single independent release group with a single project, * and the user has provided {projectName} within the custom message. * In this case we will directly interpolate both {version} and {projectName} within the commit message. */ if (releaseGroups.length === 1 && releaseGroups[0].projectsRelationship === 'independent' && commitMessage.includes('{projectName}')) { const releaseGroup = releaseGroups[0]; const releaseGroupProjectNames = Array.from(releaseGroupToFilteredProjects.get(releaseGroup)); if (releaseGroupProjectNames.length === 1) { const projectVersionData = versionData[releaseGroupProjectNames[0]]; const releaseVersion = new ReleaseVersion({ version: projectVersionData.newVersion, releaseTagPattern: releaseGroup.releaseTagPattern, projectName: releaseGroupProjectNames[0], }); commitMessageValues[0] = (0, utils_1.interpolate)(commitMessageValues[0], { version: releaseVersion.rawVersion, projectName: releaseGroupProjectNames[0], }).trim(); return commitMessageValues; } } /** * At this point we have multiple release groups for a single commit, we will not interpolate an overall {version} or {projectName} because that won't be * appropriate (for any {version} or {projectName} value within the string, we will replace it with an empty string so that it doesn't end up in the final output). * * Instead for fixed groups we will add one bullet point the release group, and for independent groups we will add one bullet point per project. */ commitMessageValues[0] = stripPlaceholders(commitMessageValues[0], [ // for cleanest possible final result try and replace the common pattern of a v prefix in front of the version first 'v{version}', '{version}', '{projectName}', ]); for (const releaseGroup of releaseGroups) { const releaseGroupProjectNames = Array.from(releaseGroupToFilteredProjects.get(releaseGroup)); // One entry per project for independent groups if (releaseGroup.projectsRelationship === 'independent') { for (const project of releaseGroupProjectNames) { const projectVersionData = versionData[project]; if (projectVersionData.newVersion !== null) { const releaseVersion = new ReleaseVersion({ version: projectVersionData.newVersion, releaseTagPattern: releaseGroup.releaseTagPattern, projectName: project, }); commitMessageValues.push(`- project: ${project} ${releaseVersion.rawVersion}`); } } continue; } // One entry for the whole group for fixed groups const projectVersionData = versionData[releaseGroupProjectNames[0]]; // all at the same version, so we can just pick the first one if (projectVersionData.newVersion !== null) { const releaseVersion = new ReleaseVersion({ version: projectVersionData.newVersion, releaseTagPattern: releaseGroup.releaseTagPattern, }); commitMessageValues.push(`- release-group: ${releaseGroup.name} ${releaseVersion.rawVersion}`); } } return commitMessageValues; } function stripPlaceholders(str, placeholders) { for (const placeholder of placeholders) { // for cleanest possible final result try and replace relevant spacing around placeholders first str = str .replace(` ${placeholder}`, '') .replace(`${placeholder} `, '') .replace(placeholder, '') .trim(); } return str; } function createGitTagValues(releaseGroups, releaseGroupToFilteredProjects, versionData) { const tags = []; for (const releaseGroup of releaseGroups) { const releaseGroupProjectNames = Array.from(releaseGroupToFilteredProjects.get(releaseGroup)); // For independent groups we want one tag per project, not one for the overall group if (releaseGroup.projectsRelationship === 'independent') { for (const project of releaseGroupProjectNames) { const projectVersionData = versionData[project]; if (projectVersionData.newVersion !== null) { tags.push((0, utils_1.interpolate)(releaseGroup.releaseTagPattern, { version: projectVersionData.newVersion, projectName: project, })); } } continue; } // For fixed groups we want one tag for the overall group const projectVersionData = versionData[releaseGroupProjectNames[0]]; // all at the same version, so we can just pick the first one if (projectVersionData.newVersion !== null) { tags.push((0, utils_1.interpolate)(releaseGroup.releaseTagPattern, { version: projectVersionData.newVersion, releaseGroupName: releaseGroup.name, })); } } return tags; } function findDuplicates(arr) { const seen = new Set(); const duplicates = new Set(); for (const item of arr) { if (seen.has(item)) { duplicates.add(item); } else { seen.add(item); } } return Array.from(duplicates); } function handleDuplicateGitTags(gitTagValues) { // If any of the gitTagValues are identical we should hard error upfront to avoid an awkward git error later const duplicateGitTagValues = findDuplicates(gitTagValues); if (duplicateGitTagValues.length) { output_1.output.error({ title: `Your current configuration would generate the following duplicate git tags:`, bodyLines: [ ...duplicateGitTagValues.map((v) => `- ${v}`), '', `Please ensure that for "independent" release groups the {projectName} placeholder is used so that all dynamically created project tags are unique.`, ], }); process.exit(1); } } async function getCommitsRelevantToProjects(projectGraph, commits, projects) { const { fileMap } = await (0, file_map_utils_1.createFileMapUsingProjectGraph)(projectGraph); const filesInReleaseGroup = new Set(projects.reduce((files, p) => [...files, ...fileMap.projectFileMap[p].map((f) => f.file)], [])); /** * The relevant commits are those that either: * - touch project files which are contained within the list of projects directly * - touch non-project files and the commit is not scoped */ return commits.filter((c) => c.affectedFiles.some((f) => filesInReleaseGroup.has(f) || (!c.scope && fileMap.nonProjectFiles.some((nonProjectFile) => nonProjectFile.file === f)))); }