Files
pack/.mise/tasks/run-with-frontend
zeekayandHanzo Dev bc404adbb6 core: pull the builder and runtime image from hanzoai/pack
RailpackBuilderImage and RailpackRuntimeImage named
ghcr.io/railwayapp/railpack-{builder,runtime}, so every `pack build`
pulled the upstream vendor's images instead of our own. Point them at
ghcr.io/hanzoai/pack-{builder,runtime}, published by the release
pipeline as of the prior commit.

Also updates the local-dev mirrors of the same reference: the
mise-builder-shell task, the run-with-frontend script, the mise clean
task's image cleanup list, and a stale comment in buildkit/frontend.go.

Snapshots refreshed with the project's own task
(UPDATE_SNAPS=true go test -short ./...) and inspected line by line:
every changed line is exactly the builder/runtime image string, nothing
else. The pre-existing, unrelated php-vanilla frankenphp version drift
that the same refresh also touches was left untouched here.

Do not merge until a release tag has gone through the pipeline from the
prior commit and ghcr.io/hanzoai/pack-builder and pack-runtime actually
exist -- until then this is a correct diff sitting ahead of its own
prerequisite, not something to land on main.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 03:57:43 -07:00

139 lines
3.8 KiB
TypeScript
Executable File

#!/usr/bin/env bun
/**
* Development script to test building with the Railpack BuildKit frontend.
*
* This script validates the production BuildKit frontend flow by:
* 1. Generating a build plan using `railpack plan`
* 2. Invoking BuildKit with the frontend container image (ghcr.io/hanzoai/pack-frontend:latest)
* 3. Loading the resulting image into Docker
*
* This is useful for testing the frontend integration locally before publishing,
* as the frontend path is what production platforms use. The `railpack build` command
* uses a different approach (direct BuildKit client) for simplicity.
*
* Example:
* bun scripts/run-with-frontend.ts examples/node-vite-react
* bun scripts/run-with-frontend.ts examples/node-vite-react --env NODE_ENV=production
*/
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import crypto from "node:crypto";
const PLAN_FILE = "railpack-plan.json";
const PRODUCTION_FRONTEND_IMAGE_NAME = "ghcr.io/hanzoai/pack-frontend:latest";
// Parse command line arguments
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Please provide a directory path");
process.exit(1);
}
const dir = args[0];
const envArgs = args.slice(1).filter((arg) => arg.startsWith("--env"));
// Create temp directory to save the build plan to
const randId = Math.random().toString(36).slice(2);
const tmpDir = join("/tmp", "railpack-" + randId);
const planDir = join(tmpDir, "plan");
mkdirSync(planDir, { recursive: true });
const cleanup = () => {
if (existsSync(tmpDir)) {
rmSync(tmpDir, { recursive: true, force: true });
}
};
process.on("exit", cleanup);
process.on("SIGINT", () => {
cleanup();
process.exit();
});
// Generate build plan
console.log(`Generating build plan for ${dir}`);
const planResult = spawnSync(
"go",
["run", "cmd/cli/main.go", "plan", dir, ...envArgs],
{
stdio: ["inherit", "pipe", "inherit"],
}
);
if (planResult.status !== 0) {
console.error("Failed to generate build plan");
process.exit(1);
}
const planPath = join(planDir, PLAN_FILE);
writeFileSync(planPath, planResult.stdout);
// Parse all env vars so that we can use them as secrets
const envVars: Record<string, string> = {};
const secretArgs: string[] = [];
// Find all env args and their values
for (let i = 0; i < args.length; i++) {
if (args[i] === "--env" && i + 1 < args.length) {
const nameValue = args[i + 1];
const [name, value] = nameValue.split("=");
if (name && value) {
envVars[name] = value;
secretArgs.push(`--secret=id=${name},env=${name}`);
}
i++; // Skip the next argument since we've processed it
}
}
// Pipe buildctl and docker load together
const buildctlArgs = [
"build",
`--local`,
`context=${dir}`,
`--local`,
`dockerfile=${planDir}`,
"--frontend=gateway.v0",
"--opt",
`source=${PRODUCTION_FRONTEND_IMAGE_NAME}`,
"--output",
"type=docker,name=test",
...secretArgs,
];
// Options that are passed to our custom frontend
const cacheKey = dir;
buildctlArgs.push("--opt", `cache-key=${cacheKey}`);
if (Object.keys(envVars).length > 0) {
const secretsHash = crypto
.createHash("sha256")
.update(Object.values(envVars).sort().join(""))
.digest("hex");
buildctlArgs.push("--opt", `secrets-hash=${secretsHash}`);
}
console.log(`Executing buildctl\n ${buildctlArgs.join(" ")}`);
const buildctl = spawnSync("buildctl", buildctlArgs, {
stdio: ["inherit", "pipe", "inherit"],
env: { ...process.env, ...envVars },
});
if (buildctl.status !== 0) {
console.error("buildctl command failed");
process.exit(1);
}
const dockerLoad = spawnSync("docker", ["load"], {
input: buildctl.stdout,
stdio: ["pipe", "inherit", "inherit"],
});
if (dockerLoad.status !== 0) {
console.error("docker load failed");
process.exit(1);
}