mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
150 lines
5.4 KiB
TypeScript
150 lines
5.4 KiB
TypeScript
import { startServer } from '../core/server.js';
|
|
import { launchBrowser } from '../utils/browser-launcher.js';
|
|
import { resolvePath, validatePath } from '../utils/path-resolver.js';
|
|
import { startReactFrontend, stopReactFrontend } from '../utils/react-frontend.js';
|
|
import { startDocsSite, stopDocsSite } from '../utils/docs-frontend.js';
|
|
import chalk from 'chalk';
|
|
import type { Server } from 'http';
|
|
|
|
interface ServeOptions {
|
|
port?: number;
|
|
path?: string;
|
|
host?: string;
|
|
browser?: boolean;
|
|
frontend?: 'js' | 'react' | 'both';
|
|
new?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Serve command handler - starts dashboard server with live path switching
|
|
* @param {Object} options - Command options
|
|
*/
|
|
export async function serveCommand(options: ServeOptions): Promise<void> {
|
|
const port = Number(options.port) || 3456;
|
|
const host = options.host || '127.0.0.1';
|
|
// --new flag is shorthand for --frontend react
|
|
const frontend = options.new ? 'react' : (options.frontend || 'js');
|
|
|
|
// Validate project path
|
|
let initialPath = process.cwd();
|
|
if (options.path) {
|
|
const pathValidation = validatePath(options.path, { mustExist: true });
|
|
if (!pathValidation.valid || !pathValidation.path) {
|
|
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
|
|
process.exit(1);
|
|
}
|
|
initialPath = pathValidation.path;
|
|
}
|
|
|
|
console.log(chalk.blue.bold('\n CCW Dashboard Server\n'));
|
|
console.log(chalk.gray(` Initial project: ${initialPath}`));
|
|
console.log(chalk.gray(` Host: ${host}`));
|
|
console.log(chalk.gray(` Port: ${port}`));
|
|
console.log(chalk.gray(` Frontend: ${frontend}\n`));
|
|
|
|
// Start React frontend if needed
|
|
let reactPort: number | undefined;
|
|
if (frontend === 'react' || frontend === 'both') {
|
|
reactPort = port + 1;
|
|
try {
|
|
await startReactFrontend(reactPort);
|
|
} catch (error) {
|
|
console.error(chalk.red(`\n Failed to start React frontend: ${error}\n`));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Start Docusaurus docs site if React frontend is enabled
|
|
// The docs site is proxied through Vite at /docs endpoint
|
|
if (frontend === 'react' || frontend === 'both') {
|
|
try {
|
|
await startDocsSite(3001);
|
|
} catch (error) {
|
|
console.log(chalk.yellow(`\n Warning: Failed to start docs site: ${error}`));
|
|
console.log(chalk.gray(` The /docs endpoint will not be available.`));
|
|
console.log(chalk.gray(` You can start it manually: cd ccw/docs-site && npm run serve -- --build --port 3001 --no-open\n`));
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Start server
|
|
console.log(chalk.cyan(' Starting server...'));
|
|
const server = await startServer({
|
|
port,
|
|
host,
|
|
initialPath,
|
|
frontend,
|
|
reactPort
|
|
});
|
|
|
|
const boundUrl = `http://${host}:${port}`;
|
|
const browserUrl = host === '0.0.0.0' || host === '::' ? `http://localhost:${port}` : boundUrl;
|
|
|
|
if (!['127.0.0.1', 'localhost', '::1'].includes(host)) {
|
|
console.log(chalk.yellow(`\n WARNING: Binding to ${host} exposes the server to network attacks.`));
|
|
console.log(chalk.yellow(' Ensure firewall is configured and never expose tokens publicly.\n'));
|
|
}
|
|
|
|
console.log(chalk.green(` Server running at ${boundUrl}`));
|
|
|
|
// Display frontend URLs
|
|
if (frontend === 'both') {
|
|
console.log(chalk.gray(` JS Frontend: ${boundUrl}`));
|
|
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
|
|
console.log(chalk.gray(` Docs: http://${host}:${reactPort}/docs/`));
|
|
} else if (frontend === 'react') {
|
|
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
|
|
console.log(chalk.gray(` Docs: http://${host}:${reactPort}/docs/`));
|
|
}
|
|
|
|
// Open browser
|
|
if (options.browser !== false) {
|
|
console.log(chalk.cyan(' Opening in browser...'));
|
|
try {
|
|
// Determine which URL to open based on frontend setting
|
|
let openUrl = browserUrl;
|
|
if (frontend === 'react' && reactPort) {
|
|
// React frontend: access via proxy path /react/
|
|
openUrl = `http://${host}:${port}/react/`;
|
|
} else if (frontend === 'both') {
|
|
// Both frontends: default to JS frontend at root
|
|
openUrl = browserUrl;
|
|
}
|
|
|
|
// Add path query parameter for workspace switching
|
|
const pathParam = initialPath ? `?path=${encodeURIComponent(initialPath)}` : '';
|
|
await launchBrowser(openUrl + pathParam);
|
|
console.log(chalk.green.bold('\n Dashboard opened in browser!'));
|
|
} catch (err) {
|
|
const error = err as Error;
|
|
console.log(chalk.yellow(`\n Could not open browser: ${error.message}`));
|
|
console.log(chalk.gray(` Open manually: ${browserUrl}`));
|
|
}
|
|
}
|
|
|
|
console.log(chalk.gray('\n Press Ctrl+C to stop the server\n'));
|
|
|
|
// Handle graceful shutdown
|
|
process.on('SIGINT', async () => {
|
|
console.log(chalk.yellow('\n Shutting down server...'));
|
|
await stopReactFrontend();
|
|
await stopDocsSite();
|
|
server.close(() => {
|
|
console.log(chalk.green(' Server stopped.\n'));
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
const err = error as Error & { code?: string };
|
|
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(chalk.yellow(` Port ${port} is already in use.`));
|
|
console.error(chalk.gray(` Try a different port: ccw serve --port ${port + 1}\n`));
|
|
}
|
|
await stopReactFrontend();
|
|
await stopDocsSite();
|
|
process.exit(1);
|
|
}
|
|
}
|