UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

250 lines (248 loc) • 9.23 kB
const require_run = require('./utils/run.cjs'); let node_fs = require("node:fs"); let node_path = require("node:path"); let effect = require("effect"); let yaml = require("yaml"); let node_fs_promises = require("node:fs/promises"); //#region src/workspace.ts var DepsBuildFailure = class extends effect.Data.TaggedError("DepsBuildFailure") { get message() { return `workspace dependency builds failed:\n${this.failures.map((failure) => `${failure.member.name} (exit ${failure.exitCode})\n${outputTail(failure.output)}`).join("\n\n")}`; } }; const workspaceRootCache = /* @__PURE__ */ new Map(); function findWorkspaceRoot(startDir) { const key = (0, node_path.resolve)(startDir); const cached = workspaceRootCache.get(key); if (cached !== void 0) return cached; const found = discoverWorkspaceRoot(key); workspaceRootCache.set(key, found); return found; } function discoverWorkspaceRoot(dir) { let current = dir; while (true) { const root = readWorkspaceRootAt(current); if (root) return root; const parent = (0, node_path.dirname)(current); if (parent === current) return null; current = parent; } } function readWorkspaceRootAt(dir) { const yamlPath = (0, node_path.join)(dir, "pnpm-workspace.yaml"); if ((0, node_fs.existsSync)(yamlPath)) { const globs = workspaceGlobsFromYaml((0, yaml.parse)((0, node_fs.readFileSync)(yamlPath, "utf8"))?.packages); if (globs) return readWorkspaceMembers(dir, globs); } const manifestPath = (0, node_path.join)(dir, "package.json"); if (!(0, node_fs.existsSync)(manifestPath)) return null; const manifest = readManifest(manifestPath); if (!manifest) return null; const globs = workspaceGlobs(manifest.workspaces); if (!globs) return null; return readWorkspaceMembers(dir, globs); } function workspaceGlobsFromYaml(packages) { if (!Array.isArray(packages)) return null; const globs = packages.filter((glob) => typeof glob === "string"); return globs.length > 0 ? globs : null; } function readWorkspaceMembers(dir, globs) { const memberDirs = matchMemberDirs(dir, globs); const drafts = []; for (const memberDir of memberDirs) { const memberManifest = readManifest((0, node_path.join)(memberDir, "package.json")); if (!memberManifest || typeof memberManifest.name !== "string") continue; drafts.push({ name: memberManifest.name, dir: memberDir, manifest: memberManifest }); } const names = new Set(drafts.map((draft) => draft.name)); return { dir, members: drafts.map((draft) => ({ name: draft.name, dir: draft.dir, localDeps: declaredDepNames(draft.manifest).filter((dep) => names.has(dep)) })).sort((a, b) => a.name.localeCompare(b.name)) }; } function workspaceGlobs(workspaces) { if (Array.isArray(workspaces)) return workspaces.filter((glob) => typeof glob === "string"); if (workspaces && typeof workspaces === "object") { const packages = workspaces.packages; if (Array.isArray(packages)) return packages.filter((glob) => typeof glob === "string"); } return null; } function matchMemberDirs(rootDir, globs) { const found = /* @__PURE__ */ new Set(); for (const glob of globs) collectMemberDirs(rootDir, glob.split("/"), found); return [...found].sort(); } function collectMemberDirs(dir, segments, out) { const [head, ...rest] = segments; if (!head) return; let entries; try { entries = (0, node_fs.readdirSync)(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (!entry.isDirectory()) continue; if (head !== "*" && head !== entry.name) continue; const child = (0, node_path.join)(dir, entry.name); if (rest.length === 0) out.add(child); else collectMemberDirs(child, rest, out); } } function readManifest(path) { try { return JSON.parse((0, node_fs.readFileSync)(path, "utf8")); } catch { return null; } } function declaredDepNames(manifest) { const names = []; for (const field of ["dependencies", "devDependencies"]) { const deps = manifest[field]; if (deps && typeof deps === "object") names.push(...Object.keys(deps)); } return names; } function localDepsOf(root, targetDirs) { const byName = new Map(root.members.map((member) => [member.name, member])); const byDir = new Map(root.members.map((member) => [member.dir, member])); const ordered = []; const seen = /* @__PURE__ */ new Set(); const visit = (member) => { if (seen.has(member.name)) return; seen.add(member.name); for (const dep of member.localDeps) { const depMember = byName.get(dep); if (depMember) visit(depMember); } ordered.push(member); }; for (const targetDir of targetDirs) { const target = byDir.get((0, node_path.resolve)(targetDir)); if (!target) continue; for (const dep of target.localDeps) { const depMember = byName.get(dep); if (depMember) visit(depMember); } } return ordered; } async function ensureFreshDeps(startDir, targetDirs, opts) { const root = findWorkspaceRoot(startDir); if (!root) return { rebuilt: [], fresh: [] }; const closure = localDepsOf(root, targetDirs); const force = opts?.force ?? false; const judgements = await Promise.all(closure.map(async (member) => force || await isStaleMember(member))); const rebuilt = closure.filter((_, index) => judgements[index]); const fresh = closure.filter((_, index) => !judgements[index]); const failures = []; for (const layer of buildLayers(rebuilt)) { const results = await Promise.allSettled(layer.map((member) => require_run.run("pnpm", ["run", "build"], { cwd: member.dir, capture: true, env: opts?.env }))); failures.push(...results.flatMap((result, index) => { const value = result.status === "fulfilled" ? result.value : void 0; if (value && value.exitCode === 0) return []; const output = value ? `${value.stdout}\n${value.stderr}` : result.status === "rejected" ? String(result.reason) : ""; return [{ member: layer[index], exitCode: value ? value.exitCode ?? 1 : 1, output }]; })); if (failures.length > 0) break; } if (failures.length > 0) throw new DepsBuildFailure({ failures }); return { rebuilt, fresh }; } function buildLayers(members) { const byName = new Map(members.map((member) => [member.name, member])); const remaining = new Set(members.map((member) => member.name)); const layers = []; while (remaining.size > 0) { const layer = members.filter((member) => remaining.has(member.name) && member.localDeps.every((dep) => !byName.has(dep) || !remaining.has(dep))); if (layer.length === 0) throw new Error(`dependency cycle in workspace closure: ${[...remaining].join(", ")}`); for (const member of layer) remaining.delete(member.name); layers.push(layer); } return layers; } async function isStaleMember(member) { const oracle = distOracle(member.dir); if (!oracle) return false; return isWorkspaceDistStale(member.dir, oracle); } function distOracle(memberDir) { const manifest = readManifest((0, node_path.join)(memberDir, "package.json")); if (!manifest) return null; const entry = manifest.exports && typeof manifest.exports === "object" ? manifest.exports["."] : void 0; if (entry && typeof entry === "object") for (const key of [ "import", "default", "require", "types" ]) { const value = entry[key]; if (typeof value === "string" && !value.startsWith("./src")) return value; } if (typeof manifest.module === "string" && !manifest.module.startsWith("./src")) return manifest.module; if (typeof manifest.main === "string" && !manifest.main.startsWith("./src")) return manifest.main; const reportPath = (0, node_path.join)(memberDir, "dist", "build-report.json"); if ((0, node_fs.existsSync)(reportPath)) { const report = readManifest(reportPath); if (report && typeof report.entry === "string" && report.entry) return (0, node_path.join)("dist", report.entry); } return null; } async function newestSourceMtimeMs(dir) { let newest = 0; const entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name === "node_modules" || entry.name === "dist") continue; const fullPath = (0, node_path.join)(dir, entry.name); if (entry.isDirectory()) newest = Math.max(newest, await newestSourceMtimeMs(fullPath)); else newest = Math.max(newest, (await (0, node_fs_promises.stat)(fullPath)).mtimeMs); } return newest; } async function isWorkspaceDistStale(packageDir, distEntry) { const distPath = (0, node_path.join)(packageDir, distEntry); if (!(0, node_fs.existsSync)(distPath)) return true; const [distMtime, srcMtime, pkgMtime] = await Promise.all([ (0, node_fs_promises.stat)(distPath).then((s) => s.mtimeMs), newestSourceMtimeMs((0, node_path.join)(packageDir, "src")), (0, node_fs_promises.stat)((0, node_path.join)(packageDir, "package.json")).then((s) => s.mtimeMs) ]); return Math.max(srcMtime, pkgMtime) > distMtime; } function outputTail(output) { return output.trim().split("\n").slice(-10).join("\n"); } //#endregion exports.DepsBuildFailure = DepsBuildFailure; exports.buildLayers = buildLayers; exports.ensureFreshDeps = ensureFreshDeps; exports.findWorkspaceRoot = findWorkspaceRoot; exports.isWorkspaceDistStale = isWorkspaceDistStale; exports.localDepsOf = localDepsOf; //# sourceMappingURL=workspace.cjs.map