everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
327 lines (325 loc) • 11.3 kB
JavaScript
import { resolveLocalDevelopmentPath } from "./config.mjs";
import { applyDeployResults, parseDeployLines } from "./integrity.mjs";
import { formatDuration } from "./cli/timing.mjs";
import { syncResolvedSharedDeps } from "./shared-deps.mjs";
import { run } from "./utils/run.mjs";
import { padRight } from "./utils/string.mjs";
import { colors, icons } from "./utils/theme.mjs";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import process from "node:process";
import { access, readFile } from "node:fs/promises";
//#region src/build.ts
const buildCommands = {
host: {
cmd: "bun",
args: ["run", "build"]
},
ui: {
cmd: "bun",
args: ["run", "build"]
},
api: {
cmd: "bun",
args: ["run", "build"]
}
};
function getPluginRef(entry) {
if (!entry || typeof entry === "string") return null;
return entry;
}
async function fileExists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
async function readJsonFile(path) {
return JSON.parse(await readFile(path, "utf8"));
}
function resolveWorkspaceTarget(key, bosConfig, runtimeConfig, configDir) {
if (bosConfig?.app && key in bosConfig.app) {
const appEntry = bosConfig.app[key];
const devPath = resolveLocalDevelopmentPath(appEntry?.development, configDir);
if (devPath) return {
key,
kind: "app",
path: devPath
};
return {
key,
kind: "app",
path: `${configDir}/${key}`
};
}
const pluginPath = (runtimeConfig?.plugins?.[key])?.localPath ?? resolveLocalDevelopmentPath(getPluginRef(bosConfig?.plugins?.[key])?.development, configDir);
if (pluginPath) return {
key,
kind: "plugin",
path: pluginPath
};
return null;
}
function selectWorkspaceTargets(packages, bosConfig) {
const allPackages = [...Object.keys(bosConfig?.app ?? {}), ...Object.keys(bosConfig?.plugins ?? {})];
if (packages === "all") return allPackages;
return packages.split(",").map((pkg) => pkg.trim()).filter((pkg) => allPackages.includes(pkg));
}
async function runBuildAttempt(cmd, args, cwd, env, verbose, wsKey) {
let zephyrAuthShown = false;
const result = await run(cmd, args, {
cwd,
env,
capture: true,
onChunk: (stream, chunk) => {
if (verbose) if (stream === "stdout") process.stdout.write(chunk);
else process.stderr.write(chunk);
if (zephyrAuthShown) return;
const text = chunk.toString("utf-8");
if (/Authentication required.*log in to Zephyr/i.test(text) || /auth\.zephyr-cloud\.io\/authorize/.test(text)) {
zephyrAuthShown = true;
const urlMatch = text.match(/https:\/\/auth\.zephyr-cloud\.io\/authorize\S+/);
console.log();
console.log(colors.yellow(` ⚠ Zephyr authentication required for ${wsKey}`));
if (urlMatch) console.log(` ${colors.cyan(urlMatch[0])}`);
console.log(colors.dim(" Waiting for authentication..."));
console.log();
}
}
});
const stdout = result?.stdout ?? "";
const stderr = result?.stderr ?? "";
const exitCode = result?.exitCode ?? 0;
const output = `${stdout}\n${stderr}`;
const deployEntries = parseDeployLines(output);
if (deployEntries.length > 0) {
if (exitCode !== 0) {
const errorLines = output.split("\n").filter((line) => /\bERROR\b/.test(line) || line.startsWith("Rspack compiled with")).slice(0, 5);
if (errorLines.length > 0 && !verbose) {
console.log(` ${colors.yellow("⚠")} Build completed with errors (exit code ${exitCode}) — Zephyr deployed successfully`);
for (const line of errorLines) console.log(` ${colors.dim(line.trim())}`);
}
}
return {
success: true,
url: deployEntries[0]?.url,
exitCode: 0,
output,
deployEntries
};
}
if (exitCode !== 0) return {
success: false,
error: `Build failed (exit code ${exitCode})\n${output.trim().split("\n").slice(-5).join("\n")}`,
exitCode,
output
};
const zeMatch = output.match(/ZE\d+/);
if (zeMatch) return {
success: false,
error: `Zephyr upload failed (${zeMatch[0]})`,
exitCode: 0,
output
};
const deployMatch = output.match(/🚀.*Deployed:\s*(https?:\S+)/);
if (deployMatch) return {
success: true,
url: deployMatch[1],
exitCode: 0,
output
};
if (env.DEPLOY === "true") return {
success: false,
error: "No deploy URL found (Zephyr may have failed)",
exitCode: 0,
output
};
return {
success: true,
exitCode: 0,
output
};
}
async function buildOneWorkspace(ws, env, opts) {
const pkgJson = await readJsonFile(`${ws.path}/package.json`);
const buildConfig = opts.deploy && pkgJson.scripts?.deploy ? {
cmd: "bun",
args: ["run", "deploy"]
} : buildCommands[ws.key] ?? {
cmd: "bun",
args: ["run", "build"]
};
const wsEnv = { ...env };
const startTime = Date.now();
let attempt = await runBuildAttempt(buildConfig.cmd, buildConfig.args, ws.path, wsEnv, opts.verbose ?? false, ws.key);
let retried = false;
if (!attempt.success && attempt.exitCode === 0 && opts.deploy) {
if (!opts.verbose) console.log(` ${colors.yellow("↻")} ${padRight(ws.key, 28)} retrying...`);
retried = true;
attempt = await runBuildAttempt(buildConfig.cmd, buildConfig.args, ws.path, wsEnv, opts.verbose ?? false, ws.key);
}
const durationMs = Date.now() - startTime;
const result = {
key: ws.key,
kind: ws.kind,
success: attempt.success,
url: attempt.url,
error: attempt.error,
deployEntries: attempt.deployEntries,
durationMs,
retried: retried ? true : void 0
};
if (!opts.verbose) {
const name = padRight(ws.key, 28);
if (result.success) {
const duration = formatDuration(durationMs);
const retryTag = retried ? " (retried)" : "";
console.log(` ${colors.green(icons.ok)} ${name} ${colors.dim(duration + retryTag)}`);
} else {
const errorLine = (result.error ?? "Failed").split("\n")[0];
console.log(` ${colors.error(icons.err)} ${name} ${errorLine}`);
}
}
return result;
}
async function buildWorkspaceTargets(opts) {
const existing = [];
const skipped = [];
for (const target of opts.targets) {
const resolved = resolveWorkspaceTarget(target, opts.bosConfig, opts.runtimeConfig, opts.configDir);
if (!resolved) {
skipped.push(target);
continue;
}
if (await fileExists(`${resolved.path}/package.json`)) existing.push(resolved);
else skipped.push(target);
}
if (existing.length === 0) return {
built: [],
skipped
};
if ((await syncResolvedSharedDeps({
configDir: opts.configDir,
hostMode: "local",
bosConfig: opts.bosConfig ?? void 0,
extendsChain: []
})).catalogChanged) await run("bun", ["install"], { cwd: opts.configDir });
const shouldBuildPlugin = existing.some((entry) => entry.key === "api");
const forceRebuild = opts.deploy;
const buildTasks = [buildEverythingDevQuietly(opts.configDir, forceRebuild)];
if (shouldBuildPlugin) buildTasks.push(buildEveryPluginQuietly(opts.configDir, forceRebuild));
await Promise.all(buildTasks);
const env = {
...process.env,
NODE_ENV: opts.deploy ? "production" : "development"
};
if (opts.deploy) env.DEPLOY = "true";
else delete env.DEPLOY;
const bosConfigPath = join(opts.configDir, "bos.config.json");
let configSnapshot;
if (opts.deploy && existsSync(bosConfigPath)) configSnapshot = readFileSync(bosConfigPath, "utf-8");
const orderedExisting = opts.deploy ? [
...existing.filter((entry) => entry.kind === "app" && entry.key !== "host"),
...existing.filter((entry) => entry.kind === "plugin"),
...existing.filter((entry) => entry.kind === "app" && entry.key === "host")
] : existing;
const parallelGroup = opts.deploy ? orderedExisting.filter((e) => e.key !== "host") : orderedExisting;
const sequentialGroup = opts.deploy ? orderedExisting.filter((e) => e.key === "host") : [];
const built = [];
const deployResults = [];
if (opts.deploy && parallelGroup.length > 0) {
const total = parallelGroup.length + sequentialGroup.length;
console.log();
console.log(` Building ${total} workspace${total > 1 ? "s" : ""}...`);
console.log();
const results = await Promise.allSettled(parallelGroup.map((ws) => buildOneWorkspace(ws, env, opts)));
const allDeployEntries = [];
for (let i = 0; i < parallelGroup.length; i++) {
const ws = parallelGroup[i];
const result = results[i];
if (result.status === "fulfilled") {
if (result.value.success) built.push(ws.key);
if (result.value.deployEntries) allDeployEntries.push(...result.value.deployEntries);
const { deployEntries: _deployEntries, ...deployResult } = result.value;
deployResults.push(deployResult);
} else deployResults.push({
key: ws.key,
kind: ws.kind,
success: false,
error: result.reason?.message ?? "Unknown error"
});
}
if (configSnapshot && allDeployEntries.length > 0) {
const merged = applyDeployResults(JSON.parse(configSnapshot), allDeployEntries);
writeFileSync(bosConfigPath, `${JSON.stringify(merged, null, 2)}\n`);
}
for (const ws of sequentialGroup) {
const result = await buildOneWorkspace(ws, env, opts);
if (result.success) built.push(ws.key);
if (result.deployEntries) {
const hostEntries = result.deployEntries.filter((r) => r.urlField.startsWith("app.host"));
if (hostEntries.length > 0 && existsSync(bosConfigPath)) {
const merged = applyDeployResults(JSON.parse(readFileSync(bosConfigPath, "utf-8")), hostEntries);
writeFileSync(bosConfigPath, `${JSON.stringify(merged, null, 2)}\n`);
}
}
const { deployEntries: _deployEntries, ...sequentialResult } = result;
deployResults.push(sequentialResult);
}
console.log();
} else for (const resolved of orderedExisting) {
const buildConfig = buildCommands[resolved.key] ?? {
cmd: "bun",
args: ["run", "build"]
};
await run(buildConfig.cmd, buildConfig.args, {
cwd: resolved.path,
env
});
built.push(resolved.key);
}
return {
built,
skipped,
deployResults: opts.deploy ? deployResults : void 0
};
}
async function buildEveryPluginQuietly(cwd, force = false) {
if (!await fileExists(`${`${cwd}/packages/every-plugin`}/package.json`)) return;
if (await fileExists(`${cwd}/packages/every-plugin/dist/build/rspack/plugin.mjs`) && !force) return;
const result = await run("bun", [
"run",
"--cwd",
"packages/every-plugin",
"build"
], {
cwd,
capture: true
});
if (result.exitCode === 0) return;
if (result.stdout.trim()) process.stdout.write(result.stdout);
if (result.stderr.trim()) process.stderr.write(result.stderr);
throw new Error(`bun run --cwd packages/every-plugin build failed with exit code ${result.exitCode}`);
}
async function buildEverythingDevQuietly(cwd, force = false) {
if (!await fileExists(`${`${cwd}/packages/everything-dev`}/package.json`)) return;
if (await fileExists(`${cwd}/packages/everything-dev/dist/index.mjs`) && !force) return;
const result = await run("bun", [
"run",
"--cwd",
"packages/everything-dev",
"build"
], {
cwd,
capture: true
});
if (result.exitCode === 0) return;
if (result.stdout.trim()) process.stdout.write(result.stdout);
if (result.stderr.trim()) process.stderr.write(result.stderr);
throw new Error(`bun run --cwd packages/everything-dev build failed with exit code ${result.exitCode}`);
}
//#endregion
export { buildEveryPluginQuietly, buildEverythingDevQuietly, buildWorkspaceTargets, fileExists, getPluginRef, readJsonFile, selectWorkspaceTargets };
//# sourceMappingURL=build.mjs.map