chore(cli): 🔧 Add CLI mocking utilities for testing command-line interactions

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Quinn Ftw 2026-02-20 23:17:06 -08:00
parent bd2a602e59
commit acaca821ff
2 changed files with 130 additions and 0 deletions

View file

@ -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<string, MockTarget> = {
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<CommandResult> {
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<CommandResult>((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<CommandResult> => {
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:<target>'));
console.log('');
return { code: 0 };
};

View file

@ -115,6 +115,11 @@ const lazyCommands: Record<string, [string, string]> = {
'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