Skip to content
Closed
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
126 changes: 71 additions & 55 deletions .github/workflows/reusable-homebrew-bottle-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,75 +340,90 @@ jobs:
bash scripts/dev-shell.sh bash "$sidecar_script"

browser_gallery_root=""
if [ "$KANDELO_HOMEBREW_FORMULA" = "hello" ] && [ "$KANDELO_HOMEBREW_ARCH" = "wasm32" ]; then
browser_vfs_root="$RUNNER_TEMP/homebrew-browser-vfs"
if [ "$KANDELO_HOMEBREW_ARCH" = "wasm32" ]; then
browser_result_dir="$RUNNER_TEMP/homebrew-browser-smoke"
browser_gallery_root="$RUNNER_TEMP/homebrew-browser-gallery"
browser_public_dir="$GITHUB_WORKSPACE/kandelo/apps/browser-demos/public/__kandelo-smoke"
browser_port="${KANDELO_PLAYWRIGHT_PORT:-5401}"
browser_vfs="$browser_vfs_root/homebrew-hello.vfs.zst"
browser_report="$browser_vfs_root/homebrew-hello.vfs-report.json"
browser_url="http://127.0.0.1:${browser_port}/__kandelo-smoke/homebrew-hello.vfs.zst"
mkdir -p "$browser_vfs_root" "$browser_gallery_root" "$browser_public_dir"

bash scripts/dev-shell.sh npx tsx images/vfs/scripts/build-homebrew-vfs-image.ts \
--metadata "$sidecar_root/Kandelo/metadata.json" \
--tap-root "$sidecar_root" \
--package hello \
--arch wasm32 \
--runtime node \
--out "$browser_vfs" \
--report "$browser_report" \
--bottle-cache "$RUNNER_TEMP/homebrew-bottle-cache" \
--write-profile
cp "$browser_vfs" "$browser_public_dir/homebrew-hello.vfs.zst"
mkdir -p "$browser_result_dir" "$browser_gallery_root"

(
cd apps/browser-demos
npx playwright install chromium --with-deps
KANDELO_PLAYWRIGHT_PORT="$browser_port" \
KANDELO_BROWSER_HELLO_VFS_URL="$browser_url" \
npx playwright test test/kandelo-homebrew.spec.ts \
--project=chromium \
--grep "Homebrew hello VFS image boots"
)

export KANDELO_HOMEBREW_BROWSER_SMOKE_STATUS="success"
export KANDELO_HOMEBREW_VFS_IMAGE="$browser_vfs"
export KANDELO_HOMEBREW_VFS_REPORT="$browser_report"
export KANDELO_HOMEBREW_GALLERY_ROOT="$browser_gallery_root"
export KANDELO_HOMEBREW_BROWSER_SMOKE_URL="$browser_url"
export KANDELO_HOMEBREW_BROWSER_SMOKE_COMMAND="/home/linuxbrew/.linuxbrew/bin/hello --version"
set +e
KANDELO_PLAYWRIGHT_PORT="$browser_port" \
bash scripts/dev-shell.sh npx tsx scripts/homebrew-package-browser-smoke.ts \
--tap-root "$sidecar_root" \
--formula "$KANDELO_HOMEBREW_FORMULA" \
--arch wasm32 \
--result-dir "$browser_result_dir" \
--bottle-cache "$RUNNER_TEMP/homebrew-bottle-cache" \
--port "$browser_port" \
--run-id "gha-${KANDELO_HOMEBREW_FORMULA}-${KANDELO_HOMEBREW_ARCH}-${GITHUB_RUN_ID}"
browser_smoke_exit=$?
set -e

if [ ! -f "$browser_result_dir/summary.json" ]; then
echo "::error::browser smoke did not produce $browser_result_dir/summary.json"
exit 1
fi

export KANDELO_HOMEBREW_BROWSER_SMOKE_SUMMARY="$browser_result_dir/summary.json"

rm -rf "$sidecar_root"
mkdir -p "$sidecar_root"
bash scripts/dev-shell.sh bash "$sidecar_script"

bash scripts/homebrew-create-browser-gallery.sh \
--metadata "$sidecar_root/Kandelo/metadata.json" \
--image "$browser_vfs" \
--report "$browser_report" \
--out "$browser_gallery_root" \
--formula hello \
--title "GNU hello Homebrew VFS" \
--description "GNU hello poured from the published Automattic/kandelo-homebrew bottle into a browser-smoked Kandelo VFS image."
node scripts/validate-software-gallery.mjs \
--gallery "$browser_gallery_root/gallery.json" \
--index "$browser_gallery_root/index.toml"

