import * as Fs from "node:fs";
import * as Path from "node:path";
import {
assertBinariesInstalled,
heartwoodRelease,
radicleHttpdRelease,
removeWorkspace,
tmpDir,
useLocalHttpd,
} from "@tests/support/support.js";
import {
defaultConfig,
createCobsFixture,
createCommitsFixture,
createMarkdownFixture,
createSourceBrowsingFixture,
gitOptions,
} from "@tests/support/fixtures.js";
import config from "@tests/support/config.js";
import { createPeerManager } from "@tests/support/peerManager.js";
const heartwoodBinaryPath = Path.join(
tmpDir,
"bin",
"heartwood",
heartwoodRelease,
).trim();
const httpdBinaryPath = useLocalHttpd
? Path.join(tmpDir, "bin", "httpd", "local").trim()
: Path.join(tmpDir, "bin", "httpd", radicleHttpdRelease).trim();
process.env.PATH = [
heartwoodBinaryPath,
httpdBinaryPath,
process.env.PATH,
].join(Path.delimiter);
export default async function globalSetup(): Promise<() => void> {
try {
await assertBinariesInstalled("rad", heartwoodRelease, heartwoodBinaryPath);
await assertBinariesInstalled(
"radicle-httpd",
useLocalHttpd ? "pre-release" : radicleHttpdRelease,
httpdBinaryPath,
);
} catch (error) {
console.error(error);
console.log("");
if (useLocalHttpd) {
console.log("To compile local radicle-httpd binary, run:");
console.log(" 👉 ./scripts/compile-local-httpd");
} else {
console.log("To download the required test binaries, run:");
console.log(" 👉 ./scripts/install-binaries");
}
console.log("");
process.exit(1);
}
// Evaluated once at startup; captured by async setup operations.
// Set SKIP_SETUP=true to skip both build and fixture creation on subsequent runs.
// See CONTRIBUTING.md for details.
const shouldSetup = !process.env.SKIP_SETUP;
if (shouldSetup) {
console.log("⚡ Starting parallel setup...");
} else {
console.log("⏭️ Skipping setup (SKIP_SETUP is set)");
}
// Run build and fixture setup in parallel
const buildPromise = (async () => {
if (shouldSetup) {
console.log(" 🔨 Starting build...");
const { execa: exec } = await import("execa");
try {
await exec("npm", ["run", "build"]);
console.log(" 🔨 Build complete");
} catch (error) {
console.log(" 🔨 Build failed!");
if (error && typeof error === "object" && "stdout" in error) {
console.log(error.stdout);
}
if (error && typeof error === "object" && "stderr" in error) {
console.log(error.stderr);
}
throw error;
}
}
})();
const fixturesPromise = (async () => {
if (shouldSetup) {
console.log(" 🗂️ Starting fixture creation...");
await removeWorkspace();
}
const peerManager = await createPeerManager({
dataDir: Path.resolve(tmpDir, "peers"),
outputLog: Fs.createWriteStream(
Path.resolve(tmpDir, "globalPeerManager.log"),
)
// Workaround for fixing MaxListenersExceededWarning.
// Since every prefixOutput stream adds stream listeners that don't autoClose.
// TODO: We still seem to have some descriptors left open when running vitest, which we should handle.
.setMaxListeners(16),
});
const palm = await peerManager.createPeer({
name: "palm",
gitOptions: gitOptions["alice"],
});
if (shouldSetup) {
await palm.startNode({
web: {
pinned: {
repositories: ["rad:z4BwwjPCFNVP27FwVbDFgwVwkjcir"],
},
description: `:seedling: Radicle is an open source, peer-to-peer code collaboration stack built on Git.
:construction: [radicle.dev](https://radicle.dev)`,
},
node: {
...defaultConfig.node,
seedingPolicy: { default: "allow", scope: "all" },
alias: "palm",
},
});
await palm.startHttpd(config.nodes.defaultHttpdPort);
try {
console.log(" Creating source-browsing fixture");
await createSourceBrowsingFixture(peerManager, palm);
console.log(" Creating markdown fixture");
await createMarkdownFixture(palm);
console.log(" Creating cobs fixture");
await createCobsFixture(peerManager, palm);
console.log(" Creating commits fixture");
await createCommitsFixture(palm);
console.log(" 🗂️ All fixtures created");
} catch (error) {
console.log("");
console.log(" 🗂️ Not able to create the required fixtures.");
console.log(
" Make sure you are not using binaries compiled for release.",
);
console.log("");
console.log(error);
console.log("");
process.exit(1);
}
await palm.stopNode();
} else {
await palm.startHttpd(config.nodes.defaultHttpdPort);
}
return peerManager;
})();
// Wait for both build and fixtures to complete
const [, peerManager] = await Promise.all([buildPromise, fixturesPromise]);
if (shouldSetup) {
console.log("🚀 Setup complete, ready to run tests");
}
// Print binary versions
const { execa: exec } = await import("execa");
const { stdout: radVersion } = await exec("rad", ["--version"]);
const { stdout: gitRemoteRadVersion } = await exec("git-remote-rad", [
"--version",
]);
const { stdout: httpdVersion } = await exec("radicle-httpd", ["--version"]);
// radicle-httpd outputs logging lines, extract just the version line (last line)
const httpdVersionClean =
httpdVersion.trim().split("\n").pop() || httpdVersion;
console.log("");
console.log("Binary versions:");
console.log(` rad: ${radVersion.trim()}`);
console.log(` git-remote-rad: ${gitRemoteRadVersion.trim()}`);
console.log(
` radicle-httpd: ${httpdVersionClean}${useLocalHttpd ? " (local)" : ""}`,
);
console.log("");
return async () => {
await peerManager.shutdown();
};
}