muddle/scripts/forgejo-push.ts
2026-10-23 09:10:23 -04:00

111 lines
2.7 KiB
TypeScript

#!/usr/bin/env bun
import { chmod, rm, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
const root = resolve(import.meta.dir, "..");
const modules = [
"amezing",
"analysis",
"astro",
"database",
"latex",
"mdx",
"optimize",
"prettier-plugin",
"shadcn",
"tailwind",
];
const api = (process.env.MUDDLE_FORGEJO_URL ?? "https://git.crit.rip").replace(
/\/$/,
"",
);
const owner = process.env.MUDDLE_FORGEJO_OWNER ?? "mdl";
const arguments_ = process.argv.slice(2);
const push = arguments_.includes("--push");
const requested = arguments_.filter((argument) => !argument.startsWith("--"));
const selected = requested.length > 0 ? requested : modules;
const token = process.env.MUDDLE_FORGEJO_TOKEN;
if (push && !token) {
throw new Error(
"Set MUDDLE_FORGEJO_TOKEN before using --push.",
);
}
for (const name of selected) {
if (!modules.includes(name)) {
throw new Error(`Unknown integration module ${name}`);
}
}
async function run(
command: string[],
environment: Record<string, string | undefined> = {},
): Promise<string> {
const child = Bun.spawn(command, {
cwd: root,
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, ...environment },
});
const [stdout, stderr, status] = await Promise.all([
new Response(child.stdout).text(),
new Response(child.stderr).text(),
child.exited,
]);
if (status !== 0) {
throw new Error(`${command.join(" ")} failed\n${stderr || stdout}`);
}
return stdout.trim();
}
async function changed(path: string): Promise<boolean> {
const tracked = await run(["git", "status", "--porcelain", "--", path]);
return tracked.length > 0;
}
let askPass: string | undefined;
try {
if (push && token) {
askPass = join(import.meta.dir, `.forgejo-askpass-${process.pid}`);
await writeFile(
askPass,
`#!/bin/sh\ncase "$1" in *Username*) printf '%s' token ;; *) printf '%s' "$MUDDLE_FORGEJO_TOKEN" ;; esac\n`,
);
await chmod(askPass, 0o700);
}
for (const name of selected) {
const prefix = `modules/${name}`;
const hasChanges = await changed(prefix);
const message = `chore: update @muddle/${name} integration`;
if (!push) {
console.log(
`would ${hasChanges ? `commit with "${message}" and ` : ""}sync @muddle/${name} to ${api}/${owner}/${name}.git`,
);
continue;
}
if (hasChanges) {
await run(["git", "add", "--", prefix]);
await run(["git", "commit", "-m", message, "--", prefix]);
}
const branch = await run([
"git",
"subtree",
"split",
"--prefix",
prefix,
]);
await run([
"git",
"push",
`${api}/${owner}/${name}.git`,
`${branch}:refs/heads/main`,
], {
GIT_ASKPASS: askPass,
GIT_TERMINAL_PROMPT: "0",
});
console.log(`pushed @muddle/${name}`);
}
} finally {
if (askPass) await rm(askPass, { force: true });
}