if [ "$KANDELO_HOMEBREW_DRY_RUN" != "true" ]; then
if ! gh release view "$KANDELO_HOMEBREW_RELEASE_TAG" --repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" >/dev/null 2>&1; then
gh release create "$KANDELO_HOMEBREW_RELEASE_TAG" \
--repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" \
--target "$(git -C "$GITHUB_WORKSPACE/tap" rev-parse HEAD)" \
--title "$KANDELO_HOMEBREW_RELEASE_TAG" \
--notes "Kandelo Homebrew bottle sidecars and browser gallery assets."
if [ "$browser_smoke_exit" -ne 0 ]; then
echo "::warning::browser smoke exited $browser_smoke_exit; final sidecars record the browser outcome from $browser_result_dir/summary.json"
fi

if jq -e \
--arg formula "$KANDELO_HOMEBREW_FORMULA" \
--arg arch "$KANDELO_HOMEBREW_ARCH" \
'.packages[] | select(.formula == $formula and .arch == $arch and .status == "success")' \
"$browser_result_dir/summary.json" >/dev/null; then
browser_vfs="$(jq -r \
--arg formula "$KANDELO_HOMEBREW_FORMULA" \
--arg arch "$KANDELO_HOMEBREW_ARCH" \
'.packages[] | select(.formula == $formula and .arch == $arch) | .vfs_image' \
"$browser_result_dir/summary.json")"
browser_report="$(jq -r \
--arg formula "$KANDELO_HOMEBREW_FORMULA" \
--arg arch "$KANDELO_HOMEBREW_ARCH" \
'.packages[] | select(.formula == $formula and .arch == $arch) | .vfs_report' \
"$browser_result_dir/summary.json")"

if [ "$KANDELO_HOMEBREW_FORMULA" = "hello" ]; then
bash scripts/homebrew-create-browser-gallery.sh \
--metadata "$sidecar_root/Kandelo/metadata.json" \
--image "$browser_vfs" \
--report "$browser_report" \
--out "$browser_gallery_root" \
--formula hello \
--title "GNU hello Homebrew VFS" \
--description "GNU hello poured from the published Automattic/kandelo-homebrew bottle into a browser-smoked Kandelo VFS image."
node scripts/validate-software-gallery.mjs \
--gallery "$browser_gallery_root/gallery.json" \
--index "$browser_gallery_root/index.toml"

