Files
paseo/scripts/server-clean-build.test.mjs

198 lines
6.5 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/plugin",
"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")));
});
test("public runtime packages construct complete tarballs from source", async (t) => {
const isolatedRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-runtime-packages-"));
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",
]) {
await assert.rejects(access(path.join(isolatedRoot, relativePath)), relativePath);
}
const packDirectory = path.join(isolatedRoot, "packs");
await mkdir(packDirectory);
const packages = [
{
workspace: "@getpaseo/workspace-runtime-contract",
files: ["dist/index.js", "dist/index.d.ts"],
},
{
workspace: "@getpaseo/workspace-helper",
files: ["dist/index.js", "dist/index.d.ts", "dist/executable.mjs"],
executable: "dist/executable.mjs",
},
];
for (const expected of packages) {
const packed = JSON.parse(
(
await run(
"npm",
[
"pack",
"--json",
`--workspace=${expected.workspace}`,
"--pack-destination",
packDirectory,
],
{ cwd: isolatedRoot, timeout: 120_000 },
)
).stdout,
)[0];
const files = new Map(packed.files.map((file) => [file.path, file]));
for (const expectedFile of expected.files) assert.ok(files.has(expectedFile), expectedFile);
if (expected.executable) assert.equal(files.get(expected.executable)?.mode, 0o755);
await access(path.join(packDirectory, packed.filename));
}
});
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");
const workspaces = await isolatedWorkspaces(isolatedRoot);
const internalScopes = new Set(workspaces.map(({ name }) => name.split("/")[0]));
await mkdir(targetModules);
await overlayModules(sourceModules, targetModules, internalScopes);
for (const { relativeDirectory, name } of workspaces) {
const [scope, packageName] = name.split("/");
const internalScope = path.join(targetModules, scope);
await mkdir(internalScope, { recursive: true });
await symlink(
path.join(isolatedRoot, relativeDirectory),
path.join(internalScope, packageName),
"dir",
);
}
}
async function createPackageNodeModulesOverlays(isolatedRoot) {
const internalScopes = new Set(
(await isolatedWorkspaces(isolatedRoot)).map(({ name }) => name.split("/")[0]),
);
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, internalScopes);
}
}
async function isolatedWorkspaces(isolatedRoot) {
return Promise.all(
packageDirectories.map(async (relativeDirectory) => ({
relativeDirectory,
name: JSON.parse(
await readFile(path.join(isolatedRoot, relativeDirectory, "package.json"), "utf8"),
).name,
})),
);
}
async function overlayModules(sourceModules, targetModules, internalScopes) {
for (const entry of await readdir(sourceModules, { withFileTypes: true })) {
if (internalScopes.has(entry.name)) continue;
await symlink(
path.join(sourceModules, entry.name),
path.join(targetModules, entry.name),
entry.isDirectory() ? "dir" : "file",
);
}
}