diff --git a/plugins/cli/src/commands/ui.ts b/plugins/cli/src/commands/ui.ts index 15a669b..a663419 100644 --- a/plugins/cli/src/commands/ui.ts +++ b/plugins/cli/src/commands/ui.ts @@ -14,9 +14,11 @@ export const ui = new Command('ui') .description('Start the Crawlith UI Dashboard') .option('--site ', 'Site URL to display in dashboard', 'https://example.com') .option('--port ', 'Port to run server on', '23484') + .option('--host
', 'Host to bind server to', '127.0.0.1') .action(async (options) => { try { const port = parseInt(options.port, 10); + const host = options.host; const siteUrl = options.site; console.log(chalk.bold.cyan(`\nšŸš€ Starting Crawlith UI`)); @@ -29,11 +31,13 @@ export const ui = new Command('ui') await startServer({ port, + host, staticPath: distPath, siteName: siteUrl }); - const url = `http://localhost:${port}`; + const displayHost = host === '0.0.0.0' ? 'localhost' : host; + const url = `http://${displayHost}:${port}`; console.log(chalk.green(`\nāœ… Dashboard ready at: ${chalk.underline(url)}`)); console.log(chalk.gray(' Press Ctrl+C to stop.')); diff --git a/plugins/server/package.json b/plugins/server/package.json index f795087..0d43cf8 100644 --- a/plugins/server/package.json +++ b/plugins/server/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "build": "tsc" + "build": "tsc", + "test": "vitest run" }, "dependencies": { "express": "^4.19.2", diff --git a/plugins/server/src/index.js b/plugins/server/src/index.js deleted file mode 100644 index a2622df..0000000 --- a/plugins/server/src/index.js +++ /dev/null @@ -1,42 +0,0 @@ -/* global console, process */ -import express from 'express'; -import path from 'path'; -import chalk from 'chalk'; -export function startServer(options) { - return new Promise((resolve, reject) => { - const { port, staticPath, siteName } = options; - const resolvedStaticPath = path.resolve(staticPath); - const app = express(); - // Serve static files - app.use(express.static(resolvedStaticPath)); - // SPA fallback - app.get('*', (req, res) => { - res.sendFile(path.join(resolvedStaticPath, 'index.html')); - }); - const server = app.listen(port, () => { - console.log(chalk.green(`\nāœ… Crawlith UI Server started at http://localhost:${port}`)); - if (siteName) { - console.log(chalk.gray(` Viewing site: ${siteName}`)); - } - resolve(); - }); - server.on('error', (err) => { - if (err.code === 'EADDRINUSE') { - console.error(chalk.red(`āŒ Port ${port} is already in use.`)); - reject(err); - } - else { - reject(err); - } - }); - const shutdown = () => { - console.log(chalk.yellow('\nShutting down server...')); - server.close(() => { - console.log(chalk.green('Server stopped.')); - process.exit(0); - }); - }; - process.on('SIGINT', shutdown); - process.on('SIGTERM', shutdown); - }); -} diff --git a/plugins/server/src/index.ts b/plugins/server/src/index.ts index 9aba783..fd5a7a8 100644 --- a/plugins/server/src/index.ts +++ b/plugins/server/src/index.ts @@ -4,13 +4,14 @@ import chalk from 'chalk'; export interface ServerOptions { port: number; + host?: string; staticPath: string; siteName?: string; } export function startServer(options: ServerOptions): Promise { return new Promise((resolve, reject) => { - const { port, staticPath, siteName } = options; + const { port, host = '127.0.0.1', staticPath, siteName } = options; const resolvedStaticPath = path.resolve(staticPath); const app = express(); @@ -23,8 +24,9 @@ export function startServer(options: ServerOptions): Promise { res.sendFile(path.join(resolvedStaticPath, 'index.html')); }); - const server = app.listen(port, () => { - console.log(chalk.green(`\nāœ… Crawlith UI Server started at http://localhost:${port}`)); + const server = app.listen(port, host, () => { + const displayHost = host === '0.0.0.0' ? 'localhost' : host; + console.log(chalk.green(`\nāœ… Crawlith UI Server started at http://${displayHost}:${port}`)); if (siteName) { console.log(chalk.gray(` Viewing site: ${siteName}`)); } diff --git a/plugins/server/tests/server.test.ts b/plugins/server/tests/server.test.ts new file mode 100644 index 0000000..e2bbf91 --- /dev/null +++ b/plugins/server/tests/server.test.ts @@ -0,0 +1,59 @@ +import { test, expect, vi, beforeEach } from 'vitest'; +import express from 'express'; +import { startServer } from '../src/index.js'; + +vi.mock('express', () => { + const mockApp = { + use: vi.fn(), + get: vi.fn(), + listen: vi.fn((port, host, callback) => { + if (typeof host === 'function') { + // Handle case where host is omitted and callback is second arg + host(); + } else if (callback) { + callback(); + } + return { + on: vi.fn(), + close: vi.fn((cb) => cb && cb()) + }; + }) + }; + const expressMock: any = vi.fn(() => mockApp); + expressMock.static = vi.fn(); + return { + default: expressMock + }; +}); + +vi.mock('node:path', () => ({ + default: { + resolve: vi.fn((p) => p), + join: vi.fn((...args) => args.join('/')) + } +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +test('startServer uses default host 127.0.0.1', async () => { + const mockApp = express(); + await startServer({ + port: 3000, + staticPath: './static' + }); + + expect(mockApp.listen).toHaveBeenCalledWith(3000, '127.0.0.1', expect.any(Function)); +}); + +test('startServer uses provided host', async () => { + const mockApp = express(); + await startServer({ + port: 3000, + host: '0.0.0.0', + staticPath: './static' + }); + + expect(mockApp.listen).toHaveBeenCalledWith(3000, '0.0.0.0', expect.any(Function)); +});