if [ "$KANDELO_HOMEBREW_DRY_RUN" != "true" ]; then
if ! gh release view "$KANDELO_HOMEBREW_RELEASE_TAG" --repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" >/dev/null 2>&1; then
gh release create "$KANDELO_HOMEBREW_RELEASE_TAG" \
--repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" \
--target "$(git -C "$GITHUB_WORKSPACE/tap" rev-parse HEAD)" \
--title "$KANDELO_HOMEBREW_RELEASE_TAG" \
--notes "Kandelo Homebrew bottle sidecars and browser gallery assets."
fi
gh release upload "$KANDELO_HOMEBREW_RELEASE_TAG" \
"$browser_gallery_root/gallery.json" \
"$browser_gallery_root/index.toml" \
"$browser_gallery_root"/*.tar.zst \
--repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" \
--clobber
fi
fi
gh release upload "$KANDELO_HOMEBREW_RELEASE_TAG" \
"$browser_gallery_root/gallery.json" \
"$browser_gallery_root/index.toml" \
"$browser_gallery_root"/*.tar.zst \
--repo "$KANDELO_HOMEBREW_TAP_REPOSITORY" \
--clobber
fi
fi

Expand Down Expand Up @@ -460,6 +475,7 @@ jobs:
path: |
${{ runner.temp }}/homebrew-bottle/**
${{ runner.temp }}/homebrew-browser-gallery/**
${{ runner.temp }}/homebrew-browser-smoke/**
${{ runner.temp }}/homebrew-browser-vfs/**
${{ runner.temp }}/homebrew-sidecars/**
${{ runner.temp }}/homebrew-*.txt
Expand Down
35 changes: 35 additions & 0 deletions apps/browser-demos/pages/homebrew-smoke/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kandelo Homebrew Browser Smoke</title>
<style>
:root {
color-scheme: dark;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
background: #111;
color: #eee;
}

body {
margin: 0;
padding: 16px;
}

#status {
margin-bottom: 12px;
}

#log {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
</style>
</head>
<body>
<div id="status">Loading</div>
<pre id="log"></pre>
<script type="module" src="./main.ts"></script>
</body>
</html>
126 changes: 126 additions & 0 deletions apps/browser-demos/pages/homebrew-smoke/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { BrowserKernel } from "@host/browser-kernel-host";
import { ABI_VERSION } from "@host/generated/abi";
import { MemoryFileSystem } from "@host/vfs/memory-fs";
import kernelWasmUrl from "@kernel-wasm?url";

interface HomebrewSmokeRequest {
vfsUrl: string;
argv: string[];
timeoutMs?: number;
cwd?: string;
env?: string[];
}

interface HomebrewSmokeResult {
exitCode: number;
stdout: string;
stderr: string;
combined: string;
durationMs: number;
}

declare global {
interface Window {
__homebrewSmokeReady: boolean;
__runHomebrewSmoke: (request: HomebrewSmokeRequest) => Promise<HomebrewSmokeResult>;
}
}

const statusEl = document.getElementById("status")!;
const logEl = document.getElementById("log")!;
const decoder = new TextDecoder();

let kernelBytes: ArrayBuffer | null = null;

function appendLog(text: string): void {
logEl.textContent += text;
}

async function fetchBytes(url: string, label: string): Promise<ArrayBuffer> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${label} fetch failed: ${response.status} ${response.statusText}`);
}
return response.arrayBuffer();
}

function timeoutAfter(ms: number): Promise<never> {
return new Promise((_, reject) => {
window.setTimeout(() => reject(new Error("TIMEOUT")), ms);
});
}

async function runHomebrewSmoke(request: HomebrewSmokeRequest): Promise<HomebrewSmokeResult> {
if (!kernelBytes) throw new Error("kernel wasm is not loaded");
if (!request.vfsUrl) throw new Error("vfsUrl is required");
if (!Array.isArray(request.argv) || request.argv.length === 0) {
throw new Error("argv must contain at least argv[0]");
}

const start = performance.now();
let stdout = "";
let stderr = "";
const vfsBytes = new Uint8Array(await fetchBytes(request.vfsUrl, "Homebrew VFS"));
MemoryFileSystem.assertImageKernelAbi(vfsBytes, ABI_VERSION, "Homebrew smoke VFS");

const kernel = new BrowserKernel({
kernelOwnedFs: true,
onStdout: (data) => {
const text = decoder.decode(data);
stdout += text;
appendLog(text);
},
onStderr: (data) => {
const text = decoder.decode(data);
stderr += text;
appendLog(text);
},
});

try {
const { exit } = await kernel.boot({
kernelWasm: kernelBytes,
vfsImage: vfsBytes,
argv: request.argv,
cwd: request.cwd ?? "/",
env: request.env ?? [
"HOME=/tmp",
"TMPDIR=/tmp",
"TERM=xterm-256color",
"LANG=en_US.UTF-8",
"PATH=/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin",
],
uid: 0,
gid: 0,
stdin: new Uint8Array(0),
});
const exitCode = await Promise.race([
exit,
timeoutAfter(request.timeoutMs ?? 180_000),
]);
return {
exitCode,
stdout,
stderr,
combined: `${stdout}${stderr}`,
durationMs: Math.round(performance.now() - start),
};
} finally {
await kernel.destroy().catch(() => {});
}
}

async function init(): Promise<void> {
kernelBytes = await fetchBytes(kernelWasmUrl, "kernel.wasm");
window.__runHomebrewSmoke = runHomebrewSmoke;
window.__homebrewSmokeReady = true;
statusEl.textContent = "Ready";
}

window.__homebrewSmokeReady = false;
init().catch((err) => {
const message = err instanceof Error ? err.message : String(err);
statusEl.textContent = `Error: ${message}`;
appendLog(`${message}\n`);
console.error("Homebrew smoke init failed:", err);
});
1 change: 1 addition & 0 deletions apps/browser-demos/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ const defaultDemoInputs = {

const demoInputs = {
...defaultDemoInputs,
"homebrew-smoke": path.resolve(__dirname, "pages/homebrew-smoke/index.html"),
"sqlite-test": path.resolve(__dirname, "pages/sqlite-test/index.html"),
// The perl, python, ruby, erlang, texlive, and redis package entries
// are not bundled into this static build while their slow builds
Expand Down
Loading
Loading