UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

253 lines (210 loc) 7.98 kB
// one daemon CLI command import colors from 'picocolors' import { labelProcess } from './label-process' export async function daemon(args: { subcommand?: string port?: string host?: string app?: string slot?: string project?: string tui?: boolean }) { const subcommand = args.subcommand || 'run' switch (subcommand) { case 'run': case 'start': return daemonStart(args) case 'stop': return daemonStop() case 'status': return daemonStatus() case 'route': return daemonRoute(args) default: console.log(`Unknown daemon subcommand: ${subcommand}`) console.log('Available: start, stop, status, route') process.exit(1) } } async function daemonStart(args: { port?: string; host?: string; tui?: boolean }) { labelProcess('daemon') const { isDaemonRunning } = await import('../daemon/ipc') if (await isDaemonRunning()) { console.log(colors.yellow('Daemon is already running')) console.log("Use 'one daemon status' to see registered servers") process.exit(1) } // suggest tray app if available await suggestTrayApp() const { startDaemon } = await import('../daemon/server') // default to TUI if running in interactive terminal const useTUI = args.tui ?? process.stdin.isTTY const { state } = await startDaemon({ port: args.port ? parseInt(args.port, 10) : undefined, host: args.host, quiet: useTUI, // suppress normal logs when TUI is active }) if (useTUI) { const { startTUI } = await import('../daemon/tui') startTUI(state) } } async function daemonStop() { const { isDaemonRunning, getSocketPath, cleanupSocket } = await import('../daemon/ipc') if (!(await isDaemonRunning())) { console.log(colors.yellow('Daemon is not running')) process.exit(1) } // send shutdown signal via IPC // for now, just cleanup socket and let user stop the process manually console.log( colors.yellow( 'Note: daemon runs in foreground. Press Ctrl+C in the daemon terminal to stop.' ) ) console.log(colors.dim(`Socket path: ${getSocketPath()}`)) } async function daemonStatus() { const { isDaemonRunning, getDaemonStatus, getLastActiveDaemonServer } = await import('../daemon/ipc') if (!(await isDaemonRunning())) { console.log(colors.yellow('Daemon is not running')) console.log(colors.dim("Start with 'one daemon'")) process.exit(1) } try { const status = await getDaemonStatus() const lastActive = await getLastActiveDaemonServer() console.log(colors.cyan('\n═══════════════════════════════════════════════════')) console.log(colors.cyan(' one daemon status')) console.log(colors.cyan('═══════════════════════════════════════════════════\n')) if (status.servers.length === 0) { console.log(colors.dim(' No servers registered')) } else { console.log(' Registered servers:') for (const server of status.servers) { const shortRoot = server.root.replace(process.env.HOME || '', '~') const isActive = lastActive?.id === server.id const activeMarker = isActive ? colors.yellow(' ★') : '' console.log( ` ${colors.green(server.id)} ${server.bundleId} → :${server.port} (${shortRoot})${activeMarker}` ) } if (lastActive) { console.log(colors.dim('\n ★ = last active (used by oi/oa)')) } } if (status.routes.length > 0) { console.log('\n Active routes:') for (const route of status.routes) { console.log(` ${route.key}${route.serverId}`) } } console.log('') } catch (err) { console.log(colors.red('Failed to get daemon status')) console.error(err) process.exit(1) } } export async function openPlatform(platform: 'ios' | 'android') { const { isDaemonRunning, getDaemonStatus, setDaemonRoute, touchDaemonServer } = await import('../daemon/ipc') const { getBundleIdFromConfig } = await import('../daemon/utils') const cwd = process.cwd() const bundleId = getBundleIdFromConfig(cwd) if (!bundleId) { console.log(colors.yellow('No app.json found in current directory')) console.log(colors.dim('Run this command from a One project directory')) process.exit(1) } // if daemon is running, pre-set the route so simulator connects to THIS project if (await isDaemonRunning()) { try { const status = await getDaemonStatus() // find server for this project root const server = status.servers.find((s) => s.root === cwd) if (server) { // set route so next connection for this bundleId goes to this server await setDaemonRoute(bundleId, server.id) await touchDaemonServer(server.id) console.log(colors.cyan(`[daemon] Route set: ${bundleId} → this project`)) } else { console.log(colors.yellow(`[daemon] No server registered for this project`)) console.log( colors.dim(`Run 'one dev' first, or the simulator will connect directly`) ) } } catch (err) { console.log(colors.dim(`[daemon] Could not set route: ${err}`)) } } // run from current directory if (platform === 'ios') { const { run } = await import('./runIos') await run({}) } else { const { run } = await import('./runAndroid') await run({}) } } async function daemonRoute(args: { app?: string; slot?: string; project?: string }) { const { isDaemonRunning, getDaemonStatus, setDaemonRoute, clearDaemonRoute } = await import('../daemon/ipc') if (!(await isDaemonRunning())) { console.log(colors.yellow('Daemon is not running')) process.exit(1) } if (!args.app) { console.log(colors.red('Missing --app parameter')) console.log('Usage: one daemon route --app=com.example.app --slot=0') console.log(' or: one daemon route --app=com.example.app --project=~/myapp') process.exit(1) } const status = await getDaemonStatus() // find the server to route to let targetServer: (typeof status.servers)[0] | undefined if (args.slot !== undefined) { // route by slot (index in server list) const slotIndex = parseInt(args.slot, 10) const matchingServers = status.servers.filter((s) => s.bundleId === args.app) if (slotIndex < 0 || slotIndex >= matchingServers.length) { console.log(colors.red(`Invalid slot: ${args.slot}`)) console.log(`Available slots for ${args.app}: 0-${matchingServers.length - 1}`) process.exit(1) } targetServer = matchingServers[slotIndex] } else if (args.project) { // route by project path const normalizedProject = args.project.replace(/^~/, process.env.HOME || '') targetServer = status.servers.find( (s) => s.bundleId === args.app && s.root === normalizedProject ) if (!targetServer) { console.log(colors.red(`No server found for ${args.app} at ${args.project}`)) process.exit(1) } } else { console.log(colors.red('Missing --slot or --project parameter')) process.exit(1) } await setDaemonRoute(args.app, targetServer.id) const shortRoot = targetServer.root.replace(process.env.HOME || '', '~') console.log(colors.green(`Route set: ${args.app}${targetServer.id} (${shortRoot})`)) } async function suggestTrayApp() { const { existsSync } = await import('node:fs') const trayPaths = [ '/Applications/OneTray.app', `${process.env.HOME}/Applications/OneTray.app`, ] const installed = trayPaths.some((p) => existsSync(p)) if (!installed) { console.log( colors.dim(' Tip: install OneTray.app for a native macOS cable interface') ) console.log(colors.dim(' https://github.com/onejs/one/releases?q=one-tray')) console.log('') } }