Files
fusion/packages/mobile/scripts/live-reload.ts
gsxdsm 1b3296d91f feat(FN-1093): add mobile build, PWA, and live reload workflow
- Optimize dashboard Vite output for mobile and include vite/client types in app typecheck
- Add PWA support with manifest, service worker, icons, and client registration hooks
- Add a mobile workspace package with Capacitor config and live-reload scripts for local development
- Add a dedicated GitHub Actions mobile pipeline and update mobile workflow documentation
- Add dashboard tests for build output, mobile scripts, and PWA asset coverage
2026-04-07 23:25:34 -07:00

119 lines
2.9 KiB
TypeScript

import { spawn, type ChildProcess } from "node:child_process";
import { setTimeout as delay } from "node:timers/promises";
type MobilePlatform = "ios" | "android";
interface Args {
platform: MobilePlatform;
serverUrl: string;
}
function parseArgs(argv: string[]): Args {
let platform: MobilePlatform | undefined;
let serverUrl = process.env.FUSION_SERVER_URL || "http://localhost:5173";
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "--platform") {
const value = argv[i + 1];
if (value !== "ios" && value !== "android") {
throw new Error("--platform must be either 'ios' or 'android'");
}
platform = value;
i += 1;
continue;
}
if (arg === "--server-url") {
const value = argv[i + 1];
if (!value) {
throw new Error("--server-url requires a value");
}
serverUrl = value;
i += 1;
}
}
if (!platform) {
throw new Error("Missing required --platform argument");
}
return { platform, serverUrl };
}
async function waitForServer(serverUrl: string, timeoutMs = 120_000): Promise<void> {
const start = Date.now();
const healthUrl = new URL("/", serverUrl).toString();
while (Date.now() - start < timeoutMs) {
try {
const response = await fetch(healthUrl, { method: "GET" });
if (response.ok || response.status === 404) {
return;
}
} catch {
// server not ready yet
}
await delay(1_000);
}
throw new Error(`Timed out waiting for dev server at ${serverUrl}`);
}
function command(name: string): string {
return process.platform === "win32" ? `${name}.cmd` : name;
}
function spawnWithInheritedIo(bin: string, args: string[], env: NodeJS.ProcessEnv): ChildProcess {
return spawn(bin, args, {
stdio: "inherit",
env,
});
}
async function run(): Promise<void> {
const { platform, serverUrl } = parseArgs(process.argv.slice(2));
const env: NodeJS.ProcessEnv = {
...process.env,
FUSION_LIVE_RELOAD: "true",
FUSION_SERVER_URL: serverUrl,
};
const devServer = spawnWithInheritedIo(command("pnpm"), ["--filter", "@fusion/dashboard", "dev:serve"], env);
const cleanup = () => {
if (!devServer.killed) {
devServer.kill("SIGTERM");
}
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
try {
await waitForServer(serverUrl);
const capRun = spawnWithInheritedIo(command("npx"), ["cap", "run", platform], env);
await new Promise<void>((resolve, reject) => {
capRun.on("error", reject);
capRun.on("exit", (code) => {
if (code === 0) {
resolve();
return;
}
reject(new Error(`cap run ${platform} exited with code ${code ?? "unknown"}`));
});
});
} finally {
cleanup();
}
}
run().catch((error) => {
console.error("[mobile live-reload]", error instanceof Error ? error.message : error);
process.exitCode = 1;
});