feat(desktop): start API and Vite dev servers with proxies
- Start API server on port 3001 and Vite dev server on port 5173 in the desktop dev flow - Proxy /auth, /health, and /api to the local API server for development - Gracefully handle startup/shutdown and report failures clearly
This commit is contained in:
@@ -35,6 +35,7 @@ const sidecarName = process.platform === 'win32'
|
|||||||
|
|
||||||
const sidecarPath = path.join(tauriDir, 'sidecars', sidecarName);
|
const sidecarPath = path.join(tauriDir, 'sidecars', sidecarName);
|
||||||
const distDir = path.join(tauriDir, 'resources', 'web-dist');
|
const distDir = path.join(tauriDir, 'resources', 'web-dist');
|
||||||
|
const webDir = path.join(repoRoot, 'packages', 'web');
|
||||||
|
|
||||||
const run = (cmd, args, cwd) => {
|
const run = (cmd, args, cwd) => {
|
||||||
const result = spawnSync(cmd, args, { cwd, stdio: 'inherit' });
|
const result = spawnSync(cmd, args, { cwd, stdio: 'inherit' });
|
||||||
@@ -47,9 +48,9 @@ const run = (cmd, args, cwd) => {
|
|||||||
console.log('[desktop] ensuring sidecar + web-dist...');
|
console.log('[desktop] ensuring sidecar + web-dist...');
|
||||||
run('node', ['./scripts/build-sidecar.mjs'], desktopDir);
|
run('node', ['./scripts/build-sidecar.mjs'], desktopDir);
|
||||||
|
|
||||||
console.log('[desktop] starting dev server on http://127.0.0.1:3001 ...');
|
console.log('[desktop] starting API server on http://127.0.0.1:3001 ...');
|
||||||
|
|
||||||
const child = spawn(sidecarPath, ['--port', '3001'], {
|
const apiChild = spawn(sidecarPath, ['--port', '3001'], {
|
||||||
cwd: repoRoot,
|
cwd: repoRoot,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
env: {
|
env: {
|
||||||
@@ -61,14 +62,62 @@ const child = spawn(sidecarPath, ['--port', '3001'], {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('[desktop] starting Vite HMR server on http://127.0.0.1:5173 ...');
|
||||||
|
|
||||||
|
const webChild = spawn('bun', ['x', 'vite', '--host', '127.0.0.1', '--port', '5173', '--strictPort'], {
|
||||||
|
cwd: webDir,
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
OPENCHAMBER_PORT: process.env.OPENCHAMBER_PORT || '3001',
|
||||||
|
NO_PROXY: process.env.NO_PROXY || 'localhost,127.0.0.1',
|
||||||
|
no_proxy: process.env.no_proxy || 'localhost,127.0.0.1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let shuttingDown = false;
|
||||||
|
|
||||||
const shutdown = () => {
|
const shutdown = () => {
|
||||||
|
if (shuttingDown) return;
|
||||||
|
shuttingDown = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
child.kill('SIGTERM');
|
apiChild.kill('SIGTERM');
|
||||||
} catch {
|
} catch {}
|
||||||
// ignore
|
|
||||||
}
|
try {
|
||||||
|
webChild.kill('SIGTERM');
|
||||||
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleExit = (label) => (code, signal) => {
|
||||||
|
if (shuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code !== 0 || signal) {
|
||||||
|
console.error(`[desktop] ${label} exited unexpectedly (code=${code ?? 'null'} signal=${signal ?? 'none'})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown();
|
||||||
|
process.exit(typeof code === 'number' ? code : 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
apiChild.on('exit', handleExit('API server'));
|
||||||
|
webChild.on('exit', handleExit('Vite server'));
|
||||||
|
|
||||||
|
const handleError = (label) => (error) => {
|
||||||
|
if (shuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(`[desktop] failed to start ${label}:`, error);
|
||||||
|
shutdown();
|
||||||
|
process.exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
apiChild.on('error', handleError('API server'));
|
||||||
|
webChild.on('error', handleError('Vite server'));
|
||||||
|
|
||||||
process.on('SIGINT', shutdown);
|
process.on('SIGINT', shutdown);
|
||||||
process.on('SIGTERM', shutdown);
|
process.on('SIGTERM', shutdown);
|
||||||
process.on('exit', shutdown);
|
process.on('exit', shutdown);
|
||||||
|
|||||||
@@ -1504,7 +1504,6 @@ fn main() {
|
|||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
// Always ensure local server is running for escape hatch.
|
|
||||||
let local_url = if cfg!(debug_assertions) {
|
let local_url = if cfg!(debug_assertions) {
|
||||||
let dev_url = "http://127.0.0.1:3001";
|
let dev_url = "http://127.0.0.1:3001";
|
||||||
if wait_for_health(dev_url).await {
|
if wait_for_health(dev_url).await {
|
||||||
@@ -1528,29 +1527,41 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let local_ui_url = if cfg!(debug_assertions) {
|
||||||
|
let vite_url = "http://127.0.0.1:5173";
|
||||||
|
if wait_for_health(vite_url).await {
|
||||||
|
vite_url.to_string()
|
||||||
|
} else {
|
||||||
|
log::warn!("[desktop] Vite dev server not ready, using local API UI at {local_url}");
|
||||||
|
local_url.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
local_url.clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure local URL is always available to desktop commands,
|
// Ensure local URL is always available to desktop commands,
|
||||||
// even when we are using the Vite dev server (no sidecar child).
|
// even when we are using the Vite dev server (no sidecar child).
|
||||||
if let Some(state) = handle.try_state::<SidecarState>() {
|
if let Some(state) = handle.try_state::<SidecarState>() {
|
||||||
*state.url.lock().expect("sidecar url mutex") = Some(local_url.clone());
|
*state.url.lock().expect("sidecar url mutex") = Some(local_url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let local_origin = url::Url::parse(&local_url)
|
let local_origin = url::Url::parse(&local_ui_url)
|
||||||
.ok()
|
.ok()
|
||||||
.map(|u| u.origin().ascii_serialization())
|
.map(|u| u.origin().ascii_serialization())
|
||||||
.unwrap_or_else(|| local_url.clone());
|
.unwrap_or_else(|| local_ui_url.clone());
|
||||||
|
|
||||||
// Selected host: env override first, then desktop default host, else local.
|
// Selected host: env override first, then desktop default host, else local.
|
||||||
let env_target = std::env::var("OPENCHAMBER_SERVER_URL")
|
let env_target = std::env::var("OPENCHAMBER_SERVER_URL")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|raw| normalize_server_url(&raw));
|
.and_then(|raw| normalize_server_url(&raw));
|
||||||
|
|
||||||
let mut initial_url = env_target.unwrap_or_else(|| local_url.clone());
|
let mut initial_url = env_target.unwrap_or_else(|| local_ui_url.clone());
|
||||||
|
|
||||||
if initial_url == local_url {
|
if initial_url == local_ui_url {
|
||||||
let cfg = read_desktop_hosts_config_from_disk();
|
let cfg = read_desktop_hosts_config_from_disk();
|
||||||
if let Some(default_id) = cfg.default_host_id {
|
if let Some(default_id) = cfg.default_host_id {
|
||||||
if default_id == LOCAL_HOST_ID {
|
if default_id == LOCAL_HOST_ID {
|
||||||
initial_url = local_url.clone();
|
initial_url = local_ui_url.clone();
|
||||||
} else if let Some(host) = cfg.hosts.into_iter().find(|h| h.id == default_id) {
|
} else if let Some(host) = cfg.hosts.into_iter().find(|h| h.id == default_id) {
|
||||||
initial_url = host.url;
|
initial_url = host.url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
'/auth': {
|
||||||
|
target: `http://127.0.0.1:${process.env.OPENCHAMBER_PORT || 3001}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/health': {
|
||||||
|
target: `http://127.0.0.1:${process.env.OPENCHAMBER_PORT || 3001}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/api': {
|
'/api': {
|
||||||
target: `http://127.0.0.1:${process.env.OPENCHAMBER_PORT || 3001}`,
|
target: `http://127.0.0.1:${process.env.OPENCHAMBER_PORT || 3001}`,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user