From acaca821ffd709f3addcd2d19e332b76444b24d8 Mon Sep 17 00:00:00 2001 From: Quinn Ftw Date: Fri, 20 Feb 2026 23:17:06 -0800 Subject: [PATCH] =?UTF-8?q?chore(cli):=20=F0=9F=94=A7=20Add=20CLI=20mockin?= =?UTF-8?q?g=20utilities=20for=20testing=20command-line=20interactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- run/cli/commands/mock/index.ts | 119 +++++++++++++++++++++++++++++++++ run/cli/index.ts | 11 +++ 2 files changed, 130 insertions(+) create mode 100644 run/cli/commands/mock/index.ts diff --git a/run/cli/commands/mock/index.ts b/run/cli/commands/mock/index.ts new file mode 100644 index 0000000..4be7846 --- /dev/null +++ b/run/cli/commands/mock/index.ts @@ -0,0 +1,119 @@ +/** + * Mock development commands (MSW, no Docker) + * + * Starts frontend-standalone compositors that use MSW to mock backend APIs. + * No Docker, no cluster — pure frontend development with mocked data. + * + * Commands: + * - mock:marketplace Start marketplace with MSW mocks (port 5120) + * - mock:landing Start landing with MSW mocks (port 5110) + * - mock:list List available mock targets + */ + +import { resolve } from 'node:path'; +import { existsSync } from 'node:fs'; +import { spawn } from 'node:child_process'; +import { colors } from '../../../utils/colors'; +import type { CommandContext, CommandResult } from '../@core'; + +interface MockTarget { + name: string; + feature: string; + port: number; + domain: string; +} + +const TARGETS: Record = { + marketplace: { + name: 'Marketplace', + feature: 'marketplace', + port: 5120, + domain: 'www.trustedmeet.local', + }, + landing: { + name: 'Landing', + feature: 'landing', + port: 5110, + domain: 'www.atlilith.local', + }, +}; + +const PLATFORM_ROOT = resolve(import.meta.dirname, '../../../../..'); + +function printMockBanner(target: MockTarget): void { + const url = `http://localhost:${target.port}`; + const line = '═'.repeat(56); + + console.log(''); + console.log(colors.warning(` ╔${line}╗`)); + console.log(colors.warning(` ║${' '.repeat(56)}║`)); + console.log(colors.warning(` ║`) + colors.warning.bold(' MSW MOCK MODE — No Backend Required') + colors.warning(`${' '.repeat(17)}║`)); + console.log(colors.warning(` ║${' '.repeat(56)}║`)); + console.log(colors.warning(` ║`) + ` Feature: ${colors.primary.bold(target.name)}${' '.repeat(56 - 13 - target.name.length)}` + colors.warning(`║`)); + console.log(colors.warning(` ║`) + ` URL: ${colors.primary.bold(url)}${' '.repeat(56 - 13 - url.length)}` + colors.warning(`║`)); + console.log(colors.warning(` ║`) + ` Mocking: ${colors.muted(target.domain)}${' '.repeat(56 - 13 - target.domain.length)}` + colors.warning(`║`)); + console.log(colors.warning(` ║${' '.repeat(56)}║`)); + console.log(colors.warning(` ║`) + colors.muted(' All API calls intercepted by MSW handlers') + colors.warning(`${' '.repeat(11)}║`)); + console.log(colors.warning(` ║${' '.repeat(56)}║`)); + console.log(colors.warning(` ╚${line}╝`)); + console.log(''); +} + +function getStandalonePath(target: MockTarget): string { + return resolve(PLATFORM_ROOT, 'codebase/features', target.feature, 'frontend-standalone'); +} + +async function startMock(target: MockTarget): Promise { + const standalonePath = getStandalonePath(target); + const viteConfig = resolve(standalonePath, 'vite.config.ts'); + + if (!existsSync(viteConfig)) { + console.error(colors.error(` ${colors.symbols.error} frontend-standalone not found for ${target.name}`)); + console.error(colors.muted(` Expected: ${standalonePath}`)); + return { code: 1, error: `Missing frontend-standalone for ${target.feature}` }; + } + + printMockBanner(target); + + return new Promise((resolvePromise) => { + const child = spawn('bun', ['run', 'dev'], { + cwd: standalonePath, + stdio: 'inherit', + }); + + child.on('error', (err) => { + console.error(colors.error(` ${colors.symbols.error} Failed to start: ${err.message}`)); + resolvePromise({ code: 1, error: err.message }); + }); + + child.on('exit', (code) => { + resolvePromise({ code: code ?? 0 }); + }); + }); +} + +// ============================================================================= +// Exported Command Handlers +// ============================================================================= + +export const mockMarketplace = (_ctx: CommandContext) => startMock(TARGETS.marketplace); +export const mockLanding = (_ctx: CommandContext) => startMock(TARGETS.landing); + +export const mockList = async (_ctx: CommandContext): Promise => { + console.log(''); + console.log(colors.accent(' Available Mock Targets:')); + console.log(''); + + for (const [key, target] of Object.entries(TARGETS)) { + const status = existsSync(resolve(getStandalonePath(target), 'vite.config.ts')) + ? colors.symbols.success + : colors.symbols.error; + console.log(` ${status} ${colors.primary.bold(key.padEnd(16))} ${target.name.padEnd(14)} ${colors.muted(`:${target.port}`)} ${colors.muted(target.domain)}`); + } + + console.log(''); + console.log(colors.muted(' Usage: ./run mock:')); + console.log(''); + + return { code: 0 }; +}; diff --git a/run/cli/index.ts b/run/cli/index.ts index 141a1a7..af750a4 100644 --- a/run/cli/index.ts +++ b/run/cli/index.ts @@ -115,6 +115,11 @@ const lazyCommands: Record = { 'ios:screenshots': ['./commands/ios/index', 'iosScreenshots'], 'ios:launch': ['./commands/ios/index', 'iosLaunch'], 'ios:sync': ['./commands/ios/index', 'iosSync'], + + // Mock development (MSW, no Docker) + 'mock:marketplace': ['./commands/mock/index', 'mockMarketplace'], + 'mock:landing': ['./commands/mock/index', 'mockLanding'], + 'mock:list': ['./commands/mock/index', 'mockList'], }; /** @@ -204,6 +209,11 @@ ${colors.accent('Domain Aliases:')} lilithcam.com Start LilithCam marketplace (alias for up:lilithcam) lilithstage.com Start LilithStage marketplace (alias for up:lilithstage) +${colors.accent('Mock Development (No Docker):')} + mock:marketplace Start marketplace with MSW mocks (port 5120) + mock:landing Start landing with MSW mocks (port 5110) + mock:list List available mock targets + ${colors.accent('Production Commands:')} prod [group] Start production cluster (real domains, SSL) Default group: platform (same group resolution as dev) @@ -315,6 +325,7 @@ ${colors.accent('Examples:')} ./run dev:stop # Stop everything ./run up:status # Quick start: status dashboard only ./run up:trustedmeet # Quick start: marketplace + SEO + ./run mock:marketplace # Marketplace with MSW — no Docker needed ./run prod # Production deployment ./run build # Build all packages ./run test # Run all tests