UNPKG

radashi-helper

Version:
364 lines (356 loc) 10.5 kB
import { dedent } from "./chunk-V32CUL4H.js"; import { addOverride, assertRepoClean } from "./chunk-QRYWERVT.js"; import "./chunk-GDVGEOPF.js"; import { loadRewired } from "./chunk-OCHPTCP5.js"; import { pullRadashi } from "./chunk-FEUFDYD5.js"; import { botCommit } from "./chunk-CQEIEK2X.js"; import "./chunk-GGEXN2BI.js"; import { findSources } from "./chunk-652CQLOF.js"; import "./chunk-BKS73LZK.js"; import { prompt } from "./chunk-MJC327DM.js"; import { debug, getEnv, isPreReleaseRadashiTag } from "./chunk-DSXY7YVB.js"; import { EarlyExitError, RadashiError, exec, forwardStderrAndRethrow, log } from "./chunk-JLKIE3A2.js"; import { __commonJS, __toESM } from "./chunk-VOEQFXI5.js"; // ../../node_modules/.pnpm/glob-regex@0.3.2/node_modules/glob-regex/index.js var require_glob_regex = __commonJS({ "../../node_modules/.pnpm/glob-regex@0.3.2/node_modules/glob-regex/index.js"(exports, module) { "use strict"; var dotRE = /\./g; var dotPattern = "\\."; var restRE = /\*\*$/g; var restPattern = "(.+)"; var globRE = /(?:\*\*\/|\*\*|\*)/g; var globPatterns = { "*": "([^/]+)", // no backslashes "**": "(.+/)?([^/]+)", // short for "**/*" "**/": "(.+/)?" // one or more directories }; function mapToPattern(str) { return globPatterns[str]; } function replace(glob) { return glob.replace(dotRE, dotPattern).replace(restRE, restPattern).replace(globRE, mapToPattern); } function join3(globs) { return "((" + globs.map(replace).join(")|(") + "))"; } function globRegex2(glob) { return new RegExp("^" + (Array.isArray(glob) ? join3 : replace)(glob) + "$"); } globRegex2.replace = replace; Object.defineProperty(globRegex2, "default", { value: globRegex2 }); module.exports = globRegex2; } }); // src/pr-import.ts var import_glob_regex = __toESM(require_glob_regex(), 1); import { yellow } from "kleur/colors"; import { existsSync } from "node:fs"; import { copyFile, mkdir } from "node:fs/promises"; import { basename, dirname, extname, join as join2, relative } from "node:path"; // src/rewired/undoRewire.ts import fs, { writeFile } from "node:fs/promises"; import { join } from "node:path"; async function undoRewire(funcPath, env) { debug(`Removing rewired file for "${funcPath}"`); const rewiredFile = join(env.overrideDir, "rewired", funcPath + ".ts"); await fs.rm(rewiredFile); const rewired = await loadRewired(env); const newRewired = rewired.filter((path) => path !== funcPath); await writeFile(rewiredFile, JSON.stringify(newRewired, null, 2)); } // src/util/checkCommand.ts import { exec as exec2 } from "node:child_process"; async function checkCommand(cmd) { return new Promise((resolve) => { exec2(`command -v ${cmd}`, (error) => { resolve(!error); }); }); } // src/pr-import.ts async function importPullRequest(prNumber, options = {}) { if (Number.isNaN(+prNumber)) { throw new RadashiError(`Invalid PR number "${prNumber}"`); } if (!await checkCommand("gh")) { throw new RadashiError( dedent` gh command is not installed. You can install it using Homebrew: brew install gh Or using the official website: https://cli.github.com/ ` ); } const env = options.env ?? getEnv(options.dir); if (!env.radashiDir) { throw new RadashiError("No upstream repository exists"); } await assertRepoClean(env.root); await pullRadashi(env); await exec("git", ["branch", "-D", "pr-" + prNumber], { cwd: env.radashiDir, reject: false }); log("Checking out PR..."); await exec("gh", ["pr", "checkout", prNumber, "-b", "pr-" + prNumber], { cwd: env.radashiDir }).catch((error) => { log.error(error.stderr); let reason = ""; if (error.message.includes("Could not resolve")) { reason = `Could not find any PR with number ${prNumber}.`; } else if (error.message.includes("couldn't find remote ref")) { reason = "The author appears to have deleted the PR branch."; } throw new RadashiError( "Failed to checkout PR" + (reason ? `: ${reason}` : "") ); }); const targetBranch = await getTargetBranch(env); debug("Target branch of the PR:", targetBranch); await exec( "git", ["fetch"].concat( targetBranch.includes("/") ? targetBranch.split("/") : ["origin", targetBranch] ), { cwd: env.radashiDir } ).catch(forwardStderrAndRethrow); const baseCommit = await exec("git", ["merge-base", "HEAD", targetBranch], { cwd: env.radashiDir }).then((r) => r.stdout); debug("Base commit of the PR:", baseCommit); const changes = await parseGitDiff(baseCommit, { cwd: env.radashiDir }); debug("Parsed changes from git diff:", changes); const pathsIn = await findSources(env); const srcGlob = (0, import_glob_regex.default)("src/*/*.ts"); changes.sort((a, b) => { return srcGlob.test(a.file) && !srcGlob.test(b.file) ? -1 : srcGlob.test(b.file) && !srcGlob.test(a.file) ? 1 : a.file.localeCompare(b.file); }); const addedFiles = []; const modifiedFiles = []; for (const change of changes) { const file = basename(change.file, extname(change.file)); if (options.files && !options.files.includes(file)) { continue; } if (change.status === "A") { addedFiles.push(change.file); if (change.file.startsWith("src/")) { const srcPath = join2(env.root, change.file); if (pathsIn.src.includes(srcPath)) { throw new RadashiError( `Cannot import PR. File named "${change.file}" is already a source file created by you.` ); } } } else if (change.status === "M" && change.file !== "src/mod.ts") { modifiedFiles.push(change.file); if (change.file.startsWith("src/")) { const overridePath = join2( env.root, change.file.replace("src/", "overrides/src/") ); if (pathsIn.overrides.includes(overridePath)) { throw new RadashiError( `Cannot import PR. File named "${change.file}" already exists in the overrides folder.` ); } const funcPath = relative(env.overrideDir, overridePath).slice(0, -3); const rewiredPath = overridePath.replace("/src/", "/rewired/"); if (pathsIn.rewired.includes(rewiredPath)) { await undoRewire(funcPath, env); } await addOverride(funcPath, { env, exactMatch: true, fromBranch: baseCommit }); } } } for (const file of addedFiles) { debug(`Adding "${file}" to project`); await tryCopyFile(join2(env.radashiDir, file), join2(env.root, file)); } for (const file of modifiedFiles) { debug(`Modifying "${file}" override in project`); await tryCopyFile( join2(env.radashiDir, file), join2(env.root, "overrides", file) ); } let prTitle = await exec( "gh", ["pr", "view", "--json", "title", "--jq", ".title"], { cwd: env.radashiDir } ).then((result) => result.stdout.trim()); const validTitleRE = /^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([^):]+\))?: /; if (!validTitleRE.test(prTitle)) { log.error(""); log.error( yellow("ATTN"), "The PR title does not follow the Conventional Commits format." ); log.error("Please select the type of change this PR introduces:\n"); const type = await prompt({ type: "autocomplete", name: "type", message: "Select the type of change:", choices: getConventionalCommitTypes() }); if (!type) { throw new EarlyExitError("No change type selected. Exiting..."); } const description = await prompt({ type: "text", name: "description", message: "Enter a short description of the change:" }); if (!description) { throw new EarlyExitError("No description provided. Exiting..."); } prTitle = `${type}: ${description}`; } const { default: build } = await import("./build-7F3PTR5C.js"); await build({ env }); log(""); await botCommit(prTitle, { cwd: env.root, add: ["-A"] }); } async function tryCopyFile(src, dst) { if (existsSync(src)) { try { await mkdir(dirname(dst), { recursive: true }); await copyFile(src, dst); return true; } catch { } } return false; } async function parseGitDiff(ref, opts) { const { stdout: nameStatus } = await exec( "git", ["diff", ref, "--name-status"], opts ); return nameStatus.trim().split("\n").map((line) => { const [status, file] = line.split(" "); return { status, file }; }); } async function getTargetBranch(env) { const { stdout } = await exec( "gh", ["pr", "view", "--json", "baseRefName", "--jq", ".baseRefName"], { cwd: env.radashiDir } ); const targetBranch = stdout?.trim() ?? "main"; const radashiRef = await env.radashiRef; if (isPreReleaseRadashiTag(radashiRef)) { return "radashi/" + targetBranch; } return targetBranch; } function getConventionalCommitTypes() { return [ { title: "feat", description: "A new feature", value: "feat" }, { title: "fix", description: "A bug fix", value: "fix" }, { title: "docs", description: "Documentation only changes", value: "docs" }, { title: "style", description: "Changes that do not affect the meaning of the code", value: "style" }, { title: "refactor", description: "A code change that neither fixes a bug nor adds a feature", value: "refactor" }, { title: "perf", description: "A code change that improves performance", value: "perf" }, { title: "test", description: "Adding missing tests or correcting existing tests", value: "test" }, { title: "build", description: "Changes that affect the build system or external dependencies", value: "build" }, { title: "ci", description: "Changes to our CI configuration files and scripts", value: "ci" }, { title: "chore", description: "Other changes that don't modify src or test files", value: "chore" }, { title: "revert", description: "Reverts a previous commit", value: "revert" } ]; } export { importPullRequest };