Files
paseo/scripts/server-clean-build.test.mjs
T
Mohamed Boudra 7c32fcda98 refactor(workspaces): finalize runtime package boundaries
Keep the daemon dependent only on the public runtime contract and helper, while optional runtime implementations remain external command registrations. Workspace setup continues asynchronously through the bound runtime without coupling production packages to an implementation.
2026-08-14 12:48:35 +02:00

131 lines
4.3 KiB
JavaScript

import { execFile } from "node:child_process";
import { access, cp, mkdir, mkdtemp, readFile, readdir, rm, symlink } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import assert from "node:assert/strict";
import { promisify } from "node:util";
const run = promisify(execFile);
const repoRoot = path.resolve(import.meta.dirname, "..");
const packageDirectories = [
"packages/highlight",
"packages/relay",
"packages/protocol",
"packages/client",
"packages/workspace-runtime-contract",
"packages/workspace-helper",
"packages/server",
"packages/cli",
];
test("build:server:clean constructs public prerequisites without runtime implementations", async (t) => {
const isolatedRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-server-clean-build-"));
t.after(() => rm(isolatedRoot, { recursive: true, force: true }));
await copyBuildCheckout(isolatedRoot);
await createNodeModulesOverlay(isolatedRoot);
await createPackageNodeModulesOverlays(isolatedRoot);
for (const relativePath of [
"packages/workspace-runtime-contract/dist",
"packages/workspace-helper/dist",
"packages/server/dist",
]) {
await assert.rejects(access(path.join(isolatedRoot, relativePath)), relativePath);
}
await assert.rejects(access(path.join(isolatedRoot, "runtimes")));
try {
await run("npm", ["run", "build:server:clean"], {
cwd: isolatedRoot,
timeout: 120_000,
});
} catch (error) {
assert.fail(`${error.stdout ?? ""}\n${error.stderr ?? ""}`);
}
for (const relativePath of [
"packages/workspace-runtime-contract/dist/index.js",
"packages/workspace-helper/dist/index.js",
"packages/server/dist/server/server/exports.js",
"packages/cli/dist/index.js",
]) {
await access(path.join(isolatedRoot, relativePath));
}
await assert.rejects(access(path.join(isolatedRoot, "runtimes")));
});
async function copyBuildCheckout(isolatedRoot) {
await Promise.all([
...["package.json", "package-lock.json", "tsconfig.base.json", "tsconfig.json"].map((file) =>
cp(path.join(repoRoot, file), path.join(isolatedRoot, file)),
),
cp(path.join(repoRoot, "scripts"), path.join(isolatedRoot, "scripts"), {
recursive: true,
filter: excludesBuildOutput,
}),
...packageDirectories.map(async (relativeDirectory) => {
const target = path.join(isolatedRoot, relativeDirectory);
await mkdir(path.dirname(target), { recursive: true });
await cp(path.join(repoRoot, relativeDirectory), target, {
recursive: true,
filter: excludesBuildOutput,
});
}),
]);
}
function excludesBuildOutput(source) {
return (
!source.endsWith(`${path.sep}dist`) &&
!source.endsWith(`${path.sep}node_modules`) &&
!source.endsWith(`${path.sep}test-results`)
);
}
async function createNodeModulesOverlay(isolatedRoot) {
const sourceModules = path.join(repoRoot, "node_modules");
const targetModules = path.join(isolatedRoot, "node_modules");
await mkdir(targetModules);
await overlayModules(sourceModules, targetModules);
const internalScope = path.join(targetModules, "@getpaseo");
await mkdir(internalScope);
for (const relativeDirectory of packageDirectories) {
const manifest = JSON.parse(
await readFile(path.join(isolatedRoot, relativeDirectory, "package.json"), "utf8"),
);
await symlink(
path.join(isolatedRoot, relativeDirectory),
path.join(internalScope, manifest.name.slice("@getpaseo/".length)),
"dir",
);
}
}
async function createPackageNodeModulesOverlays(isolatedRoot) {
for (const relativeDirectory of packageDirectories) {
const sourceModules = path.join(repoRoot, relativeDirectory, "node_modules");
try {
await access(sourceModules);
} catch {
continue;
}
const targetModules = path.join(isolatedRoot, relativeDirectory, "node_modules");
await mkdir(targetModules);
await overlayModules(sourceModules, targetModules);
}
}
async function overlayModules(sourceModules, targetModules) {
for (const entry of await readdir(sourceModules, { withFileTypes: true })) {
if (entry.name === "@getpaseo") continue;
await symlink(
path.join(sourceModules, entry.name),
path.join(targetModules, entry.name),
entry.isDirectory() ? "dir" : "file",
);
}
}