everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
150 lines (148 loc) • 5.76 kB
JavaScript
import { fileExists, listFiles, readText, removePath, writeText } from "../fs.mjs";
import { OBSOLETE_FILES } from "../obsolete-files.mjs";
import { Effect } from "effect";
//#region src/services/scaffold/upgrade-audit/steps/apply-manifest.ts
const applyRewrites = (projectDir, rewrites, dryRun) => Effect.gen(function* () {
const touched = [];
for (const rewrite of rewrites) for (const file of rewrite.files) {
const source = yield* readText(projectDir, file);
const next = source.replaceAll(rewrite.from, rewrite.to);
if (next !== source) {
touched.push(file);
if (!dryRun) yield* writeText(projectDir, file, next);
}
}
return touched;
});
const applyDeletions = (projectDir, dryRun) => Effect.gen(function* () {
const removed = [];
for (const path of OBSOLETE_FILES) if (yield* fileExists(projectDir, path)) {
removed.push(path);
if (!dryRun) yield* removePath(projectDir, path);
}
return removed;
});
const bareSpecifiers = (source) => {
const specifiers = [];
for (const match of source.matchAll(/(?:import|export)[^"'`]*?from\s*["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)|require\s*\(\s*["']([^"']+)["']\s*\)/g)) {
const spec = match[1] ?? match[2] ?? match[3];
if (spec && !spec.startsWith(".") && !spec.startsWith("/")) specifiers.push(spec);
}
return specifiers;
};
const packageNameOf = (specifier) => {
if (specifier.startsWith("@")) {
const [, scope, name] = specifier.split("/");
return scope && name ? `${scope}/${name}` : specifier;
}
return specifier.split("/")[0];
};
const isProtocolSpecifier = (specifier) => /^[^/]+:/.test(specifier);
const memberPackageNames = (projectDir) => Effect.gen(function* () {
const pkgPaths = yield* listFiles(projectDir, "{package.json,ui/package.json,api/package.json,plugins/*/package.json}");
const names = /* @__PURE__ */ new Set();
for (const pkgPath of pkgPaths) {
if (pkgPath === "package.json") continue;
const pkg = JSON.parse(yield* readText(projectDir, pkgPath));
if (typeof pkg.name === "string") names.add(pkg.name);
}
return names;
});
const declarePluginDeps = (projectDir, dryRun) => Effect.gen(function* () {
const catalog = JSON.parse(yield* readText(projectDir, "package.json")).workspaces?.catalog ?? {};
const members = yield* memberPackageNames(projectDir);
const pluginPkgs = yield* listFiles(projectDir, "plugins/*/package.json");
const added = [];
const unresolved = [];
for (const pkgPath of pluginPkgs) {
const pluginDir = pkgPath.slice(0, pkgPath.lastIndexOf("/"));
const declared = /* @__PURE__ */ new Set();
const pkg = JSON.parse(yield* readText(projectDir, pkgPath));
for (const name of [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]) declared.add(name);
const sources = yield* listFiles(projectDir, `${pluginDir}/src/**/*.{ts,tsx}`);
const needed = /* @__PURE__ */ new Set();
for (const source of sources) {
const text = yield* readText(projectDir, source);
for (const specifier of bareSpecifiers(text)) {
if (isProtocolSpecifier(specifier)) continue;
needed.add(packageNameOf(specifier));
}
}
const nextDeps = { ...pkg.dependencies ?? {} };
let modified = false;
for (const name of needed) {
if (declared.has(name) || name === pluginDir.split("/")[1]) continue;
if (members.has(name)) {
added.push(`${pluginDir}: ${name} (workspace:*)`);
nextDeps[name] = "workspace:*";
modified = true;
continue;
}
if (!(name in catalog)) {
unresolved.push(`${pluginDir}: ${name} (not in catalog — declare manually)`);
continue;
}
added.push(`${pluginDir}: ${name}`);
nextDeps[name] = "catalog:";
modified = true;
}
if (modified && !dryRun) yield* writeText(projectDir, pkgPath, `${JSON.stringify({
...pkg,
dependencies: nextDeps
}, null, 2)}\n`);
}
return {
added,
unresolved
};
});
const consumerizeRefs = (projectDir, dryRun) => Effect.gen(function* () {
const pkgPaths = yield* listFiles(projectDir, "{package.json,ui/package.json,api/package.json,plugins/*/package.json}");
const memberNames = /* @__PURE__ */ new Set();
for (const pkgPath of pkgPaths) {
if (pkgPath === "package.json") continue;
const pkg = JSON.parse(yield* readText(projectDir, pkgPath));
if (typeof pkg.name === "string") memberNames.add(pkg.name);
}
const fixed = [];
for (const pkgPath of pkgPaths) {
const pkg = JSON.parse(yield* readText(projectDir, pkgPath));
let modified = false;
for (const field of [
"dependencies",
"devDependencies",
"peerDependencies"
]) {
const deps = pkg[field];
if (deps === null || typeof deps !== "object") continue;
const record = deps;
for (const [name, ref] of Object.entries(record)) if (typeof ref === "string" && (ref.startsWith("workspace:") || ref.startsWith("file:")) && !memberNames.has(name)) {
record[name] = "catalog:";
modified = true;
}
}
if (modified) {
fixed.push(pkgPath);
if (!dryRun) yield* writeText(projectDir, pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
}
}
return fixed;
});
const applyManifest = (projectDir, rewrites, dryRun) => Effect.gen(function* () {
const rewritten = yield* applyRewrites(projectDir, rewrites, dryRun);
const deleted = yield* applyDeletions(projectDir, dryRun);
const deps = yield* declarePluginDeps(projectDir, dryRun);
const refs = yield* consumerizeRefs(projectDir, dryRun);
return {
writes: rewritten,
deletes: deleted,
notes: [
...refs.length > 0 ? [`workspace:/file: refs → catalog: in ${refs.join(", ")}`] : [],
...deps.added.length > 0 ? [`plugin deps added: ${deps.added.join(", ")}`] : [],
...deps.unresolved
]
};
});
//#endregion
export { applyManifest, consumerizeRefs };
//# sourceMappingURL=apply-manifest.mjs.map