Files
paseo/packages/desktop/scripts/dev-build-contract.test.mjs

161 lines
5.6 KiB
JavaScript

import { execFile } from "node:child_process";
import {
access,
cp,
mkdir,
mkdtemp,
readFile,
rm,
stat,
symlink,
utimes,
writeFile,
} from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { describe, expect, test } from "vitest";
const desktopDir = path.resolve(import.meta.dirname, "..");
const repoRoot = path.resolve(desktopDir, "../..");
const execFileAsync = promisify(execFile);
const npmCommand = process.platform === "win32" ? process.execPath : "npm";
const npmPrefixArgs =
process.platform === "win32" ? [requireEnvironmentVariable("npm_execpath")] : [];
const prerequisite = "npm run dev:prerequisites";
function requireEnvironmentVariable(name) {
const value = process.env[name];
if (!value) throw new Error(`${name} is required to invoke npm on Windows`);
return value;
}
describe("desktop dev build contract", () => {
test("runs one prerequisite before either platform dev entry point", async () => {
const packageJson = JSON.parse(await readFile(path.join(desktopDir, "package.json"), "utf8"));
const devScript = await readFile(path.join(import.meta.dirname, "dev.sh"), "utf8");
expect(packageJson.scripts.predev).toBe(prerequisite);
expect(packageJson.scripts["predev:win"]).toBe(prerequisite);
expect(packageJson.scripts["dev:prerequisites"]).toBe(
"npm --prefix ../.. run build:server-deps && npm --prefix ../.. run build --workspace=@getpaseo/server",
);
expect(packageJson.scripts.dev).toBe("./scripts/dev.sh");
expect(packageJson.scripts["dev:win"]).toBe("powershell ./scripts/dev.ps1");
expect(devScript.indexOf("npm run build:main")).toBeLessThan(
devScript.indexOf('exec node "$SCRIPT_DIR/dev-runner.mjs"'),
);
});
test("builds the private runtime fixture before renderer acceptance", async () => {
const packageJson = JSON.parse(await readFile(path.join(desktopDir, "package.json"), "utf8"));
expect(packageJson.scripts["pretest:e2e:renderer"]).toBe(
"npm --prefix ../.. run build:workspace-runtime-fixture",
);
});
test("the public prerequisite repairs missing and stale server artifacts in an isolated checkout", async () => {
const isolatedRoot = await createIsolatedBuildCheckout();
const staleServerExport = path.join(
isolatedRoot,
"packages/server/dist/server/server/exports.js",
);
const staleSourceExport = path.join(isolatedRoot, "packages/server/src/server/exports.ts");
const missingServerExport = path.join(
isolatedRoot,
"packages/server/dist/server/server/session.js",
);
const missingSourceExport = path.join(isolatedRoot, "packages/server/src/server/session.ts");
await mkdir(path.dirname(staleServerExport), { recursive: true });
await writeFile(staleServerExport, "// stale isolated artifact\n");
const staleTime = new Date(0);
await utimes(staleServerExport, staleTime, staleTime);
try {
await runPrerequisite(isolatedRoot);
await expect(access(missingServerExport)).resolves.toBeUndefined();
expect(await readFile(staleServerExport, "utf8")).not.toContain("stale isolated artifact");
expect((await stat(staleServerExport)).mtimeMs).toBeGreaterThanOrEqual(
(await stat(staleSourceExport)).mtimeMs,
);
expect((await stat(missingServerExport)).mtimeMs).toBeGreaterThanOrEqual(
(await stat(missingSourceExport)).mtimeMs,
);
} finally {
await rm(isolatedRoot, {
recursive: true,
force: true,
maxRetries: 5,
retryDelay: 100,
});
}
}, 130_000);
});
async function createIsolatedBuildCheckout() {
const isolatedRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-desktop-build-contract-"));
const packageDirectories = [
"packages/client",
"packages/desktop",
"packages/highlight",
"packages/plugin",
"packages/protocol",
"packages/relay",
"packages/server",
"packages/workspace-runtime-contract",
"packages/workspace-helper",
];
try {
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,
}),
...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: (source) =>
!source.endsWith(`${path.sep}dist`) &&
!source.endsWith(`${path.sep}node_modules`) &&
!source.endsWith(`${path.sep}test-results`),
});
}),
]);
await symlink(path.join(repoRoot, "node_modules"), path.join(isolatedRoot, "node_modules"));
await Promise.all(
packageDirectories.map((relativeDirectory) =>
symlink(
path.join(repoRoot, relativeDirectory, "node_modules"),
path.join(isolatedRoot, relativeDirectory, "node_modules"),
),
),
);
return isolatedRoot;
} catch (error) {
await rm(isolatedRoot, { recursive: true, force: true });
throw error;
}
}
async function runPrerequisite(isolatedRoot) {
try {
await execFileAsync(
npmCommand,
[...npmPrefixArgs, "run", "dev:prerequisites", "--workspace=@getpaseo/desktop"],
{
cwd: isolatedRoot,
timeout: 120_000,
},
);
} catch (error) {
throw new Error(`${error.stdout ?? ""}\n${error.stderr ?? ""}`, { cause: error });
}
}