UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

75 lines (74 loc) 3.13 kB
import { mkdtemp, rm as rmrf } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { gitWithAuth, resolveToken, vcsApi } from "./common.js"; function normalize(p) { return p.trim().replace(/^\.\//, "").replace(/\/+$/, ""); } export function expandPathsToDeletions(repoFiles, requested) { const out = new Set(); for (const raw of requested) { const path = normalize(raw); if (path.startsWith("/") || path.split("/").includes("..")) { throw new Error(`"${raw}" is not a repo-relative path — vcs paths never start with "/" or traverse with "..".`); } const exact = repoFiles.filter((f) => f === path); if (exact.length > 0) { for (const f of exact) out.add(f); continue; } const under = repoFiles.filter((f) => f.startsWith(`${path}/`)); if (under.length === 0) { throw new Error(`"${raw}" matched no files in the repo — nothing was proposed. ` + `Check the path with: mesh vcs get <repo> ${path}`); } for (const f of under) out.add(f); } return [...out].sort(); } export async function rmCommand(repo, paths, opts) { const baseUrl = (opts.url ?? process.env.VCS_URL)?.replace(/\/$/, ""); if (!baseUrl) throw new Error("rm requires --url or VCS_URL"); const token = await resolveToken(opts); const dir = await mkdtemp(join(tmpdir(), "mesh-vcs-rm-")); try { const git = (args, cwd) => gitWithAuth(args, token, cwd); await git([ "clone", "--filter=blob:none", "--no-checkout", "-c", `http.extraHeader=Authorization: Bearer ${token}`, `${baseUrl}/git/${repo}`, dir, ]); const { stdout: treeOut } = await git(["ls-tree", "-r", "--name-only", "HEAD"], dir); const repoFiles = treeOut.split("\n").filter(Boolean); const doomed = expandPathsToDeletions(repoFiles, paths); if (opts.dryRun) { console.log(`would delete ${doomed.length} file(s) from ${repo}:`); for (const p of doomed) console.log(` ${p}`); console.log("(--dry-run: nothing proposed)"); return; } const { stdout: head } = await git(["rev-parse", "HEAD"], dir); const body = { baseCommit: head.trim(), message: opts.message ?? `Delete ${doomed.length} file(s)`, operations: doomed.map((path) => ({ op: "delete", path })), }; const data = (await vcsApi(baseUrl, token, `/v1/repos/${repo}/proposals`, "POST", body)); console.log(`deleting ${doomed.length} file(s) from ${repo}:`); for (const p of doomed) console.log(` ${p}`); console.log(`proposal ${data.proposalId} @ ${data.sha.slice(0, 8)} — a human must approve: ` + `mesh vcs approve ${repo} ${data.proposalId} --url ${baseUrl}`); } finally { await rmrf(dir, { recursive: true, force: true }); } }