81 lines
3.4 KiB
TypeScript
81 lines
3.4 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { build } from "esbuild";
|
|
import JSZip from "jszip";
|
|
import { spawn } from "node:child_process";
|
|
import { projectRoot, workspaceRoot } from "./storage";
|
|
import { validateArtifact } from "./validation";
|
|
|
|
const activeOperations = new Set<string>();
|
|
export function isBuildActive(id: string) { return activeOperations.has(id); }
|
|
|
|
export async function buildProject(id: string) {
|
|
if (activeOperations.has(id)) throw new Error("Project is already busy");
|
|
activeOperations.add(id);
|
|
const workspace = workspaceRoot(id);
|
|
const dist = path.join(projectRoot(id), "dist");
|
|
try {
|
|
await fs.rm(dist, { recursive: true, force: true });
|
|
await fs.mkdir(path.join(dist, "assets"), { recursive: true });
|
|
await build({
|
|
absWorkingDir: workspace,
|
|
entryPoints: ["src/App.jsx"],
|
|
bundle: true,
|
|
outfile: path.join(dist, "assets", "main.js"),
|
|
format: "iife",
|
|
target: ["es2017", "chrome61"],
|
|
minify: true,
|
|
sourcemap: false,
|
|
legalComments: "inline",
|
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
nodePaths: [path.join(process.cwd(), "node_modules"), path.join(workspace, "node_modules")],
|
|
logLevel: "silent",
|
|
});
|
|
await fs.copyFile(path.join(workspace, "src", "index.html"), path.join(dist, "index.html"));
|
|
await fs.copyFile(path.join(workspace, "src", "style.css"), path.join(dist, "assets", "style.css"));
|
|
return { dist, report: await validateArtifact(dist) };
|
|
} finally {
|
|
activeOperations.delete(id);
|
|
}
|
|
}
|
|
|
|
export async function installProjectDependencies(id: string) {
|
|
if (activeOperations.has(id)) throw new Error("Project is already busy");
|
|
activeOperations.add(id);
|
|
const workspace = workspaceRoot(id);
|
|
try {
|
|
await fs.access(path.join(workspace, "package.json"));
|
|
const output = await new Promise<string>((resolve, reject) => {
|
|
const child = spawn("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], { cwd: workspace, env: { ...process.env, npm_config_update_notifier: "false" } });
|
|
let text = "";
|
|
child.stdout.on("data", (chunk) => { text += chunk; });
|
|
child.stderr.on("data", (chunk) => { text += chunk; });
|
|
child.on("error", reject);
|
|
child.on("close", (code) => code === 0 ? resolve(text.trim()) : reject(new Error(`npm install failed (${code}): ${text.trim()}`)));
|
|
});
|
|
return { output };
|
|
} finally {
|
|
activeOperations.delete(id);
|
|
}
|
|
}
|
|
|
|
async function addDirectoryToZip(zip: JSZip, root: string, dir = root) {
|
|
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) await addDirectoryToZip(zip, root, full);
|
|
else zip.file(path.relative(root, full).split(path.sep).join("/"), await fs.readFile(full));
|
|
}
|
|
}
|
|
|
|
export async function packageProject(id: string) {
|
|
const { dist, report } = await buildProject(id);
|
|
if (report.status === "failed") return { report };
|
|
const artifacts = path.join(projectRoot(id), "artifacts");
|
|
await fs.mkdir(artifacts, { recursive: true });
|
|
const zipPath = path.join(artifacts, "minitool.zip");
|
|
const zip = new JSZip();
|
|
await addDirectoryToZip(zip, dist);
|
|
await fs.writeFile(zipPath, await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE", compressionOptions: { level: 9 } }));
|
|
return { zipPath, report: await validateArtifact(dist, zipPath) };
|
|
}
|