mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Keep one VM per environment with independent lease cgroups, persistent workspaces, immutable runner images, and an explicit experimental gate. Add live compatibility, recovery, browser-preview, and stress campaigns. Co-Authored-By: Paperclip <noreply@paperclip.ing>
31 lines
2.5 KiB
TypeScript
31 lines
2.5 KiB
TypeScript
import { execFileSync, spawnSync } from "node:child_process";
|
|
import { readFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { computeDaytonaImageContentId, DAYTONA_IMAGE_INPUT_PATHS, extractDaytonaDockerfileFrontendDigest } from "../tests/runner-e2e/daytona-image-content.js";
|
|
|
|
const root = path.resolve(import.meta.dirname, "..");
|
|
const args = process.argv.slice(2);
|
|
const option = (name: string) => { const i = args.indexOf(name); return i < 0 ? undefined : args[i + 1]; };
|
|
const dockerfile = await readFile(path.join(root, "docker/exe-dev-runner/Dockerfile"), "utf8");
|
|
const revision = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8" }).trim().length > 0;
|
|
if (dirty && process.env.PAPERCLIP_IMAGE_ALLOW_DIRTY !== "1") throw new Error("Commit the image inputs before building, or explicitly set PAPERCLIP_IMAGE_ALLOW_DIRTY=1 for a development image");
|
|
// COPY packages includes the workspace graph. Hash every source-owned package
|
|
// input so changes outside the runner can never reuse an old content tag.
|
|
const packageInputs = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z", "packages"], { cwd: root, encoding: "utf8" }).split("\0").filter(Boolean);
|
|
const contentId = await computeDaytonaImageContentId({
|
|
repositoryRoot: root,
|
|
inputPaths: [...new Set([...DAYTONA_IMAGE_INPUT_PATHS.filter((p) => p !== "docker/daytona-runner/Dockerfile" && !p.startsWith("packages/")), ...packageInputs, "server/package.json", "ui/package.json", "cli/package.json", "docker/exe-dev-runner", "scripts/build-exe-dev-image.ts"])].sort(),
|
|
baseImages: [...dockerfile.matchAll(/^FROM (\S+@sha256:[a-f0-9]{64})/gm)].map((match) => match[1]),
|
|
frontendDigest: extractDaytonaDockerfileFrontendDigest(dockerfile),
|
|
});
|
|
if (args.includes("--content-id")) { console.log(contentId); process.exit(0); }
|
|
const tag = option("--tag") ?? `ghcr.io/paperclipai/paperclip-exe-runner:experimental-${contentId}`;
|
|
const build = spawnSync("docker", ["buildx", "build", "--platform", "linux/amd64", "--file", "docker/exe-dev-runner/Dockerfile",
|
|
"--build-arg", `PAPERCLIP_RUNNER_SOURCE_REVISION=${revision}`,
|
|
"--build-arg", `PAPERCLIP_RUNNER_CONTENT_ID=${contentId}`,
|
|
"--tag", tag, ...(args.includes("--push") ? ["--push"] : ["--load"]),
|
|
...(option("--metadata-file") ? ["--metadata-file", option("--metadata-file")!] : []), "."], { cwd: root, stdio: "inherit" });
|
|
if (build.error) throw build.error;
|
|
process.exitCode = build.status ?? 1;
|