UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

84 lines (83 loc) 3.45 kB
import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { execFileAsync, resolveTarget, resolveToken, vcsApi, } from "./common.js"; export function parseStatus(out) { const tokens = out.split("\0"); const changes = []; let i = 0; while (i < tokens.length) { const entry = tokens[i]; if (!entry) { i++; continue; } const status = entry.slice(0, 2); const newPath = entry.slice(3); const isRename = status[0] === "R" || status[1] === "R"; const isCopy = !isRename && (status[0] === "C" || status[1] === "C"); if (isRename || isCopy) { const origPath = tokens[i + 1] ?? ""; i += 2; if (isRename && origPath && !origPath.endsWith("/")) { changes.push({ status: "D", path: origPath }); } if (newPath && !newPath.endsWith("/")) { changes.push({ status: "A", path: newPath }); } continue; } i++; if (newPath.length > 0 && !newPath.endsWith("/")) { changes.push({ status: status.trim(), path: newPath }); } } return changes; } export async function writeOp(cwd, path) { const buf = await readFile(join(cwd, path)); const asUtf8 = buf.toString("utf8"); if (Buffer.from(asUtf8, "utf8").equals(buf)) { return { op: "write", path, content: asUtf8 }; } return { op: "write", path, content: buf.toString("base64"), encoding: "base64" }; } export async function proposeCommand(opts) { const cwd = process.cwd(); const target = await resolveTarget(opts); if (!target.repo) { throw new Error("could not determine repo from the origin remote (pass --url from a clone)"); } const token = await resolveToken(opts); const { stdout: statusOut } = await execFileAsync("git", ["status", "--porcelain", "-z", "--untracked-files=all"], { cwd }); const changes = parseStatus(statusOut); if (changes.length === 0) { console.log("working tree clean — nothing to propose"); return; } const operations = await Promise.all(changes.map(async (c) => c.status.startsWith("D") ? { op: "delete", path: c.path } : await writeOp(cwd, c.path))); let baseCommit; if (opts.revise) { const state = (await vcsApi(target.baseUrl, token, `/v1/repos/${target.repo}/proposals/${opts.revise}`)); if (!state.headSha) throw new Error(`cannot resolve head of proposal ${opts.revise}`); baseCommit = state.headSha; } else { const { stdout: head } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd }); baseCommit = head.trim(); } const body = { ...(opts.proposalId && !opts.revise ? { proposalId: opts.proposalId } : {}), baseCommit, message: opts.message ?? `Update ${operations.length} file(s)`, operations, ...(opts.mergeParent ? { mergeParent: opts.mergeParent } : {}), }; const path = opts.revise ? `/v1/repos/${target.repo}/proposals/${opts.revise}/revisions` : `/v1/repos/${target.repo}/proposals`; const data = (await vcsApi(target.baseUrl, token, path, "POST", body)); console.log(`proposal ${data.proposalId} @ ${data.sha.slice(0, 8)} — review: mesh vcs show ${target.repo} ${data.proposalId} --url ${target.baseUrl}`); }