import { SandboxInstance } from "@blaxel/core";
const sandboxName = "my-expo-sandbox";
const responseHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-Requested-With, X-Blaxel-Workspace, X-Blaxel-Preview-Token, X-Blaxel-Authorization",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Length, X-Request-Id",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
};
async function addRouterOriginToAppJson(
sandbox: SandboxInstance,
previewUrl: string
) {
const appJsonPath = "/app/app.json";
const appJsonContent = await sandbox.fs.read(appJsonPath);
const appJson = JSON.parse(appJsonContent);
appJson.expo = {
...appJson.expo,
extra: {
...(appJson.expo.extra || {}),
router: {
...(appJson.expo.extra?.router || {}),
origin: previewUrl,
},
},
};
await sandbox.fs.write(appJsonPath, JSON.stringify(appJson, null, 2));
}
async function configureExpoProxyUrl(
sandbox: SandboxInstance,
previewUrl: string
): Promise<boolean> {
const baseUrl = previewUrl.replace(/\/$/, "");
let envContent = "";
try {
envContent = await sandbox.fs.read("/app/.env");
} catch {
// File doesn't exist
}
const expectedEnvLine = `EXPO_PACKAGER_PROXY_URL=${baseUrl}`;
if (envContent.includes(expectedEnvLine)) {
return false;
}
const lines = envContent
.split("\n")
.filter((line) => !line.startsWith("EXPO_PACKAGER_PROXY_URL="));
lines.push(expectedEnvLine);
await sandbox.fs.write("/app/.env", lines.join("\n"));
return true;
}
async function startDevServer(sandbox: SandboxInstance) {
await sandbox.process.exec({
name: "dev-server",
command: "npx expo start --web --port 8081 --scheme exp",
workingDir: "/app",
waitForPorts: [8081],
restartOnFailure: true,
maxRestarts: 25,
});
}
async function configureExpo(sandbox: SandboxInstance, previewUrl: string) {
await addRouterOriginToAppJson(sandbox, previewUrl);
const proxyUrlChanged = await configureExpoProxyUrl(sandbox, previewUrl);
await startDevServer(sandbox);
}
async function main() {
try {
// Create or reuse the sandbox
const sandbox = await SandboxInstance.createIfNotExists({
name: sandboxName,
labels: {
framework: "expo",
},
image: "expo-template:latest",
memory: 8096,
ports: [
{ name: "preview", target: 8081, protocol: "HTTP" },
]
});
// Create preview
const preview = await sandbox.previews.createIfNotExists({
metadata: { name: "preview" },
spec: {
responseHeaders,
public: false,
port: 8081,
},
});
// Generate preview token
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24);
const token = await preview.tokens.create(expiresAt);
// Configure Expo (will restart dev server if needed)
await configureExpo(sandbox, preview.spec?.url!);
// Print access URLs
const webUrl = `${preview.spec?.url}?bl_preview_token=${token.value}`;
const expoUrl = webUrl.replace("https://", "exp://");
console.log(`Web Preview URL: ${webUrl}`);
console.log(`Expo Mobile URL: ${expoUrl}`);
// Stream logs
const logStream = sandbox.process.streamLogs("dev-server", {
onLog(log) {
console.log(log)
},
});
// Keep running until interrupted
process.on("SIGINT", () => {
logStream.close();
process.exit(0);
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();