feat: add Docker container launch option and update process handling

- Introduced a new option to launch the application in a Docker container (Isolated Mode) from the main menu.
- Added checks for the ANTHROPIC_API_KEY environment variable to ensure proper API functionality.
- Updated process management to include Docker, allowing for better cleanup and handling of spawned processes.
- Enhanced user prompts and logging for improved clarity during the launch process.
This commit is contained in:
webdevcody
2026-01-03 23:53:44 -05:00
parent 586aabe11f
commit 22aa24ae04
4 changed files with 94 additions and 40 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules/

39
dev.mjs
View File

@@ -42,6 +42,7 @@ const processes = {
server: null,
web: null,
electron: null,
docker: null,
};
/**
@@ -96,7 +97,7 @@ async function main() {
// Prompt for choice
while (true) {
const choice = await prompt('Enter your choice (1 or 2): ');
const choice = await prompt('Enter your choice (1, 2, or 3): ');
if (choice === '1') {
console.log('');
@@ -167,9 +168,43 @@ async function main() {
processes.electron.on('close', resolve);
});
break;
} else if (choice === '3') {
console.log('');
log('Launching Docker Container (Isolated Mode)...', 'blue');
log('Building and starting Docker containers...', 'yellow');
console.log('');
// Check if ANTHROPIC_API_KEY is set
if (!process.env.ANTHROPIC_API_KEY) {
log('Warning: ANTHROPIC_API_KEY environment variable is not set.', 'yellow');
log('The server will require an API key to function.', 'yellow');
log('Set it with: export ANTHROPIC_API_KEY=your-key', 'yellow');
console.log('');
}
// Build and start containers with docker-compose
processes.docker = crossSpawn('docker', ['compose', 'up', '--build'], {
stdio: 'inherit',
cwd: __dirname,
env: {
...process.env,
},
});
log('Docker containers starting...', 'blue');
log('UI will be available at: http://localhost:3007', 'green');
log('API will be available at: http://localhost:3008', 'green');
console.log('');
log('Press Ctrl+C to stop the containers.', 'yellow');
await new Promise((resolve) => {
processes.docker.on('close', resolve);
});
break;
} else {
log('Invalid choice. Please enter 1 or 2.', 'red');
log('Invalid choice. Please enter 1, 2, or 3.', 'red');
}
}
}

View File

@@ -496,6 +496,7 @@ export function printModeMenu() {
console.log('═══════════════════════════════════════════════════════');
console.log(' 1) Web Application (Browser)');
console.log(' 2) Desktop Application (Electron)');
console.log(' 3) Docker Container (Isolated)');
console.log('═══════════════════════════════════════════════════════');
console.log('');
}
@@ -506,7 +507,7 @@ export function printModeMenu() {
/**
* Create a cleanup handler for spawned processes
* @param {object} processes - Object with process references {server, web, electron}
* @param {object} processes - Object with process references {server, web, electron, docker}
* @returns {Function} - Cleanup function
*/
export function createCleanupHandler(processes) {
@@ -527,6 +528,10 @@ export function createCleanupHandler(processes) {
killPromises.push(killProcessTree(processes.electron.pid));
}
if (processes.docker && !processes.docker.killed && processes.docker.pid) {
killPromises.push(killProcessTree(processes.docker.pid));
}
await Promise.all(killPromises);
};
}

View File

@@ -18,6 +18,7 @@
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import {
createRestrictedFs,
log,
@@ -36,6 +37,9 @@ import {
sleep,
} from './scripts/launcher-utils.mjs';
const require = createRequire(import.meta.url);
const crossSpawn = require('cross-spawn');
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -47,26 +51,11 @@ const processes = {
server: null,
web: null,
electron: null,
docker: 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
* Build all production artifacts
*/
async function ensureProductionBuilds() {
// Always build shared packages first to ensure they're up to date
@@ -93,26 +82,16 @@ async function ensureProductionBuilds() {
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 {
// Always rebuild UI to ensure it's in sync with latest code
log('Building UI...', 'blue');
try {
await runNpmAndWait(['run', 'build'], { stdio: 'inherit' }, __dirname);
log('✓ Build complete!', 'green');
log('✓ UI built', 'green');
console.log('');
} catch (error) {
log(`Build failed: ${error.message}`, 'red');
log(`Failed to build UI: ${error.message}`, 'red');
process.exit(1);
}
} else {
log('✓ UI builds found', 'green');
console.log('');
}
}
/**
@@ -142,7 +121,7 @@ async function main() {
// Prompt for choice
while (true) {
const choice = await prompt('Enter your choice (1 or 2): ');
const choice = await prompt('Enter your choice (1, 2, or 3): ');
if (choice === '1') {
console.log('');
@@ -248,9 +227,43 @@ async function main() {
});
});
break;
} else if (choice === '3') {
console.log('');
log('Launching Docker Container (Isolated Mode)...', 'blue');
log('Building and starting Docker containers...', 'yellow');
console.log('');
// Check if ANTHROPIC_API_KEY is set
if (!process.env.ANTHROPIC_API_KEY) {
log('Warning: ANTHROPIC_API_KEY environment variable is not set.', 'yellow');
log('The server will require an API key to function.', 'yellow');
log('Set it with: export ANTHROPIC_API_KEY=your-key', 'yellow');
console.log('');
}
// Build and start containers with docker-compose
processes.docker = crossSpawn('docker', ['compose', 'up', '--build'], {
stdio: 'inherit',
cwd: __dirname,
env: {
...process.env,
},
});
log('Docker containers starting...', 'blue');
log('UI will be available at: http://localhost:3007', 'green');
log('API will be available at: http://localhost:3008', 'green');
console.log('');
log('Press Ctrl+C to stop the containers.', 'yellow');
await new Promise((resolve) => {
processes.docker.on('close', resolve);
});
break;
} else {
log('Invalid choice. Please enter 1 or 2.', 'red');
log('Invalid choice. Please enter 1, 2, or 3.', 'red');
}
}
}