#!/usr/bin/env node import { spawn, spawnSync } from "node:child_process"; import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { resolvePnpmRunner } from "./pnpm-runner.mjs"; import { buildCmdExeCommandLine } from "./windows-cmd-helpers.mjs"; const here = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(here, ".."); const uiDir = path.join(repoRoot, "ui"); const WINDOWS_CMD_EXE_EXTENSIONS = new Set([".cmd", ".bat"]); function usage() { // keep this tiny; it's invoked from npm scripts too process.stderr.write("Usage: node scripts/ui.js [...args]\n"); } export function shouldUseCmdExeForCommand(cmd, platform = process.platform) { if (platform !== "win32") { return false; } const extension = path.extname(cmd).toLowerCase(); return WINDOWS_CMD_EXE_EXTENSIONS.has(extension); } export function resolveSpawnCall(cmd, args, envOverride, params = {}) { const platform = params.platform ?? process.platform; const comSpec = params.comSpec ?? process.env.ComSpec ?? "cmd.exe"; const options = { cwd: params.cwd ?? uiDir, stdio: "inherit", env: envOverride ?? process.env, shell: false, }; if (shouldUseCmdExeForCommand(cmd, platform)) { return { command: comSpec, args: ["/d", "/s", "/c", buildCmdExeCommandLine(cmd, args)], options: { ...options, windowsVerbatimArguments: true, }, }; } return { command: cmd, args, options, }; } export function resolvePnpmSpawnCall(pnpmArgs, envOverride, params = {}) { const env = envOverride ?? process.env; const platform = params.platform ?? process.platform; const runner = resolvePnpmRunner({ pnpmArgs, nodeExecPath: params.nodeExecPath ?? process.execPath, npmExecPath: params.npmExecPath ?? env.npm_execpath, comSpec: params.comSpec ?? env.ComSpec, platform, }); return { command: runner.command, args: runner.args, options: { cwd: params.cwd ?? uiDir, stdio: "inherit", env, shell: runner.shell, windowsVerbatimArguments: runner.windowsVerbatimArguments, }, }; } function runSpawnCall(spawnCall, label) { const { command, args: spawnArgs, options } = spawnCall; let child; try { child = spawn(command, spawnArgs, options); } catch (err) { console.error(`Failed to launch ${label}:`, err); process.exit(1); return; } child.on("error", (err) => { console.error(`Failed to launch ${label}:`, err); process.exit(1); }); child.on("exit", (code) => { if (code !== 0) { process.exit(code ?? 1); } }); } function run(cmd, args) { runSpawnCall(resolveSpawnCall(cmd, args), cmd); } function runPnpm(args, envOverride) { runSpawnCall(resolvePnpmSpawnCall(args, envOverride), "pnpm"); } function runSpawnCallSync(spawnCall, label) { const { command, args: spawnArgs, options } = spawnCall; let result; try { result = spawnSync(command, spawnArgs, options); } catch (err) { console.error(`Failed to launch ${label}:`, err); process.exit(1); return; } if (result.signal) { process.exit(1); } if ((result.status ?? 1) !== 0) { process.exit(result.status ?? 1); } } function runPnpmSync(args, envOverride) { runSpawnCallSync(resolvePnpmSpawnCall(args, envOverride), "pnpm"); } function depsInstalled(kind) { try { const require = createRequire(path.join(uiDir, "package.json")); require.resolve("vite"); require.resolve("dompurify"); if (kind === "test") { require.resolve("vitest"); require.resolve("@vitest/browser-playwright"); require.resolve("playwright"); } return true; } catch { return false; } } function resolveScriptAction(action) { if (action === "install") { return null; } if (action === "dev") { return "dev"; } if (action === "build") { return "build"; } if (action === "test") { return "test"; } return null; } export function main(argv = process.argv.slice(2)) { const [action, ...rest] = argv; if (!action) { usage(); process.exit(2); } const script = resolveScriptAction(action); if (action !== "install" && !script) { usage(); process.exit(2); } if (process.env.OPENCLAW_BUILD_ALL_NO_PNPM === "1" && action === "build") { run(process.execPath, [path.join(repoRoot, "node_modules/vite/bin/vite.js"), "build", ...rest]); return; } if (action === "install") { runPnpm(["install", ...rest]); return; } if (!depsInstalled(action === "test" ? "test" : "build")) { const installEnv = process.env; const installArgs = ["install"]; runPnpmSync(installArgs, installEnv); } runPnpm(["run", script, ...rest]); } export function resolveDirectExecutionPath(entry, realpath = fs.realpathSync.native) { const resolved = path.resolve(entry); try { return realpath(resolved); } catch { return resolved; } } export function isDirectScriptExecution( entry = process.argv[1], scriptPath = fileURLToPath(import.meta.url), realpath = fs.realpathSync.native, ) { if (!entry) { return false; } return ( resolveDirectExecutionPath(entry, realpath) === resolveDirectExecutionPath(scriptPath, realpath) ); } const isDirectExecution = isDirectScriptExecution(); if (isDirectExecution) { main(); }