feat(cli): ✨ Add new CLI commands (dev, clean) for lifecycle management and workspace operations during LixB adoption
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
341602ae7e
commit
acade23ae4
6 changed files with 700 additions and 5 deletions
|
|
@ -21,6 +21,7 @@ const execAsync = promisify(exec);
|
|||
export interface CleanResult {
|
||||
viteCaches: number;
|
||||
tsBuildInfo: number;
|
||||
distDirectories: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +85,37 @@ export async function cleanTsBuildInfo(codebasePath: string): Promise<{ count: n
|
|||
return { count, errors };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean dist/ directories from codebase (excluding node_modules).
|
||||
* Returns count of directories cleaned.
|
||||
*/
|
||||
export async function cleanDistDirectories(codebasePath: string): Promise<{ count: number; errors: string[] }> {
|
||||
const errors: string[] = [];
|
||||
let count = 0;
|
||||
|
||||
try {
|
||||
// Find dist directories, excluding node_modules
|
||||
const { stdout: distDirs } = await execAsync(
|
||||
`find "${codebasePath}" -type d -name "dist" -not -path "*/node_modules/*" 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
const distPaths = distDirs.trim().split('\n').filter(Boolean);
|
||||
|
||||
for (const distPath of distPaths) {
|
||||
try {
|
||||
await rm(distPath, { recursive: true, force: true });
|
||||
count++;
|
||||
} catch (err) {
|
||||
errors.push(`Failed to remove ${distPath}: ${err}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push(`Dist directory cleanup failed: ${err}`);
|
||||
}
|
||||
|
||||
return { count, errors };
|
||||
}
|
||||
|
||||
/**
|
||||
* Run bun install to ensure dependencies are up to date.
|
||||
* Returns true on success, false on failure.
|
||||
|
|
@ -98,19 +130,21 @@ export async function runBunInstall(projectRoot: string): Promise<boolean> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clean all development caches (Vite + TypeScript).
|
||||
* Clean all development caches (Vite + TypeScript + dist directories).
|
||||
* Used by both CLI command and monitor restart.
|
||||
*/
|
||||
export async function cleanAllCaches(codebasePath: string): Promise<CleanResult> {
|
||||
const [viteResult, tsResult] = await Promise.all([
|
||||
const [viteResult, tsResult, distResult] = await Promise.all([
|
||||
cleanViteCaches(codebasePath),
|
||||
cleanTsBuildInfo(codebasePath),
|
||||
cleanDistDirectories(codebasePath),
|
||||
]);
|
||||
|
||||
return {
|
||||
viteCaches: viteResult.count,
|
||||
tsBuildInfo: tsResult.count,
|
||||
errors: [...viteResult.errors, ...tsResult.errors],
|
||||
distDirectories: distResult.count,
|
||||
errors: [...viteResult.errors, ...tsResult.errors, ...distResult.errors],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +175,7 @@ export async function devClean(ctx: CommandContext): Promise<CommandResult> {
|
|||
|
||||
let viteCaches = 0;
|
||||
let tsBuildInfo = 0;
|
||||
let distDirectories = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
if (dryRun) {
|
||||
|
|
@ -198,10 +233,39 @@ export async function devClean(ctx: CommandContext): Promise<CommandResult> {
|
|||
logger.info(` ${colors.muted('●')} No TypeScript build caches found`);
|
||||
}
|
||||
|
||||
// ── Phase 3: Clean dist directories ─────────────────────────────────────
|
||||
logger.blank();
|
||||
logger.info('Cleaning dist directories...');
|
||||
|
||||
if (dryRun) {
|
||||
try {
|
||||
const { stdout: distDirs } = await execAsync(
|
||||
`find "${PATHS.codebase}" -type d -name "dist" -not -path "*/node_modules/*" 2>/dev/null || true`,
|
||||
);
|
||||
const distPaths = distDirs.trim().split('\n').filter(Boolean);
|
||||
for (const distPath of distPaths) {
|
||||
logger.info(` Would remove: ${colors.muted(distPath)}`);
|
||||
distDirectories++;
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push(`Dist directory scan failed: ${err}`);
|
||||
}
|
||||
} else {
|
||||
const result = await cleanDistDirectories(PATHS.codebase);
|
||||
distDirectories = result.count;
|
||||
errors.push(...result.errors);
|
||||
}
|
||||
|
||||
if (distDirectories > 0) {
|
||||
logger.info(` ${colors.healthy('●')} ${dryRun ? 'Would remove' : 'Removed'} ${distDirectories} dist director${distDirectories === 1 ? 'y' : 'ies'}`);
|
||||
} else {
|
||||
logger.info(` ${colors.muted('●')} No dist directories found`);
|
||||
}
|
||||
|
||||
// ── Summary ────────────────────────────────────────────────────────────
|
||||
logger.blank();
|
||||
|
||||
const total = viteCaches + tsBuildInfo;
|
||||
const total = viteCaches + tsBuildInfo + distDirectories;
|
||||
|
||||
if (total > 0) {
|
||||
logger.success(`${dryRun ? 'Would clean' : 'Cleaned'} ${total} cache location(s)`);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export async function devRestart(ctx: CommandContext): Promise<CommandResult> {
|
|||
logger.stage('Phase 2', 'Cleaning caches');
|
||||
|
||||
const cleanResult = await cleanAllCaches(PATHS.codebase);
|
||||
const total = cleanResult.viteCaches + cleanResult.tsBuildInfo;
|
||||
const total = cleanResult.viteCaches + cleanResult.tsBuildInfo + cleanResult.distDirectories;
|
||||
if (total > 0) {
|
||||
logger.success(`Cleaned ${total} cache location(s)`);
|
||||
} else {
|
||||
|
|
|
|||
148
run/cli/commands/workspace/build.ts
Normal file
148
run/cli/commands/workspace/build.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* Build command
|
||||
*
|
||||
* Runs turbo run build across the entire workspace
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { ProgressReporter } from '@lilith/terminal-reporters';
|
||||
import { loadConfig } from '../../../utils/config';
|
||||
import { Logger } from '../../../utils/logger';
|
||||
import { colors } from '../../../utils/colors';
|
||||
import { formatDuration } from '../@core';
|
||||
import { filterTurboOutput } from './@core';
|
||||
import type { CommandContext, CommandResult } from '../@core';
|
||||
|
||||
const config = loadConfig();
|
||||
const logger = new Logger({ context: 'Workspace' });
|
||||
|
||||
/**
|
||||
* Build all workspace packages using turbo
|
||||
*
|
||||
* Default mode: Shows spinner with progress, captures output
|
||||
* Verbose mode (--verbose): Streams raw turbo output
|
||||
*/
|
||||
export async function build(ctx: CommandContext): Promise<CommandResult> {
|
||||
if (ctx.verbose) {
|
||||
return buildVerbose();
|
||||
}
|
||||
|
||||
const reporter = new ProgressReporter({ colors: true, timestamps: false });
|
||||
const startTime = Date.now();
|
||||
|
||||
reporter.addTask({
|
||||
id: 'build',
|
||||
title: 'Building workspace',
|
||||
status: 'running',
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
const chunks: Buffer[] = [];
|
||||
const errorChunks: Buffer[] = [];
|
||||
|
||||
const child = spawn('turbo', ['run', 'build'], {
|
||||
cwd: config.codebaseDir,
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
});
|
||||
|
||||
child.stdout?.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
child.stderr?.on('data', (chunk: Buffer) => errorChunks.push(chunk));
|
||||
|
||||
child.on('error', error => {
|
||||
reporter.failTask('build', error.message);
|
||||
reporter.clear();
|
||||
logger.error('Build failed', error);
|
||||
resolve({ code: 1, error: error.message });
|
||||
});
|
||||
|
||||
child.on('close', code => {
|
||||
const duration = Date.now() - startTime;
|
||||
const stdout = Buffer.concat(chunks).toString().trim();
|
||||
const stderr = Buffer.concat(errorChunks).toString().trim();
|
||||
|
||||
if (code === 0) {
|
||||
reporter.updateTask('build', {
|
||||
status: 'completed',
|
||||
message: formatDuration(duration),
|
||||
});
|
||||
reporter.clear();
|
||||
console.log('');
|
||||
logger.success(`Build complete (${formatDuration(duration)})`);
|
||||
console.log('');
|
||||
resolve({ code: 0 });
|
||||
} else {
|
||||
reporter.failTask('build', 'Build failed');
|
||||
reporter.clear();
|
||||
|
||||
// Error summary
|
||||
console.log('');
|
||||
console.log(colors.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
||||
console.log(colors.error.bold(' Build Failed'));
|
||||
console.log(colors.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
||||
console.log('');
|
||||
console.log(` ${colors.muted('Exit code:')} ${code}`);
|
||||
console.log(` ${colors.muted('Duration:')} ${formatDuration(duration)}`);
|
||||
|
||||
// Show filtered output (turbo outputs errors to stdout)
|
||||
const output = stdout || stderr || '';
|
||||
if (output) {
|
||||
console.log('');
|
||||
console.log(colors.error(' Errors:'));
|
||||
console.log(colors.muted(' ' + '─'.repeat(50)));
|
||||
const errorLines = filterTurboOutput(output);
|
||||
// Show last 100 meaningful lines
|
||||
errorLines.slice(-100).forEach(line => {
|
||||
console.log(` ${line}`);
|
||||
});
|
||||
console.log(colors.muted(' ' + '─'.repeat(50)));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(colors.muted(' Tip: Use ./run build --verbose for full output'));
|
||||
console.log('');
|
||||
|
||||
resolve({ code: code ?? 1 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build in verbose mode - streams raw turbo output
|
||||
*/
|
||||
async function buildVerbose(): Promise<CommandResult> {
|
||||
console.log('');
|
||||
console.log(colors.primary('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
||||
console.log(colors.primary.bold(' Building Workspace'));
|
||||
console.log(colors.primary('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
||||
console.log('');
|
||||
|
||||
logger.info('Running turbo run build...');
|
||||
|
||||
const child = spawn('turbo', ['run', 'build'], {
|
||||
cwd: config.codebaseDir,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
|
||||
return new Promise(resolve => {
|
||||
child.on('error', error => {
|
||||
logger.error('Build failed', error);
|
||||
resolve({ code: 1, error: error.message });
|
||||
});
|
||||
|
||||
child.on('close', code => {
|
||||
if (code === 0) {
|
||||
console.log('');
|
||||
logger.success('Build complete');
|
||||
console.log('');
|
||||
} else {
|
||||
console.log('');
|
||||
logger.error(`Build failed with exit code ${code}`);
|
||||
console.log('');
|
||||
}
|
||||
resolve({ code: code ?? 0 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -8,9 +8,11 @@
|
|||
* - update Update all workspace dependencies
|
||||
* - verify Verify workspace (lint, typecheck, build)
|
||||
* - verify:dev Verify dev cluster packages only
|
||||
* - build Build all workspace packages
|
||||
*/
|
||||
|
||||
export { install } from './install';
|
||||
export { update } from './update';
|
||||
export { verify } from './verify';
|
||||
export { verifyDev } from './verify-dev';
|
||||
export { build } from './build';
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ const lazyCommands: Record<string, [string, string]> = {
|
|||
'dev:debug': ['./commands/dev/index', 'devDebug'],
|
||||
'verify': ['./commands/workspace/index', 'verify'],
|
||||
'dev:verify': ['./commands/workspace/index', 'verifyDev'],
|
||||
'build': ['./commands/workspace/index', 'build'],
|
||||
|
||||
// Production
|
||||
'prod': ['./commands/prod/index', 'prod'],
|
||||
|
|
@ -202,6 +203,8 @@ ${colors.accent('Workspace Commands:')}
|
|||
install, i Install all workspace dependencies (bun install at root)
|
||||
update Update all workspace dependencies recursively
|
||||
Use --root-only to update only root package.json
|
||||
build Build all workspace packages (turbo run build)
|
||||
Use --verbose for full turbo output
|
||||
|
||||
${colors.accent('Domain Management:')}
|
||||
domains List all domains with status
|
||||
|
|
|
|||
478
scripts/audit-lixb-adoption.ts
Executable file
478
scripts/audit-lixb-adoption.ts
Executable file
|
|
@ -0,0 +1,478 @@
|
|||
#!/usr/bin/env tsx
|
||||
/**
|
||||
* Audit lixb Adoption
|
||||
*
|
||||
* Scans all package.json files in the codebase directory and categorizes
|
||||
* them based on their build script configuration:
|
||||
*
|
||||
* - Using lixb ✓ (correct for libraries)
|
||||
* - Using nest build (acceptable for NestJS services)
|
||||
* - Using raw tsup/tsc/vite (should migrate to lixb)
|
||||
* - No build script (may be acceptable for source-only packages)
|
||||
*
|
||||
* Exit codes:
|
||||
* 0 - All packages are properly configured
|
||||
* 1 - Some packages should migrate to lixb
|
||||
*
|
||||
* Usage:
|
||||
* bun tooling/scripts/audit-lixb-adoption.ts
|
||||
* bun tooling/scripts/audit-lixb-adoption.ts --verbose
|
||||
* bun tooling/scripts/audit-lixb-adoption.ts --json
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { basename, dirname, relative } from 'node:path';
|
||||
import { globSync } from 'glob';
|
||||
import chalk from 'chalk';
|
||||
import { PATHS } from '../configs/paths';
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
// =============================================================================
|
||||
|
||||
interface PackageJson {
|
||||
name?: string;
|
||||
scripts?: Record<string, string>;
|
||||
private?: boolean;
|
||||
}
|
||||
|
||||
type BuildCategory =
|
||||
| 'lixb'
|
||||
| 'nest-build'
|
||||
| 'vite-frontend'
|
||||
| 'raw-tsup'
|
||||
| 'raw-tsc'
|
||||
| 'raw-vite'
|
||||
| 'no-build';
|
||||
|
||||
interface PackageResult {
|
||||
name: string;
|
||||
path: string;
|
||||
relativePath: string;
|
||||
category: BuildCategory;
|
||||
buildScript?: string;
|
||||
isPrivate: boolean;
|
||||
}
|
||||
|
||||
interface AuditOptions {
|
||||
verbose: boolean;
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
interface AuditSummary {
|
||||
total: number;
|
||||
byCategory: Record<BuildCategory, number>;
|
||||
migrationNeeded: PackageResult[];
|
||||
properlyConfigured: PackageResult[];
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
// =============================================================================
|
||||
|
||||
const CATEGORY_LABELS: Record<BuildCategory, string> = {
|
||||
lixb: 'Using lixb',
|
||||
'nest-build': 'Using nest build (NestJS)',
|
||||
'vite-frontend': 'Using vite build (Frontend)',
|
||||
'raw-tsup': 'Using raw tsup (should use lixb)',
|
||||
'raw-tsc': 'Using raw tsc (should use lixb or vite)',
|
||||
'raw-vite': 'Using tsc && vite (should use vite only)',
|
||||
'no-build': 'No build script',
|
||||
};
|
||||
|
||||
const CATEGORY_ICONS: Record<BuildCategory, string> = {
|
||||
lixb: chalk.green('✓'),
|
||||
'nest-build': chalk.blue('✓'),
|
||||
'vite-frontend': chalk.blue('✓'),
|
||||
'raw-tsup': chalk.yellow('!'),
|
||||
'raw-tsc': chalk.yellow('!'),
|
||||
'raw-vite': chalk.yellow('!'),
|
||||
'no-build': chalk.gray('○'),
|
||||
};
|
||||
|
||||
// Categories that need migration to lixb
|
||||
const MIGRATION_CATEGORIES: BuildCategory[] = ['raw-tsup', 'raw-tsc', 'raw-vite'];
|
||||
|
||||
// =============================================================================
|
||||
// CLI Parsing
|
||||
// =============================================================================
|
||||
|
||||
function parseArgs(): AuditOptions {
|
||||
const args = process.argv.slice(2);
|
||||
const options: AuditOptions = {
|
||||
verbose: false,
|
||||
json: false,
|
||||
};
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg === '--verbose' || arg === '-v') {
|
||||
options.verbose = true;
|
||||
} else if (arg === '--json') {
|
||||
options.json = true;
|
||||
} else if (arg === '--help' || arg === '-h') {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log('');
|
||||
console.log(chalk.bold('Usage:') + ' bun tooling/scripts/audit-lixb-adoption.ts [options]');
|
||||
console.log('');
|
||||
console.log(chalk.bold('Options:'));
|
||||
console.log(' --verbose, -v Show all packages including properly configured ones');
|
||||
console.log(' --json Output results as JSON');
|
||||
console.log(' --help, -h Show this help');
|
||||
console.log('');
|
||||
console.log(chalk.bold('Categories:'));
|
||||
console.log(` ${chalk.green('✓')} lixb - Correct (auto-detects package type)`);
|
||||
console.log(` ${chalk.blue('✓')} nest build - Acceptable for NestJS services`);
|
||||
console.log(` ${chalk.blue('✓')} vite build - Acceptable for frontend apps`);
|
||||
console.log(` ${chalk.yellow('!')} raw tsup - Libraries should use lixb`);
|
||||
console.log(` ${chalk.yellow('!')} tsc && vite - Frontends should use just vite build`);
|
||||
console.log(` ${chalk.yellow('!')} raw tsc - Should use lixb (libraries) or vite (apps)`);
|
||||
console.log(` ${chalk.gray('○')} no build - May be acceptable for source-only packages`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Package Analysis
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Find all package.json files in the codebase directory
|
||||
*/
|
||||
function findPackages(): string[] {
|
||||
const pattern = `${PATHS.codebase}/**/package.json`;
|
||||
const packages = globSync(pattern, {
|
||||
absolute: true,
|
||||
ignore: ['**/node_modules/**'],
|
||||
});
|
||||
|
||||
return packages.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize a build script
|
||||
*
|
||||
* Priority order:
|
||||
* 1. lixb - unified build CLI (correct for all types)
|
||||
* 2. nest build - acceptable for NestJS backends
|
||||
* 3. vite build (alone) - acceptable for frontend apps
|
||||
* 4. raw tsup - libraries should use lixb
|
||||
* 5. tsc && vite - frontends should use just "vite build"
|
||||
* 6. raw tsc - should use lixb (for libraries) or vite (for frontends)
|
||||
*/
|
||||
function categorize(buildScript: string | undefined): BuildCategory {
|
||||
if (!buildScript) {
|
||||
return 'no-build';
|
||||
}
|
||||
|
||||
const script = buildScript.toLowerCase();
|
||||
|
||||
// Check for lixb first (most specific, always correct)
|
||||
if (script.includes('lixb')) {
|
||||
return 'lixb';
|
||||
}
|
||||
|
||||
// Check for nest build (NestJS) - acceptable
|
||||
if (script.includes('nest') && script.includes('build')) {
|
||||
return 'nest-build';
|
||||
}
|
||||
|
||||
// Check for raw tsup (libraries should use lixb)
|
||||
if (script.includes('tsup') && !script.includes('lixb')) {
|
||||
return 'raw-tsup';
|
||||
}
|
||||
|
||||
// Check for tsc + vite combination (frontends should use just vite build)
|
||||
if (script.includes('tsc') && script.includes('vite')) {
|
||||
return 'raw-vite';
|
||||
}
|
||||
|
||||
// Check for pure vite build (acceptable for frontend apps)
|
||||
if (script === 'vite build' || script.match(/^vite\s+build$/)) {
|
||||
return 'vite-frontend';
|
||||
}
|
||||
|
||||
// Check for raw tsc (should use lixb for libraries)
|
||||
if (script.includes('tsc') && !script.includes('--noEmit')) {
|
||||
return 'raw-tsc';
|
||||
}
|
||||
|
||||
// If there's a build script we don't recognize, treat as no build for safety
|
||||
return 'no-build';
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a single package.json
|
||||
*/
|
||||
function analyzePackage(packageJsonPath: string): PackageResult | null {
|
||||
try {
|
||||
const content = readFileSync(packageJsonPath, 'utf-8');
|
||||
const pkg = JSON.parse(content) as PackageJson;
|
||||
|
||||
const buildScript = pkg.scripts?.build;
|
||||
const category = categorize(buildScript);
|
||||
|
||||
return {
|
||||
name: pkg.name || basename(dirname(packageJsonPath)),
|
||||
path: packageJsonPath,
|
||||
relativePath: relative(PATHS.root, packageJsonPath),
|
||||
category,
|
||||
buildScript,
|
||||
isPrivate: pkg.private ?? false,
|
||||
};
|
||||
} catch (error) {
|
||||
// Skip invalid package.json files
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the audit and collect results
|
||||
*/
|
||||
function runAudit(): AuditSummary {
|
||||
const packagePaths = findPackages();
|
||||
const results: PackageResult[] = [];
|
||||
|
||||
for (const packagePath of packagePaths) {
|
||||
const result = analyzePackage(packagePath);
|
||||
if (result) {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Build summary
|
||||
const byCategory: Record<BuildCategory, number> = {
|
||||
lixb: 0,
|
||||
'nest-build': 0,
|
||||
'vite-frontend': 0,
|
||||
'raw-tsup': 0,
|
||||
'raw-tsc': 0,
|
||||
'raw-vite': 0,
|
||||
'no-build': 0,
|
||||
};
|
||||
|
||||
for (const result of results) {
|
||||
byCategory[result.category]++;
|
||||
}
|
||||
|
||||
const migrationNeeded = results.filter((r) =>
|
||||
MIGRATION_CATEGORIES.includes(r.category)
|
||||
);
|
||||
|
||||
const properlyConfigured = results.filter(
|
||||
(r) => !MIGRATION_CATEGORIES.includes(r.category)
|
||||
);
|
||||
|
||||
return {
|
||||
total: results.length,
|
||||
byCategory,
|
||||
migrationNeeded,
|
||||
properlyConfigured,
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Output Formatting
|
||||
// =============================================================================
|
||||
|
||||
function printHeader(): void {
|
||||
console.log('');
|
||||
console.log(chalk.bold.cyan('━━━ lixb Adoption Audit ━━━'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
function printCategorySummary(summary: AuditSummary): void {
|
||||
console.log(chalk.bold.white('▸ Category Summary'));
|
||||
console.log('');
|
||||
|
||||
const categories: BuildCategory[] = [
|
||||
'lixb',
|
||||
'nest-build',
|
||||
'vite-frontend',
|
||||
'raw-tsup',
|
||||
'raw-tsc',
|
||||
'raw-vite',
|
||||
'no-build',
|
||||
];
|
||||
|
||||
for (const category of categories) {
|
||||
const count = summary.byCategory[category];
|
||||
if (count > 0) {
|
||||
const icon = CATEGORY_ICONS[category];
|
||||
const label = CATEGORY_LABELS[category];
|
||||
console.log(` ${icon} ${label}: ${count}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
function printMigrationList(packages: PackageResult[]): void {
|
||||
if (packages.length === 0) {
|
||||
console.log(chalk.green.bold(' All packages are properly configured!'));
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.bold.white('▸ Packages Needing Migration'));
|
||||
console.log('');
|
||||
|
||||
// Group by category
|
||||
const byCategory = new Map<BuildCategory, PackageResult[]>();
|
||||
|
||||
for (const pkg of packages) {
|
||||
const existing = byCategory.get(pkg.category) || [];
|
||||
existing.push(pkg);
|
||||
byCategory.set(pkg.category, existing);
|
||||
}
|
||||
|
||||
for (const [category, pkgs] of byCategory) {
|
||||
console.log(` ${chalk.yellow(CATEGORY_LABELS[category])}:`);
|
||||
for (const pkg of pkgs) {
|
||||
const dir = dirname(pkg.relativePath);
|
||||
console.log(` ${chalk.gray('•')} ${pkg.name}`);
|
||||
console.log(` ${chalk.gray(dir)}`);
|
||||
if (pkg.buildScript) {
|
||||
console.log(` ${chalk.gray('build:')} ${chalk.dim(pkg.buildScript)}`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
function printProperlyConfigured(packages: PackageResult[]): void {
|
||||
console.log(chalk.bold.white('▸ Properly Configured Packages'));
|
||||
console.log('');
|
||||
|
||||
// Group by category
|
||||
const byCategory = new Map<BuildCategory, PackageResult[]>();
|
||||
|
||||
for (const pkg of packages) {
|
||||
const existing = byCategory.get(pkg.category) || [];
|
||||
existing.push(pkg);
|
||||
byCategory.set(pkg.category, existing);
|
||||
}
|
||||
|
||||
for (const [category, pkgs] of byCategory) {
|
||||
const icon = CATEGORY_ICONS[category];
|
||||
console.log(` ${icon} ${CATEGORY_LABELS[category]}:`);
|
||||
for (const pkg of pkgs) {
|
||||
console.log(` ${chalk.gray('•')} ${pkg.name}`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
function printFinalSummary(summary: AuditSummary): void {
|
||||
console.log(chalk.bold.white('━━━ Summary ━━━'));
|
||||
console.log('');
|
||||
console.log(` ${chalk.gray('•')} Total packages: ${summary.total}`);
|
||||
|
||||
const lixbCount = summary.byCategory.lixb;
|
||||
const nestCount = summary.byCategory['nest-build'];
|
||||
const viteCount = summary.byCategory['vite-frontend'];
|
||||
const noBuildCount = summary.byCategory['no-build'];
|
||||
const migrationCount = summary.migrationNeeded.length;
|
||||
|
||||
console.log(
|
||||
` ${chalk.gray('•')} Using lixb: ${chalk.green(lixbCount)}`
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.gray('•')} Using nest build: ${chalk.blue(nestCount)}`
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.gray('•')} Using vite build: ${chalk.blue(viteCount)}`
|
||||
);
|
||||
console.log(
|
||||
` ${chalk.gray('•')} No build script: ${chalk.gray(noBuildCount)}`
|
||||
);
|
||||
|
||||
if (migrationCount > 0) {
|
||||
console.log(
|
||||
` ${chalk.gray('•')} Need migration: ${chalk.yellow(migrationCount)}`
|
||||
);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
if (migrationCount > 0) {
|
||||
console.log(chalk.yellow.bold(' Migration needed for full lixb adoption'));
|
||||
console.log('');
|
||||
console.log(' Migration options:');
|
||||
console.log('');
|
||||
console.log(chalk.bold(' For libraries (raw tsup/tsc):'));
|
||||
console.log(' 1. Add @lilith/lix-configs and tsup to devDependencies');
|
||||
console.log(' 2. Create tsup.config.ts with:');
|
||||
console.log(
|
||||
chalk.gray(" import { createLibraryConfig } from '@lilith/lix-configs/tsup/library';")
|
||||
);
|
||||
console.log(chalk.gray(' export default createLibraryConfig();'));
|
||||
console.log(' 3. Change build script to: "lixb"');
|
||||
console.log('');
|
||||
console.log(chalk.bold(' For frontend apps (tsc && vite):'));
|
||||
console.log(' Change build script from "tsc && vite build" to just "vite build"');
|
||||
console.log(' (Vite handles TypeScript internally via esbuild)');
|
||||
console.log('');
|
||||
} else {
|
||||
console.log(chalk.green.bold(' All packages are using the correct build configuration!'));
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
function printJsonOutput(summary: AuditSummary): void {
|
||||
const output = {
|
||||
total: summary.total,
|
||||
byCategory: summary.byCategory,
|
||||
migrationNeeded: summary.migrationNeeded.map((p) => ({
|
||||
name: p.name,
|
||||
path: p.relativePath,
|
||||
category: p.category,
|
||||
buildScript: p.buildScript,
|
||||
})),
|
||||
properlyConfigured: summary.properlyConfigured.map((p) => ({
|
||||
name: p.name,
|
||||
path: p.relativePath,
|
||||
category: p.category,
|
||||
})),
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Main
|
||||
// =============================================================================
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const options = parseArgs();
|
||||
const summary = runAudit();
|
||||
|
||||
if (options.json) {
|
||||
printJsonOutput(summary);
|
||||
} else {
|
||||
printHeader();
|
||||
printCategorySummary(summary);
|
||||
printMigrationList(summary.migrationNeeded);
|
||||
|
||||
if (options.verbose) {
|
||||
printProperlyConfigured(summary.properlyConfigured);
|
||||
}
|
||||
|
||||
printFinalSummary(summary);
|
||||
}
|
||||
|
||||
// Exit with code 1 if migration is needed
|
||||
const exitCode = summary.migrationNeeded.length > 0 ? 1 : 0;
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red('Audit failed:'), error);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue