UNPKG

@storm-software/workspace-tools

Version:

Tools for managing a Storm workspace, including various Nx generators and executors for common development tasks.

910 lines (899 loc) • 33.8 kB
import { modifyCargoTable, parseCargoToml, parseCargoTomlWithTree, stringifyCargoToml } from "./chunk-PGQZ6GWB.mjs"; import { getConfig } from "./chunk-IIIBVYB5.mjs"; import { getStopwatch, writeDebug, writeError, writeFatal, writeInfo, writeSuccess, writeTrace } from "./chunk-5AXTWPT3.mjs"; import { findWorkspaceRoot } from "./chunk-LSF4BHCI.mjs"; // src/generators/release-version/generator.ts import { formatFiles, joinPathFragments, output, readJson, updateJson, writeJson } from "@nx/devkit"; import { resolveLocalPackageDependencies as resolveLocalPackageJsonDependencies } from "@nx/js/src/generators/release-version/utils/resolve-local-package-dependencies"; import { updateLockFile } from "@nx/js/src/release/utils/update-lock-file"; // ../git-tools/src/types.ts var DEFAULT_COMMIT_TYPES = { /* --- Bumps version when selected --- */ chore: { description: "Other changes that don't modify src or test files", title: "Chore", emoji: "\u2699\uFE0F ", semverBump: "patch", changelog: { title: "Miscellaneous", hidden: false } }, fix: { description: "A change that resolves an issue previously identified with the package", title: "Bug Fix", emoji: "\u{1FAB2} ", semverBump: "patch", changelog: { title: "Bug Fixes", hidden: false } }, feat: { description: "A change that adds a new feature to the package", title: "Feature", emoji: "\u{1F511} ", semverBump: "minor", changelog: { title: "Features", hidden: false } }, ci: { description: "Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)", title: "Continuous Integration", emoji: "\u{1F9F0} ", semverBump: "patch", changelog: { title: "Continuous Integration", hidden: false } }, refactor: { description: "A code change that neither fixes a bug nor adds a feature", title: "Code Refactoring", emoji: "\u{1F9EA} ", semverBump: "patch", changelog: { title: "Source Code Improvements", hidden: false } }, style: { description: "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", title: "Style Improvements", emoji: "\u{1F48E} ", semverBump: "patch", changelog: { title: "Style Improvements", hidden: false } }, perf: { description: "A code change that improves performance", title: "Performance Improvement", emoji: "\u23F1\uFE0F ", semverBump: "patch", changelog: { title: "Performance Improvements", hidden: false } }, /* --- Does not bump version when selected --- */ docs: { description: "A change that only includes documentation updates", title: "Documentation", emoji: "\u{1F4DC} ", semverBump: "none", changelog: { title: "Documentation", hidden: false } }, test: { description: "Adding missing tests or correcting existing tests", title: "Testing", emoji: "\u{1F6A8} ", semverBump: "none", changelog: { title: "Testing", hidden: true } }, /* --- Not included in commitlint but included in changelog --- */ deps: { description: "Changes that add, update, or remove dependencies. This includes devDependencies and peerDependencies", title: "Dependencies", emoji: "\u{1F4E6} ", hidden: true, semverBump: "patch", changelog: { title: "Dependency Upgrades", hidden: false } }, /* --- Not included in commitlint or changelog --- */ build: { description: "Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)", title: "Build", emoji: "\u{1F6E0} ", hidden: true, semverBump: "none", changelog: { title: "Build", hidden: true } }, release: { description: "Publishing a commit containing a newly released version", title: "Publish Release", emoji: "\u{1F680} ", hidden: true, semverBump: "none", changelog: { title: "Publish Release", hidden: true } } }; var DEFAULT_COMMIT_QUESTIONS = { type: { type: "select", title: "Commit Type", description: "Select the commit type that best describes your changes", enum: Object.keys(DEFAULT_COMMIT_TYPES).filter( (type) => DEFAULT_COMMIT_TYPES[type].hidden !== true ).reduce((ret, type) => { ret[type] = DEFAULT_COMMIT_TYPES[type]; return ret; }, {}), defaultValue: "chore", maxLength: 20, minLength: 3 }, scope: { type: "select", title: "Commit Scope", description: "Select the monorepo project that is primarily impacted by this change", enum: {}, defaultValue: "monorepo", maxLength: 50, minLength: 1 }, subject: { type: "input", title: "Commit Subject", description: "Write a short, imperative tense description of the change", maxLength: 150, minLength: 3 }, body: { type: "input", title: "Commit Body", description: "Provide a longer description of the change", maxLength: 600 }, isBreaking: { type: "confirm", title: "Breaking Changes", description: "Are there any breaking changes as a result of this commit?", defaultValue: false }, breakingBody: { type: "input", title: "Breaking Changes (Details)", description: "A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself", when: (answers) => answers.isBreaking === true, maxLength: 600, minLength: 3 }, isIssueAffected: { type: "confirm", title: "Open Issue Affected", description: "Does this change impact any open issues?", defaultValue: false }, issuesBody: { type: "input", title: "Open Issue Affected (Details)", description: "If issues are closed, the commit requires a body. Please enter a longer description of the commit itself", when: (answers) => answers.isIssueAffected === true, maxLength: 600, minLength: 3 } }; // ../git-tools/src/release/config.ts var DEFAULT_CONVENTIONAL_COMMITS_CONFIG = { questions: DEFAULT_COMMIT_QUESTIONS, types: DEFAULT_COMMIT_TYPES }; // src/generators/release-version/generator.ts import { exec, execSync } from "node:child_process"; import { relative } from "node:path"; import { IMPLICIT_DEFAULT_RELEASE_GROUP } from "nx/src/command-line/release/config/config"; import { getFirstGitCommit, getLatestGitTagForPattern } from "nx/src/command-line/release/utils/git"; import { resolveSemverSpecifierFromConventionalCommits, resolveSemverSpecifierFromPrompt } from "nx/src/command-line/release/utils/resolve-semver-specifier"; import { isValidSemverSpecifier } from "nx/src/command-line/release/utils/semver"; import { deriveNewSemverVersion, validReleaseVersionPrefixes } from "nx/src/command-line/release/version-legacy"; import { interpolate } from "nx/src/tasks-runner/utils"; import { prerelease } from "semver"; async function releaseVersionGeneratorFn(tree, options, config) { writeInfo(`\u26A1 Running the Storm Release Version generator... `, config); const stopwatch = getStopwatch("Storm Release Version generator"); try { const workspaceRoot = findWorkspaceRoot(); writeDebug( `Loading the Storm Config from environment variables and storm.config.js file... - workspaceRoot: ${workspaceRoot}`, config ); config = await getConfig(workspaceRoot); writeTrace( `Generator schema options \u2699\uFE0F ${Object.keys(options ?? {}).filter( (key) => ![ "projects", "releaseGroup", "projectGraph", "currentVersionResolverMetadata" ].includes(key) ).map((key) => ` - ${key}=${JSON.stringify(options[key])}`).join("\n")}`, config ); const versionData = {}; if (options.specifier) { if (!isValidSemverSpecifier(options.specifier)) { throw new Error( `The given version specifier "${options.specifier}" is not valid. You provide an exact version or a valid semver keyword such as "major", "minor", "patch", etc.` ); } options.specifier = options.specifier.replace(/^v/, ""); } if (options.versionPrefix && validReleaseVersionPrefixes.indexOf( options.versionPrefix ) === -1) { throw new Error( `Invalid value for version.generatorOptions.versionPrefix: "${options.versionPrefix}" Valid values are: ${validReleaseVersionPrefixes.map((s) => `"${s}"`).join(", ")}` ); } options.fallbackCurrentVersionResolver ??= "disk"; options.currentVersionResolver ??= "git-tag"; const projects = options.projects; const createResolvePackageRoot = (customPackageRoot) => (projectNode) => { if (projectNode?.data?.root === config?.workspaceRoot || projectNode?.data?.root === ".") { return config?.workspaceRoot ?? findWorkspaceRoot(); } if (!customPackageRoot) { return projectNode.data.root; } return interpolate(customPackageRoot, { workspaceRoot: "", projectRoot: projectNode.data.root, projectName: projectNode.name }); }; const resolvePackageRoot = createResolvePackageRoot(options.packageRoot); const projectNameToPackageRootMap = /* @__PURE__ */ new Map(); for (const project of projects) { projectNameToPackageRootMap.set( project.name, resolvePackageRoot(project) ); } let currentVersion = null; let currentVersionResolvedFromFallback = false; let latestMatchingGitTag = null; let specifier = options.specifier ? options.specifier : void 0; for (const project of projects) { const projectName = project.name; const packageRoot = projectNameToPackageRootMap.get(projectName); const packageJsonPath = joinPathFragments( packageRoot ?? "./", "package.json" ); const cargoTomlPath = joinPathFragments( packageRoot ?? "./", "Cargo.toml" ); if (!tree.exists(packageJsonPath) && !tree.exists(cargoTomlPath)) { throw new Error( `The project "${projectName}" does not have a package.json available at ${packageJsonPath} or a Cargo.toml file available at ${cargoTomlPath}. To fix this you will either need to add a package.json or Cargo.toml file at that location, or configure "release" within your nx.json to exclude "${projectName}" from the current release group, or amend the packageRoot configuration to point to where the package.json should be.` ); } const workspaceRelativePackagePath = relative( config?.workspaceRoot ?? findWorkspaceRoot(), tree.exists(packageJsonPath) ? packageJsonPath : cargoTomlPath ); const log = (msg) => { writeInfo(`${projectName}: ${msg}`, config); }; writeInfo(`Running release version for project: ${project.name}`, config); let packageName; let currentVersionFromDisk; if (tree.exists(packageJsonPath)) { const projectPackageJson = readJson(tree, packageJsonPath); log( `\u{1F50D} Reading data for package "${projectPackageJson.name}" from ${workspaceRelativePackagePath}` ); packageName = projectPackageJson.name; currentVersionFromDisk = projectPackageJson.version; } else if (tree.exists(cargoTomlPath)) { const cargoToml = parseCargoToml( tree.read(cargoTomlPath)?.toString("utf-8") ); log( `\u{1F50D} Reading data for package "${cargoToml.package.name}" from ${workspaceRelativePackagePath}` ); packageName = cargoToml.package.name; currentVersionFromDisk = cargoToml.package.version; if (options.currentVersionResolver === "registry") { options.currentVersionResolver = "disk"; } } else { throw new Error( `The project "${projectName}" does not have a package.json available at ${workspaceRelativePackagePath} or a Cargo.toml file available at ${cargoTomlPath}. To fix this you will either need to add a package.json or Cargo.toml file at that location, or configure "release" within your nx.json to exclude "${projectName}" from the current release group, or amend the packageRoot configuration to point to where the package.json should be.` ); } switch (options.currentVersionResolver) { case "registry": { const metadata = options.currentVersionResolverMetadata; const npmRegistry = metadata?.registry ?? await getNpmRegistry(); const githubRegistry = metadata?.registry ?? await getGitHubRegistry(); const tag = metadata?.tag ?? "latest"; if (options.releaseGroup.projectsRelationship === "independent") { try { currentVersion = await new Promise((resolve, reject) => { exec( `npm view ${packageName} version --registry=${npmRegistry} --tag=${tag}`, (error, stdout, stderr) => { if (error) { return reject(error); } if (stderr) { return reject(stderr); } return resolve(stdout.trim()); } ); }); log( `\u{1F4C4} Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${npmRegistry}` ); } catch (_) { try { currentVersion = await new Promise( (resolve, reject) => { exec( `npm view ${packageName} version --registry=${githubRegistry} --tag=${tag}`, (error, stdout, stderr) => { if (error) { return reject(error); } if (stderr) { return reject(stderr); } return resolve(stdout.trim()); } ); } ); log( `\u{1F4C4} Resolved the current version as ${currentVersion} for tag "${tag}" from registry ${githubRegistry}` ); } catch (_2) { if (options.fallbackCurrentVersionResolver === "disk") { log( `\u{1F4C4} Unable to resolve the current version from the registry ${npmRegistry}${githubRegistry ? ` or ${githubRegistry}` : ""}. 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 ${npmRegistry}${githubRegistry ? ` or ${githubRegistry}` : ""}. 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.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.` ); } } } } else { if (currentVersionResolvedFromFallback) { log( `\u{1F4C4} Using the current version ${currentVersion} already resolved from disk fallback.` ); } else { log( `\u{1F4C4} Using the current version ${currentVersion} already resolved from the registry ${npmRegistry ?? githubRegistry}` ); } } break; } case "disk": currentVersion = currentVersionFromDisk; log( `\u{1F4C4} Resolved the current version as ${currentVersion} from ${packageJsonPath}` ); break; case "git-tag": { if ( // We always need to independently resolve the current version from git tag per project if the projects are independent options.releaseGroup.projectsRelationship === "independent" ) { const releaseTagPattern = options.releaseGroup.releaseTagPattern; latestMatchingGitTag = await getLatestGitTagForPattern( releaseTagPattern, { projectName: project.name } ); if (!latestMatchingGitTag) { if (currentVersionFromDisk) { log( `\u{1F4C4} Unable to resolve the current version from git tag using pattern "${releaseTagPattern}". Falling back to the version on disk of ${currentVersionFromDisk}` ); currentVersion = currentVersionFromDisk; } else { log( `No git tags matching pattern "${releaseTagPattern}" for project "${project.name}" were found. This process also could not determine the version by checking the package files on disk, so we will attempt to use the default version value: "0.0.1".` ); currentVersion = "0.0.1"; } currentVersionResolvedFromFallback = true; } else { currentVersion = latestMatchingGitTag.extractedVersion; log( `\u{1F4C4} Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}".` ); } } else { if (currentVersionResolvedFromFallback) { log( `\u{1F4C4} Using the current version ${currentVersion} already resolved from disk fallback.` ); } else { log( `\u{1F4C4} Using the current version ${currentVersion} already resolved from git tag "${latestMatchingGitTag.tag}".` ); } } break; } default: throw new Error( `Invalid value for options.currentVersionResolver: ${options.currentVersionResolver}` ); } if (options.specifier) { log(`\u{1F4C4} Using the provided version specifier "${options.specifier}".`); } if (specifier === void 0 || options.releaseGroup.projectsRelationship === "independent" && !options.specifier) { const specifierSource = options.specifierSource; switch (specifierSource) { case "conventional-commits": { if (options.currentVersionResolver !== "git-tag") { throw new Error( `Invalid currentVersionResolver "${options.currentVersionResolver}" provided for release group "${options.releaseGroup.name}". Must be "git-tag" when "specifierSource" is "conventional-commits"` ); } const affectedProjects = options.releaseGroup.projectsRelationship === "independent" ? [projectName] : projects.map((p) => p.name); let previousVersionRef = latestMatchingGitTag?.tag ? latestMatchingGitTag.tag : await getFirstGitCommit(); if (!previousVersionRef) { log( `Unable to determine previous version ref for the projects ${affectedProjects.join( ", " )}. This is likely a bug in Storm's Release Versioning. We will attempt to use the default version value "0.0.1" and continue with the process.` ); previousVersionRef = "0.0.1"; } specifier = await resolveSemverSpecifierFromConventionalCommits( previousVersionRef, options.projectGraph, affectedProjects, DEFAULT_CONVENTIONAL_COMMITS_CONFIG ) ?? void 0; if (!specifier) { log( "\u{1F6AB} No changes were detected using git history and the conventional commits standard." ); break; } if (currentVersion && prerelease(currentVersion)) { specifier = "prerelease"; log( `\u{1F4C4} Resolved the specifier as "${specifier}" since the current version is a prerelease.` ); } else { log( `\u{1F4C4} Resolved the specifier as "${specifier}" using git history and the conventional commits standard.` ); } break; } case "prompt": { const maybeLogReleaseGroup = (log2) => { if (options.releaseGroup.name === IMPLICIT_DEFAULT_RELEASE_GROUP) { return log2; } return `${log2} within release group "${options.releaseGroup.name}"`; }; if (options.releaseGroup.projectsRelationship === "independent") { specifier = await resolveSemverSpecifierFromPrompt( `${maybeLogReleaseGroup( `What kind of change is this for project "${projectName}"` )}?`, `${maybeLogReleaseGroup(`What is the exact version for project "${projectName}"`)}?` ); } else { specifier = await resolveSemverSpecifierFromPrompt( `${maybeLogReleaseGroup( `What kind of change is this for the ${projects.length} matched projects(s)` )}?`, `${maybeLogReleaseGroup( `What is the exact version for the ${projects.length} matched project(s)` )}?` ); } break; } default: throw new Error( `Invalid specifierSource "${specifierSource}" provided. Must be one of "prompt" or "conventional-commits"` ); } } const localPackageDependencies = resolveLocalPackageDependencies( tree, options.projectGraph, projects.filter( (project2) => project2?.data?.root && project2?.data?.root !== config?.workspaceRoot ), projectNameToPackageRootMap, resolvePackageRoot, // includeAll when the release group is independent, as we may be filtering to a specific subset of projects, but we still want to update their dependents options.releaseGroup.projectsRelationship === "independent", tree.exists(packageJsonPath) ); const dependentProjects = Object.values(localPackageDependencies).flat().filter((localPackageDependency) => { return localPackageDependency.target === project.name; }); if (!currentVersion) { throw new Error( `Unable to determine the current version for project "${projectName}"` ); } versionData[projectName] = { currentVersion: currentVersion ? currentVersion : "0.0.1", dependentProjects, newVersion: null // will stay as null in the final result in the case that no changes are detected }; if (!specifier) { log( `\u{1F6AB} Skipping versioning "${packageName}" as no changes were detected.` ); continue; } const newVersion = deriveNewSemverVersion( currentVersion, specifier, options.preid ); if (versionData[projectName]) { versionData[projectName].newVersion = newVersion; } if (tree.exists(packageJsonPath)) { const projectPackageJson = readJson(tree, packageJsonPath); writeJson(tree, packageJsonPath, { ...projectPackageJson, version: newVersion }); } else if (tree.exists(cargoTomlPath)) { const cargoToml = parseCargoToml( tree.read(cargoTomlPath)?.toString("utf-8") ); cargoToml.package ??= {}; cargoToml.package.version = newVersion; tree.write(cargoTomlPath, stringifyCargoToml(cargoToml)); } log( `\u270D\uFE0F New version ${newVersion} written to ${workspaceRelativePackagePath}` ); if (dependentProjects.length > 0) { log( `\u270D\uFE0F Applying new version ${newVersion} to ${dependentProjects.length} ${dependentProjects.length > 1 ? "packages which depend" : "package which depends"} on ${project.name}` ); } for (const dependentProject of dependentProjects) { const dependentPackageRoot = projectNameToPackageRootMap.get( dependentProject.source ); if (!dependentPackageRoot) { throw new Error( `The dependent project "${dependentProject.source}" does not have a packageRoot available. Projects with packageRoot configured: ${Array.from(projectNameToPackageRootMap.keys()).join(", ")}` ); } const dependentPackageJsonPath = joinPathFragments( dependentPackageRoot, "package.json" ); const dependentCargoTomlPath = joinPathFragments( dependentPackageRoot, "Cargo.toml" ); if (tree.exists(dependentPackageJsonPath)) { updateJson(tree, dependentPackageJsonPath, (json) => { let versionPrefix = options.versionPrefix ?? "auto"; if (versionPrefix === "auto") { versionPrefix = ""; const current = json[dependentProject.dependencyCollection][packageName]; if (current) { const prefixMatch = current.match(/^[~^]/); if (prefixMatch) { versionPrefix = prefixMatch[0]; } else { versionPrefix = ""; } } } json[dependentProject.dependencyCollection][packageName] = `${versionPrefix}${newVersion}`; return json; }); } else if (tree.exists(dependentCargoTomlPath)) { const dependentPkg = parseCargoTomlWithTree( tree, dependentPackageRoot, dependentProject.source ); let versionPrefix = options.versionPrefix ?? "auto"; let updatedDependencyData = ""; for (const dependency of Object.entries( dependentPkg[dependentProject.dependencyCollection] ?? {} )) { const [dependencyName, dependencyData] = dependency; if (dependencyName !== dependentProject.target) { continue; } if (versionPrefix === "auto") { versionPrefix = ""; if (currentVersion) { const dependencyVersion = typeof dependencyData === "string" ? dependencyData : dependencyData.version; const prefixMatch = dependencyVersion?.match(/^[~^=]/); if (prefixMatch) { versionPrefix = prefixMatch[0]; } else { versionPrefix = ""; } if (versionPrefix === "^") { if (typeof dependencyData !== "string" && !dependencyData.version?.startsWith("^")) { versionPrefix = ""; } } } } const newVersionWithPrefix = `${versionPrefix}${newVersion}`; updatedDependencyData = typeof dependencyData === "string" ? newVersionWithPrefix : { ...dependencyData, version: newVersionWithPrefix }; break; } const cargoTomlToUpdate = joinPathFragments( dependentPackageRoot, "Cargo.toml" ); modifyCargoTable( dependentPkg, dependentProject.dependencyCollection, dependentProject.target, updatedDependencyData ); tree.write(cargoTomlToUpdate, stringifyCargoToml(dependentPkg)); } } } await formatFiles(tree); writeSuccess( `Completed running the Storm Release Version generator! `, config ); return { data: versionData, callback: async (tree2, opts) => { output.logSingleLine("Updating Cargo.lock file"); const cwd = tree2.root; const updatedFiles = await updateLockFile(cwd, opts) ?? []; const updatedCargoPackages = []; for (const [projectName, projectVersionData] of Object.entries( versionData )) { const project = projects.find((proj) => proj.name === projectName); if (projectVersionData.newVersion && project?.name && projectNameToPackageRootMap.get(project.name)) { const projectRoot = projectNameToPackageRootMap.get(project.name); if (projectRoot && tree2.exists(joinPathFragments(projectRoot, "Cargo.toml"))) { updatedCargoPackages.push(projectName); } } } if (updatedCargoPackages.length > 0) { execSync(`cargo update ${updatedCargoPackages.join(" ")}`, { maxBuffer: 1024 * 1024 * 1024, env: { ...process.env }, cwd: tree2.root }); if (hasGitDiff("Cargo.lock")) { updatedFiles.push("Cargo.lock"); } } return updatedFiles; } }; } catch (error) { writeFatal( "A fatal error occurred while running the Storm Release Version generator - the process was forced to terminate", config ); writeError( `An exception was thrown in the Storm Release Version generator's process - Details: ${error.message} - Stacktrace: ${error.stack}`, config ); throw new Error( `An exception was thrown in the Storm Release Version generator's process - Details: ${error.message}`, { cause: error } ); } finally { stopwatch(); } } var generator_default = releaseVersionGeneratorFn; async function getNpmRegistry() { if (process.env.STORM_REGISTRY_NPM) { return process.env.STORM_REGISTRY_NPM; } const registry = await new Promise((resolve, reject) => { exec("npm config get registry", (error, stdout, stderr) => { if (error) { return reject(error); } if (stderr) { return reject(stderr); } return resolve(stdout.trim()); }); }); return registry ? registry : "https://registry.npmjs.org"; } function getGitHubRegistry() { if (process.env.STORM_REGISTRY_GITHUB) { return process.env.STORM_REGISTRY_GITHUB; } return "https://npm.pkg.github.com"; } function hasGitDiff(filePath) { try { const result = execSync(`git diff --name-only "${filePath}"`).toString(); return result.trim() === filePath; } catch (_) { return false; } } function resolveLocalPackageDependencies(tree, projectGraph, filteredProjects, projectNameToPackageRootMap, resolvePackageRoot, includeAll = false, isNodeProject = true) { if (isNodeProject) { return resolveLocalPackageJsonDependencies( tree, projectGraph, filteredProjects, projectNameToPackageRootMap, resolvePackageRoot, includeAll ); } return resolveLocalPackageCargoDependencies( tree, projectGraph, filteredProjects, projectNameToPackageRootMap, resolvePackageRoot, includeAll ); } function resolveLocalPackageCargoDependencies(tree, projectGraph, filteredProjects, projectNameToPackageRootMap, resolvePackageRoot, includeAll = false) { const localPackageDependencies = {}; const projects = includeAll ? Object.values(projectGraph.nodes) : filteredProjects; for (const projectNode of projects) { let packageRoot = projectNameToPackageRootMap.get(projectNode.name); if (!packageRoot && includeAll) { packageRoot = resolvePackageRoot(projectNode); if (!packageRoot) { continue; } projectNameToPackageRootMap.set(projectNode.name, packageRoot); } const cargoTomlPath = joinPathFragments(packageRoot ?? "./", "Cargo.toml"); if (!tree.exists(cargoTomlPath)) { continue; } const projectDeps = projectGraph.dependencies[projectNode.name]; if (!projectDeps) { continue; } const localPackageDepsForProject = []; for (const dep of projectDeps) { const depProject = projectGraph.nodes[dep.target]; if (!depProject) { continue; } const depProjectRoot = projectNameToPackageRootMap.get(dep.target); if (!depProjectRoot) { throw new Error( `The project "${dep.target}" does not have a packageRoot available.` ); } const cargoToml = parseCargoTomlWithTree( tree, resolvePackageRoot(projectNode), projectNode.name ); const dependencies = cargoToml.dependencies ?? {}; const devDependencies = cargoToml["dev-dependencies"] ?? {}; const dependencyCollection = dependencies[depProject.name] ? "dependencies" : devDependencies[depProject.name] ? "dev-dependencies" : null; if (!dependencyCollection) { throw new Error( `The project "${projectNode.name}" does not have a local dependency on "${depProject.name}" in its Cargo.toml` ); } localPackageDepsForProject.push({ ...dep, dependencyCollection }); } localPackageDependencies[projectNode.name] = localPackageDepsForProject; } return localPackageDependencies; } export { releaseVersionGeneratorFn, generator_default };