Files
automaker/start.mjs
webdevcody 9552670d3d feat: introduce development mode launch script
- Added a new script (dev.mjs) to start the application in development mode with hot reloading using Vite.
- The script includes functionality for installing Playwright browsers, resolving port configurations, and launching either a web or desktop application.
- Removed the old init.mjs script, which was previously responsible for launching the application.
- Updated package.json to reference the new dev.mjs script for the development command.
- Introduced a shared utilities module (launcher-utils.mjs) for common functionalities used in both development and production scripts.
2026-01-03 23:11:18 -05:00

265 lines
7.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Automaker - Production Mode Launch Script
*
* This script runs the application in production mode (no Vite dev server).
* It builds everything if needed, then serves static files via vite preview.
*
* Key differences from dev.mjs:
* - Uses pre-built static files instead of Vite dev server (faster startup)
* - No HMR or hot reloading
* - Server runs from compiled dist/ directory
* - Uses "vite preview" to serve static UI files
*
* Usage: npm run start
*/
import path from 'path';
import { fileURLToPath } from 'url';
import {
createRestrictedFs,
log,
runNpm,
runNpmAndWait,
runNpx,
printHeader,
printModeMenu,
resolvePortConfiguration,
createCleanupHandler,
setupSignalHandlers,
startServerAndWait,
ensureDependencies,
prompt,
killProcessTree,
sleep,
} from './scripts/launcher-utils.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Create restricted fs for this script's directory
const fs = createRestrictedFs(__dirname, 'start.mjs');
// Track background processes for cleanup
const processes = {
server: null,
web: null,
electron: null,
};
/**
* Check if production builds exist
* @returns {{server: boolean, ui: boolean, electron: boolean}}
*/
function checkBuilds() {
const serverDist = path.join(__dirname, 'apps', 'server', 'dist');
const uiDist = path.join(__dirname, 'apps', 'ui', 'dist');
const electronDist = path.join(__dirname, 'apps', 'ui', 'dist-electron', 'main.js');
return {
server: fs.existsSync(serverDist),
ui: fs.existsSync(uiDist),
electron: fs.existsSync(electronDist),
};
}
/**
* Build all production artifacts if needed
*/
async function ensureProductionBuilds() {
// Always build shared packages first to ensure they're up to date
log('Building shared packages...', 'blue');
try {
await runNpmAndWait(['run', 'build:packages'], { stdio: 'inherit' }, __dirname);
log('✓ Shared packages built', 'green');
} catch (error) {
log(`Failed to build shared packages: ${error.message}`, 'red');
process.exit(1);
}
// Always rebuild server to ensure it's in sync with packages
log('Building server...', 'blue');
try {
await runNpmAndWait(
['run', 'build'],
{ stdio: 'inherit' },
path.join(__dirname, 'apps', 'server')
);
log('✓ Server built', 'green');
} catch (error) {
log(`Failed to build server: ${error.message}`, 'red');
process.exit(1);
}
// Check if UI/Electron builds exist (these are slower, so only build if missing)
const builds = checkBuilds();
if (!builds.ui || !builds.electron) {
log('UI/Electron builds not found. Building...', 'yellow');
console.log('');
try {
log('Building UI...', 'blue');
await runNpmAndWait(['run', 'build'], { stdio: 'inherit' }, __dirname);
log('✓ Build complete!', 'green');
console.log('');
} catch (error) {
log(`Build failed: ${error.message}`, 'red');
process.exit(1);
}
} else {
log('✓ UI builds found', 'green');
console.log('');
}
}
/**
* Main function
*/
async function main() {
// Change to script directory
process.chdir(__dirname);
printHeader('Automaker Production Mode');
// Ensure dependencies are installed
await ensureDependencies(fs, __dirname);
// Build production artifacts if needed
await ensureProductionBuilds();
// Resolve port configuration (check/kill/change ports)
const { webPort, serverPort, corsOriginEnv } = await resolvePortConfiguration();
// Show mode selection menu
printModeMenu();
// Setup cleanup handlers
const cleanup = createCleanupHandler(processes);
setupSignalHandlers(cleanup);
// Prompt for choice
while (true) {
const choice = await prompt('Enter your choice (1 or 2): ');
if (choice === '1') {
console.log('');
log('Launching Web Application (Production Mode)...', 'blue');
// Start the backend server in PRODUCTION mode
// Uses "npm run start" in apps/server which runs the compiled dist/
// NOT the Vite dev server (no HMR, faster startup)
processes.server = await startServerAndWait({
serverPort,
corsOriginEnv,
npmArgs: ['run', 'start'],
cwd: path.join(__dirname, 'apps', 'server'),
fs,
baseDir: __dirname,
});
if (!processes.server) {
cleanup();
process.exit(1);
}
log(`Starting web server...`, 'blue');
// Start vite preview to serve pre-built static files
// This is NOT Vite dev server - it just serves the dist/ folder
// No HMR, no compilation, just static file serving
processes.web = runNpx(
['vite', 'preview', '--port', String(webPort)],
{
stdio: 'inherit',
env: {
VITE_SERVER_URL: `http://localhost:${serverPort}`,
},
},
path.join(__dirname, 'apps', 'ui')
);
log(`The application is available at: http://localhost:${webPort}`, 'green');
console.log('');
await new Promise((resolve) => {
processes.web.on('close', resolve);
});
break;
} else if (choice === '2') {
console.log('');
log('Launching Desktop Application (Production Mode)...', 'blue');
log('(Electron will start its own backend server)', 'yellow');
console.log('');
// Run electron directly with the built main.js
const electronMainPath = path.join(__dirname, 'apps', 'ui', 'dist-electron', 'main.js');
if (!fs.existsSync(electronMainPath)) {
log('Error: Electron main process not built. Run build first.', 'red');
process.exit(1);
}
// Start vite preview to serve built static files for electron
// (Electron in non-packaged mode needs a server to load from)
log('Starting static file server...', 'blue');
processes.web = runNpx(
['vite', 'preview', '--port', String(webPort)],
{
stdio: ['ignore', 'pipe', 'pipe'],
env: {
VITE_SERVER_URL: `http://localhost:${serverPort}`,
},
},
path.join(__dirname, 'apps', 'ui')
);
// Wait for vite preview to start
await sleep(2000);
// Use electron from node_modules with NODE_ENV=production
// This ensures electron loads from the preview server, not Vite dev
processes.electron = runNpx(
['electron', electronMainPath],
{
stdio: 'inherit',
env: {
TEST_PORT: String(webPort),
PORT: String(serverPort),
VITE_DEV_SERVER_URL: `http://localhost:${webPort}`,
VITE_SERVER_URL: `http://localhost:${serverPort}`,
CORS_ORIGIN: corsOriginEnv,
NODE_ENV: 'production',
},
},
path.join(__dirname, 'apps', 'ui')
);
await new Promise((resolve) => {
processes.electron.on('close', () => {
// Also kill vite preview when electron closes
if (processes.web && !processes.web.killed && processes.web.pid) {
killProcessTree(processes.web.pid);
}
resolve();
});
});
break;
} else {
log('Invalid choice. Please enter 1 or 2.', 'red');
}
}
}
// Run main function
main().catch((err) => {
console.error(err);
const cleanup = createCleanupHandler(processes);
cleanup();
process.exit(1);
});