123 lines
5.8 KiB
TypeScript
Executable file
123 lines
5.8 KiB
TypeScript
Executable file
#!/usr/bin/env bun
|
|
import { existsSync } from "node:fs";
|
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { generate, collectTables } from "./contracts/typegen/generator.ts";
|
|
import { format } from "./core/format.ts";
|
|
import { loadConfig } from "./core/config.ts";
|
|
import { setupWrangler } from "./adapters/cloudflare-wrangler.ts";
|
|
import { setupVercel } from "./adapters/vercel-vercel.ts";
|
|
import { backportProject } from "./core/backport.ts";
|
|
import { createTemplate, muddleTemplates, type MuddleTemplate } from "./core/templates.ts";
|
|
|
|
function progress(event: { current: number; total: number; route: string }): void {
|
|
const width = 20;
|
|
const filled = Math.round((event.current / Math.max(1, event.total)) * width);
|
|
console.log(`[${"#".repeat(filled)}${".".repeat(width - filled)}] ${event.current}/${event.total} ${event.route}`);
|
|
}
|
|
|
|
async function dev(entry: string, root: string, port: number): Promise<void> {
|
|
const moduleUrl = pathToFileURL(entry).href;
|
|
const source = `const project = await import(${JSON.stringify(moduleUrl)}); if (!project.app || typeof project.app.fetch !== "function") throw new Error("Project entry must export an app with fetch(request)"); const server = Bun.serve({ port: ${port}, fetch: (request) => project.app.fetch(request) }); console.log(\`muddle dev listening on \${server.url}\`); await new Promise(() => {});`;
|
|
const child = Bun.spawn([process.execPath, "--watch", "--eval", source], {
|
|
cwd: root,
|
|
stdin: "inherit",
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
});
|
|
const stop = () => child.kill();
|
|
process.once("SIGINT", stop);
|
|
process.once("SIGTERM", stop);
|
|
await child.exited;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
const command = args[0] ?? "help";
|
|
const portFlag = args.indexOf("--port");
|
|
const requestedPort = portFlag === -1 ? undefined : Number(args[portFlag + 1]);
|
|
const root = args.slice(1).find((argument, index, values) => {
|
|
if (argument.startsWith("--")) return false;
|
|
return values[index - 1] !== "--port";
|
|
}) ?? process.cwd();
|
|
if (requestedPort !== undefined && (!Number.isInteger(requestedPort) || requestedPort < 1 || requestedPort > 65_535)) {
|
|
throw new Error("--port must be an integer from 1 through 65535");
|
|
}
|
|
if (command === "typegen") {
|
|
const result = await generate({ root });
|
|
for (const file of result.written) console.log(`muddle generated ${relative(process.cwd(), file)}`);
|
|
return;
|
|
}
|
|
if (command === "backport") {
|
|
const source = args[1] ? resolve(args[1]) : process.cwd();
|
|
const destination = args[2] ? resolve(args[2]) : resolve(source, "muddle");
|
|
const result = await backportProject(source, destination);
|
|
console.log(`muddle backported ${result.source} project to ${result.output} (${result.files.length} files)`);
|
|
return;
|
|
}
|
|
if (command === "templates") {
|
|
const name = args[1] as MuddleTemplate | undefined;
|
|
if (!name) {
|
|
console.log(`Available templates: ${muddleTemplates.join(", ")}`);
|
|
console.log("Usage: muddle templates <docs|minimal> [destination]");
|
|
return;
|
|
}
|
|
if (!muddleTemplates.includes(name)) throw new Error(`Unknown template ${name}. Choose one of: ${muddleTemplates.join(", ")}`);
|
|
const result = await createTemplate(name, args[2] ? resolve(args[2]) : resolve(process.cwd(), name));
|
|
console.log(`muddle created ${result.name} template at ${result.output} (${result.files.length} files)`);
|
|
return;
|
|
}
|
|
if (command === "repo") {
|
|
const target = resolve(root);
|
|
const tables = await collectTables(target, ["**/*.table.ts"]);
|
|
const out = join(target, "repositories");
|
|
await mkdir(out, { recursive: true });
|
|
for (const table of tables) {
|
|
const rowType = table.fields
|
|
.map((field) => {
|
|
const tsType = field.type;
|
|
const optional = field.nullable ? "?" : "";
|
|
return `\t${field.property}${optional}: ${tsType};`;
|
|
})
|
|
.join("\n");
|
|
const typeName = `${table.name[0]?.toUpperCase() ?? ""}${table.name.slice(1)}Row`;
|
|
const source = `import { Repository } from "@muddle/database";\n\nexport type ${typeName} = {\n${rowType}\n};\n\nexport class ${table.name[0]?.toUpperCase() ?? ""}${table.name.slice(1)}Repository extends Repository<${typeName}> {}\n`;
|
|
const file = join(out, `${table.name}.repository.ts`);
|
|
await writeFile(file, source);
|
|
console.log(`muddle generated ${relative(target, file)}`);
|
|
}
|
|
return;
|
|
}
|
|
if (command === "fmt") {
|
|
const check = process.argv.includes("--check");
|
|
const changed = await format(resolve(root === "--check" ? process.cwd() : root), { check });
|
|
for (const file of changed) console.log(`${check ? "Unformatted" : "Formatted"} ${file}`);
|
|
if (check && changed.length) process.exitCode = 1;
|
|
return;
|
|
}
|
|
if (command === "build" || command === "dev") {
|
|
const target = resolve(root);
|
|
const config = await loadConfig(target);
|
|
const entry = resolve(target, config.entry ?? "index.ts");
|
|
if (!existsSync(entry)) throw new Error(`No index.ts found in ${root}`);
|
|
if (config.adapter?.name === "cloudflare-wrangler") await setupWrangler(target);
|
|
if (config.adapter?.name === "vercel-vercel") await setupVercel(target);
|
|
const project = await import(entry) as { build?: (options?: { progress?: typeof progress }) => Promise<void>; app?: unknown };
|
|
if (command === "build") {
|
|
if (!project.build) throw new Error("Project must export build() for muddle build");
|
|
await project.build({ progress });
|
|
return;
|
|
} else if (command === "dev") {
|
|
if (!project.app || typeof (project.app as { fetch?: unknown }).fetch !== "function") {
|
|
throw new Error("Project entry must export an app with fetch(request) for muddle dev");
|
|
}
|
|
await dev(entry, target, requestedPort ?? config.adapter?.port ?? 3000);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
console.log("muddle <typegen|repo|fmt|build|dev|backport|templates> [project-root] [--port number]");
|
|
}
|
|
|
|
await main();
|