Archived
feat: bundle workspace tasks plugin
This commit is contained in:
@@ -40,7 +40,7 @@ async function buildDirectory(sourceDir, targetDir) {
|
||||
}
|
||||
|
||||
if (!entry.isFile()) continue;
|
||||
if (entry.name.endsWith(".d.ts")) continue;
|
||||
if (entry.name.endsWith(".d.ts") || isTestSource(entry.name)) continue;
|
||||
|
||||
if (isPluginSource(entry.name)) {
|
||||
await buildFile(sourcePath, targetPath.replace(/\.ts$/u, ".js"));
|
||||
@@ -94,6 +94,10 @@ function isPluginSource(fileName) {
|
||||
return fileName.endsWith(".ts") && !fileName.endsWith(".d.ts");
|
||||
}
|
||||
|
||||
function isTestSource(fileName) {
|
||||
return /\.(?:test|spec)\.ts$/u.test(fileName);
|
||||
}
|
||||
|
||||
async function hasTypeScriptSource(javaScriptPath) {
|
||||
const typeScriptPath = javaScriptPath.replace(/\.js$/u, ".ts");
|
||||
try {
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import { relative, resolve } from "node:path";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const pluginsRoot = resolve(cwd, "plugins");
|
||||
const devPackages = await findPluginPackagesWithDevScripts(pluginsRoot);
|
||||
const children = new Set();
|
||||
let stopping = false;
|
||||
|
||||
process.on("SIGINT", () => { stopAndExit(130); });
|
||||
process.on("SIGTERM", () => { stopAndExit(143); });
|
||||
|
||||
if (devPackages.length === 0) {
|
||||
console.log("[plugin-packages] no plugin package dev scripts found");
|
||||
await stayAlive();
|
||||
}
|
||||
|
||||
for (const packageInfo of devPackages) startPackageDev(packageInfo);
|
||||
console.log(`[plugin-packages] watching ${String(devPackages.length)} plugin package${devPackages.length === 1 ? "" : "s"}`);
|
||||
|
||||
await stayAlive();
|
||||
|
||||
async function findPluginPackagesWithDevScripts(root) {
|
||||
if (!existsSync(root)) return [];
|
||||
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
||||
const packages = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const dir = resolve(root, entry.name);
|
||||
const packageInfo = await readPluginPackageInfo(dir);
|
||||
if (packageInfo !== undefined) packages.push(packageInfo);
|
||||
}
|
||||
return packages.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}
|
||||
|
||||
async function readPluginPackageInfo(dir) {
|
||||
const packagePath = resolve(dir, "package.json");
|
||||
const content = await readFile(packagePath, "utf8").catch(() => undefined);
|
||||
if (content === undefined) return undefined;
|
||||
const parsed = JSON.parse(content);
|
||||
if (!isRecord(parsed)) return undefined;
|
||||
const scripts = parsed["scripts"];
|
||||
if (!isRecord(scripts) || typeof scripts["dev"] !== "string") return undefined;
|
||||
const rawName = parsed["name"];
|
||||
return { dir, name: typeof rawName === "string" && rawName !== "" ? rawName : relative(cwd, dir) };
|
||||
}
|
||||
|
||||
function startPackageDev(packageInfo) {
|
||||
const child = spawn("npm", ["run", "dev"], {
|
||||
cwd: packageInfo.dir,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
children.add(child);
|
||||
pipeWithPrefix(child.stdout, process.stdout, `[${packageInfo.name}]`);
|
||||
pipeWithPrefix(child.stderr, process.stderr, `[${packageInfo.name}]`);
|
||||
child.on("error", (error) => {
|
||||
children.delete(child);
|
||||
if (stopping) return;
|
||||
console.error(`[plugin-packages] failed to start ${packageInfo.name} dev: ${error instanceof Error ? error.message : String(error)}`);
|
||||
stopAndExit(1);
|
||||
});
|
||||
child.on("exit", (code, signal) => {
|
||||
children.delete(child);
|
||||
if (stopping) return;
|
||||
const reason = signal === null ? `code ${String(code ?? 0)}` : `signal ${signal}`;
|
||||
console.error(`[plugin-packages] ${packageInfo.name} dev exited with ${reason}`);
|
||||
stopAndExit(code === null || code === 0 ? 1 : code);
|
||||
});
|
||||
}
|
||||
|
||||
function pipeWithPrefix(stream, output, prefix) {
|
||||
let pending = "";
|
||||
stream.setEncoding("utf8");
|
||||
stream.on("data", (chunk) => {
|
||||
pending += chunk;
|
||||
const lines = pending.split(/\r?\n/u);
|
||||
pending = lines.pop() ?? "";
|
||||
for (const line of lines) output.write(`${prefix} ${line}\n`);
|
||||
});
|
||||
stream.on("end", () => {
|
||||
if (pending !== "") output.write(`${prefix} ${pending}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
function stopAndExit(code) {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
for (const child of children) child.kill("SIGTERM");
|
||||
setTimeout(() => { process.exit(code); }, 100);
|
||||
}
|
||||
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
async function stayAlive() {
|
||||
await new Promise(() => undefined);
|
||||
}
|
||||
Reference in New Issue
Block a user