UNPKG

lerna

Version:

Lerna is a fast, modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository

236 lines (232 loc) 7.96 kB
import { Command, ValidationError, exec, execSync, promptConfirmation, pulseTillDone } from "./chunk-WC2B4V4E.js"; // libs/commands/import/src/index.ts import dedent from "dedent"; import fs from "fs-extra"; import { createRequire } from "node:module"; import path from "path"; var require2 = createRequire(import.meta.url); function factory(argv) { return new ImportCommand(argv); } var ImportCommand = class extends Command { externalExecOpts = { cwd: "" }; targetDirRelativeToGitRoot; commits = []; origGitEmail; origGitName; preImportHead; gitParamsForTargetCommits() { const params = ["log", "--format=%h"]; if (this.options.flatten) { params.push("--first-parent"); } return params; } initialize() { const inputPath = this.options.dir; const externalRepoPath = path.resolve(inputPath ?? ""); const externalRepoBase = path.basename(externalRepoPath); this.externalExecOpts = Object.assign({}, this.execOpts, { cwd: externalRepoPath }); let stats; try { stats = fs.statSync(externalRepoPath); } catch (e) { if (e.code === "ENOENT") { throw new ValidationError("ENOENT", `No repository found at "${inputPath}"`); } throw e; } if (!stats.isDirectory()) { throw new ValidationError("ENODIR", `Input path "${inputPath}" is not a directory`); } const packageJson = path.join(externalRepoPath, "package.json"); const packageName = require2(packageJson).name; if (!packageName) { throw new ValidationError("ENOPKG", `No package name specified in "${packageJson}"`); } const targetBase = this.getTargetBase(); if (this.getPackageDirectories().indexOf(targetBase) === -1) { throw new ValidationError( "EDESTDIR", `--dest does not match with the package directories: ${this.getPackageDirectories()}` ); } const targetDir = path.join(targetBase, externalRepoBase); const gitRepoRoot = this.getWorkspaceRoot(); const lernaRootRelativeToGitRoot = path.relative(gitRepoRoot, this.project.rootPath); this.targetDirRelativeToGitRoot = path.join(lernaRootRelativeToGitRoot, targetDir); if (this.targetDirRelativeToGitRoot.startsWith("..")) { throw new ValidationError( "ENOTINREPO", `Project root ${this.project.rootPath} is not a subdirectory of git root ${gitRepoRoot}` ); } if (fs.existsSync(path.resolve(this.project.rootPath, targetDir))) { throw new ValidationError("EEXISTS", `Target directory already exists "${targetDir}"`); } this.commits = this.externalExecSync("git", this.gitParamsForTargetCommits()).split("\n").reverse(); if (!this.commits.length) { throw new ValidationError("NOCOMMITS", `No git commits to import at "${inputPath}"`); } if (this.options.preserveCommit) { this.origGitEmail = this.execSync("git", ["config", "user.email"]); this.origGitName = this.execSync("git", ["config", "user.name"]); } this.preImportHead = this.getCurrentSHA(); if (this.execSync("git", ["diff-index", "HEAD"])) { throw new ValidationError("ECHANGES", "Local repository has un-committed changes"); } this.logger.info( "", `About to import ${this.commits.length} commits from ${inputPath} into ${targetDir}` ); if (this.options.yes) { return true; } return promptConfirmation("Are you sure you want to import these commits onto the current branch?"); } getPackageDirectories() { return this.project.packageConfigs.filter((p) => p.endsWith("*")).map((p) => path.dirname(p)); } getTargetBase() { if (this.options.dest) { return this.options.dest; } return this.getPackageDirectories().shift() || "packages"; } getCurrentSHA() { return this.execSync("git", ["rev-parse", "HEAD"]); } getWorkspaceRoot() { return this.execSync("git", ["rev-parse", "--show-toplevel"]); } execSync(cmd, args) { return execSync(cmd, args, this.execOpts); } externalExecSync(cmd, args) { return execSync(cmd, args, this.externalExecOpts); } createPatchForCommit(sha) { let patch = null; if (this.options.flatten) { const diff = this.externalExecSync("git", [ "log", "--reverse", "--first-parent", "-p", "-m", "--pretty=email", "--stat", "--binary", "-1", "--color=never", sha, // custom git prefixes for accurate parsing of filepaths (#1655) `--src-prefix=COMPARE_A/`, `--dst-prefix=COMPARE_B/` ]); const version = this.externalExecSync("git", ["--version"]).replace(/git version /g, ""); patch = `${diff} -- ${version}`; } else { patch = this.externalExecSync("git", [ "format-patch", "-1", sha, "--stdout", // custom git prefixes for accurate parsing of filepaths (#1655) `--src-prefix=COMPARE_A/`, `--dst-prefix=COMPARE_B/` ]); } const formattedTarget = this.targetDirRelativeToGitRoot?.replace(/\\/g, "/"); const replacement = `$1/${formattedTarget}`; return patch.replace(/^([-+]{3} "?COMPARE_[AB])/gm, replacement).replace(/^(diff --git "?COMPARE_A)/gm, replacement).replace(/^(diff --git (?! "?COMPARE_B\/).+ "?COMPARE_B)/gm, replacement).replace(/^(copy (from|to)) ("?)/gm, `$1 $3${formattedTarget}/`).replace(/^(rename (from|to)) ("?)/gm, `$1 $3${formattedTarget}/`); } getGitUserFromSha(sha) { return { email: this.externalExecSync("git", ["show", "-s", "--format='%ae'", sha]), name: this.externalExecSync("git", ["show", "-s", "--format='%an'", sha]) }; } configureGitUser({ email, name }) { this.execSync("git", ["config", "user.email", `"${email}"`]); this.execSync("git", ["config", "user.name", `"${name}"`]); } execute() { this.enableProgressBar(); const tracker = this.logger["newItem"]("execute"); const mapper = (sha) => { tracker.info(sha); const patch = this.createPatchForCommit(sha); const procArgs = ["am", "-3", "--keep-non-patch"]; if (this.options.preserveCommit) { this.configureGitUser(this.getGitUserFromSha(sha)); procArgs.push("--committer-date-is-author-date"); } const proc = exec("git", procArgs, this.execOpts); proc.stdin.end(patch); return pulseTillDone(proc).then(() => { tracker.completeWork(1); }).catch((err) => { const diff = this.externalExecSync("git", ["diff", "-s", `${sha}^!`]).trim(); if (diff === "") { tracker.completeWork(1); return exec("git", ["am", "--skip"], this.execOpts); } err.sha = sha; throw err; }); }; tracker.addWork(this.commits.length); return (async () => { for (const commit of this.commits) { await mapper(commit); } })().then(() => { tracker.finish(); if (this.options.preserveCommit) { this.configureGitUser({ email: this.origGitEmail, name: this.origGitName }); } this.logger.success("import", "finished"); }).catch((err) => { tracker.finish(); if (this.options.preserveCommit) { this.configureGitUser({ email: this.origGitEmail, name: this.origGitName }); } this.logger.error("import", `Rolling back to previous HEAD (commit ${this.preImportHead})`); this.execSync("git", ["am", "--abort"]); if (this.preImportHead) { this.execSync("git", ["reset", "--hard", this.preImportHead]); } throw new ValidationError( "EIMPORT", dedent` Failed to apply commit ${err.sha}. ${err.message} You may try again with --flatten to import flat history. ` ); }); } }; export { factory, ImportCommand };