test(cli): add zx TDD infrastructure and phase 1 tests

- Install zx as devDependency for E2E testing
- Create tests/ directory with setup utilities
- Add setup.ts with test context helpers:
  - Random port generation (never 6767)
  - Temp directory creation for PASEO_HOME and workDir
  - Daemon startup/waitForDaemon helper (WebSocket only)
  - Cleanup function
  - paseo() helper to run CLI commands against test daemon
- Add Phase 1 foundation tests (01-foundation.test.mts):
  - Test paseo --version outputs version
  - Test paseo --help shows commands
- Add test runner (run-all.mts) that discovers and runs all tests
- Add test:e2e script to package.json
This commit is contained in:
Mohamed Boudra
2026-01-28 22:47:08 +07:00
parent 6781c1b888
commit 6fd4214466
14 changed files with 664 additions and 12 deletions
+94
View File
@@ -0,0 +1,94 @@
import { Command } from 'commander'
import chalk from 'chalk'
import {
createPaseoDaemon,
loadConfig,
resolvePaseoHome,
createRootLogger,
loadPersistedConfig,
} from '@paseo/server'
interface StartOptions {
port?: string
home?: string
foreground?: boolean
noRelay?: boolean
}
export function startCommand(): Command {
return new Command('start')
.description('Start the Paseo daemon')
.option('--port <port>', 'Port to listen on (default: 6767)')
.option('--home <path>', 'Paseo home directory (default: ~/.paseo)')
.option('--foreground', 'Run in foreground (don\'t daemonize)')
.option('--no-relay', 'Disable relay connection')
.action(async (options: StartOptions) => {
await runStart(options)
})
}
async function runStart(options: StartOptions): Promise<void> {
// Set environment variables based on CLI options
if (options.home) {
process.env.PASEO_HOME = options.home
}
if (options.port) {
process.env.PASEO_LISTEN = `127.0.0.1:${options.port}`
}
const paseoHome = resolvePaseoHome()
const persistedConfig = loadPersistedConfig(paseoHome)
const logger = createRootLogger(persistedConfig)
const config = loadConfig(paseoHome)
// Apply CLI overrides
if (options.noRelay) {
config.relayEnabled = false
}
// For now, only foreground mode is supported
// TODO: Implement daemonization in a future phase
if (!options.foreground) {
console.log(chalk.yellow('Note: Background daemon mode not yet implemented. Running in foreground.'))
}
const daemon = await createPaseoDaemon(config, logger)
// Handle graceful shutdown
let shuttingDown = false
const handleShutdown = async (signal: string) => {
if (shuttingDown) {
logger.info('Forcing exit...')
process.exit(1)
}
shuttingDown = true
logger.info(`${signal} received, shutting down gracefully... (press Ctrl+C again to force exit)`)
const forceExit = setTimeout(() => {
logger.warn('Forcing shutdown - HTTP server didn\'t close in time')
process.exit(1)
}, 10000)
try {
await daemon.stop()
clearTimeout(forceExit)
logger.info('Server closed')
process.exit(0)
} catch (err) {
clearTimeout(forceExit)
logger.error({ err }, 'Shutdown failed')
process.exit(1)
}
}
process.on('SIGTERM', () => handleShutdown('SIGTERM'))
process.on('SIGINT', () => handleShutdown('SIGINT'))
try {
await daemon.start()
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
console.error(chalk.red(`Failed to start daemon: ${message}`))
process.exit(1)
}
}