@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
84 lines (83 loc) • 3.45 kB
JavaScript
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}`);
}