Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/selfhost-worker-bundler-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"executor": patch
---

Ship @cloudflare/worker-bundler in the self-host Docker runtime so the server starts; it was resolved at runtime since the dynamic Worker bundler change but never copied into the image.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,35 @@ jobs:
context: .
file: apps/host-selfhost/Dockerfile
push: false
load: true
tags: executor-selfhost:ci

# Building alone doesn't catch startup crashes (e.g. a runtime-resolved
# package missing from the packaged runtime), so boot the container and
# require a healthy /api/health before calling the image good.
- name: Boot image and check health
shell: bash
run: |
set -euo pipefail
docker run -d --name selfhost-smoke -p 4788:4788 \
-e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
-e EXECUTOR_BOOTSTRAP_ADMIN_EMAIL=smoke@example.com \
-e EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD="$(openssl rand -hex 16)" \
-e EXECUTOR_WEB_BASE_URL=http://localhost:4788 \
executor-selfhost:ci
for i in $(seq 1 30); do
if [ "$(docker inspect -f '{{.State.Running}}' selfhost-smoke)" != "true" ]; then
echo "container exited during startup"
docker logs selfhost-smoke
exit 1
fi
if curl -fsS http://localhost:4788/api/health >/dev/null 2>&1; then
echo "healthy after ${i}s"
docker logs selfhost-smoke
exit 0
fi
sleep 1
done
echo "health check never passed"
docker logs selfhost-smoke
exit 1
31 changes: 30 additions & 1 deletion apps/host-selfhost/scripts/package-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";

const root = process.cwd();
const out = join(root, ".selfhost-runtime");
Expand Down Expand Up @@ -36,6 +37,7 @@ const libsqlNativePackage = (): string => {
};

const externalPackages = [
"@cloudflare/worker-bundler",
"quickjs-emscripten",
"quickjs-emscripten-core",
"@jitl/quickjs-ffi-types",
Expand Down Expand Up @@ -64,12 +66,39 @@ const copyPackage = (name: string): void => {
cpSync(packageDir(name), destination, { recursive: true, dereference: true });
};

// The worker-bundler backend (packages/plugins/apps/src/pipeline/
// worker-bundler-artifact.ts) loads `dist/index.bundled.js` from the resolved
// package, falling back to bundling `dist/index.js` with esbuild at runtime.
// The npm package doesn't ship the bundled entry and the runtime image has no
// esbuild, so produce it here, the same way apps/cli/src/build.ts does for the
// packed CLI.
const writeBundledWorkerBundler = async (): Promise<void> => {
const distPath = join(packageDir("@cloudflare/worker-bundler"), "dist");
const esbuildEntry = requireFromSelfHost.resolve("esbuild", {
paths: [join(root, "node_modules/.bun/node_modules")],
});
const { build } = await import(pathToFileURL(esbuildEntry).href);
const result = await build({
entryPoints: [join(distPath, "index.js")],
bundle: true,
format: "esm",
platform: "browser",
external: ["./esbuild.wasm"],
logLevel: "silent",
write: false,
});
const source = result.outputFiles[0]?.text;
if (source === undefined) throw new Error("failed to bundle @cloudflare/worker-bundler");
writeFileSync(join(out, "node_modules/@cloudflare/worker-bundler/dist/index.bundled.js"), source);
};

rmSync(out, { recursive: true, force: true });
mkdirSync(serverOut, { recursive: true });

await Bun.$`bun build apps/host-selfhost/src/serve.ts --target=bun --format=esm --outdir=${serverOut} ${quickJsExternals.map((name) => `--external=${name}`)}`;

for (const name of externalPackages) copyPackage(name);
await writeBundledWorkerBundler();

if (!existsSync(join(serverOut, "serve.js"))) {
throw new Error(
Expand Down
Loading