@@ -4,6 +4,7 @@ import fs from 'fs-extra';
44import path from 'path' ;
55import url from 'url' ;
66import { execa } from 'execa' ;
7+ import { renderServicesTable , printBoxMessage } from './ui.js' ;
78
89const __dirname = path . dirname ( url . fileURLToPath ( import . meta. url ) ) ;
910
@@ -234,10 +235,12 @@ export async function scaffoldMonorepo(projectNameArg, options) {
234235 portMap . set ( s . port , s . name ) ;
235236 }
236237
237- console . log ( chalk . magenta ( '\nSummary:' ) ) ;
238- console . table ( services . map ( s => ( { type : s . type , name : s . name , port : s . port } ) ) ) ;
239- console . log ( `Preset: ${ options . preset || 'none' } ` ) ;
240- console . log ( `Package Manager: ${ options . packageManager } ` ) ;
238+ printBoxMessage ( [
239+ `Project: ${ projectName } ` ,
240+ `Preset: ${ options . preset || 'none' } | Package Manager: ${ options . packageManager } ` ,
241+ 'Selected Services:'
242+ ] , { color : chalk . magenta } ) ;
243+ renderServicesTable ( services . map ( s => ( { ...s , path : `services/${ s . name } ` } ) ) , { title : 'Service Summary' } ) ;
241244 let proceed = true ;
242245 if ( ! nonInteractive ) {
243246 const answer = await prompts ( {
@@ -318,23 +321,24 @@ export async function scaffoldMonorepo(projectNameArg, options) {
318321 console . log ( chalk . green ( `✅ Created ${ svcName } (${ svcType } ) service on port ${ svcPort } ` ) ) ;
319322 }
320323
321- const rootPkgPath = path . join ( projectDir , 'package.json' ) ;
324+ const rootPkgPath = path . join ( projectDir , 'package.json' ) ;
322325 const rootPkg = {
323326 name : projectName ,
324327 private : true ,
325328 version : '0.1.0' ,
326- workspaces : [ 'apps /*' , 'packages/*' ] ,
329+ workspaces : [ 'services /*' , 'packages/*' ] ,
327330 scripts : {
328331 dev : 'node scripts/dev-basic.cjs' ,
329- 'list:services' : 'ls apps ' ,
332+ 'list:services' : 'node scripts/list-services.mjs ' ,
330333 format : 'prettier --write .' ,
331- lint : 'eslint "apps /**/*.{js,jsx,ts,tsx}" --max-warnings 0 || true'
334+ lint : 'eslint "services /**/*.{js,jsx,ts,tsx}" --max-warnings 0 || true'
332335 } ,
333336 devDependencies : {
334337 prettier : '^3.3.3' ,
335338 eslint : '^9.11.1' ,
336339 'eslint-config-prettier' : '^9.1.0' ,
337- 'eslint-plugin-import' : '^2.29.1'
340+ 'eslint-plugin-import' : '^2.29.1' ,
341+ chalk : '^5.6.2'
338342 }
339343 } ;
340344 if ( options . preset === 'turborepo' ) {
@@ -346,9 +350,10 @@ export async function scaffoldMonorepo(projectNameArg, options) {
346350 }
347351 await fs . writeJSON ( rootPkgPath , rootPkg , { spaces : 2 } ) ;
348352
353+ // Always ensure scripts dir exists (needed for list-services script)
354+ const scriptsDir = path . join ( projectDir , 'scripts' ) ;
355+ await fs . mkdirp ( scriptsDir ) ;
349356 if ( ! options . preset ) {
350- const scriptsDir = path . join ( projectDir , 'scripts' ) ;
351- await fs . mkdirp ( scriptsDir ) ;
352357 const runnerSrc = path . join ( __dirname , '../../scripts/dev-basic.cjs' ) ;
353358 try {
354359 if ( await fs . pathExists ( runnerSrc ) ) {
@@ -358,6 +363,9 @@ export async function scaffoldMonorepo(projectNameArg, options) {
358363 console . log ( chalk . yellow ( '⚠️ Failed to copy dev-basic runner:' , e . message ) ) ;
359364 }
360365 }
366+ // Create list-services script with runtime status detection
367+ const listScriptPath = path . join ( scriptsDir , 'list-services.mjs' ) ;
368+ await fs . writeFile ( listScriptPath , `#!/usr/bin/env node\nimport fs from 'fs';\nimport path from 'path';\nimport net from 'net';\nimport chalk from 'chalk';\nconst cwd = process.cwd();\nconst cfgPath = path.join(cwd, 'polyglot.json');\nif(!fs.existsSync(cfgPath)){ console.error(chalk.red('polyglot.json not found.')); process.exit(1);}\nconst cfg = JSON.parse(fs.readFileSync(cfgPath,'utf-8'));\n\nfunction strip(str){return str.replace(/\\x1B\\[[0-9;]*m/g,'');}\nfunction pad(str,w){const raw=strip(str);return str+' '.repeat(Math.max(0,w-raw.length));}\nfunction table(items){ if(!items.length){console.log(chalk.yellow('No services.'));return;} const cols=[{k:'name',h:'Name'},{k:'type',h:'Type'},{k:'port',h:'Port'},{k:'status',h:'Status'},{k:'path',h:'Path'}]; const widths=cols.map(c=>Math.max(c.h.length,...items.map(i=>strip(i[c.k]).length))+2); const top='┌'+widths.map(w=>'─'.repeat(w)).join('┬')+'┐'; const sep='├'+widths.map(w=>'─'.repeat(w)).join('┼')+'┤'; const bot='└'+widths.map(w=>'─'.repeat(w)).join('┴')+'┘'; console.log(top); console.log('│'+cols.map((c,i)=>pad(chalk.bold.white(c.h),widths[i])).join('│')+'│'); console.log(sep); for(const it of items){ console.log('│'+cols.map((c,i)=>pad(it[c.k],widths[i])).join('│')+'│'); } console.log(bot); console.log(chalk.gray('Total: '+items.length)); }\n\nasync function check(port){ return new Promise(res=>{ const sock=net.createConnection({port,host:'127.0.0.1'},()=>{sock.destroy();res(true);}); sock.setTimeout(350,()=>{sock.destroy();res(false);}); sock.on('error',()=>{res(false);});}); }\nconst promises = cfg.services.map(async s=>{ const up = await check(s.port); return { ...s, _up: up }; });\nconst results = await Promise.all(promises);\nconst rows = results.map(s=>({ name: chalk.cyan(s.name), type: colorType(s.type)(s.type), port: chalk.green(String(s.port)), status: s._up ? chalk.bgGreen.black(' UP ') : chalk.bgRed.white(' DOWN '), path: chalk.dim(s.path) }));\nfunction colorType(t){ switch(t){case 'node': return chalk.green; case 'python': return chalk.yellow; case 'go': return chalk.cyan; case 'java': return chalk.red; case 'frontend': return chalk.blue; default: return chalk.white;} }\nif(process.argv.includes('--json')) { console.log(JSON.stringify(results.map(r=>({name:r.name,type:r.type,port:r.port,up:r._up,path:r.path})),null,2)); } else { console.log(chalk.magentaBright('\nWorkspace Services (runtime status)')); table(rows); }\n` ) ;
361369
362370 const readmePath = path . join ( projectDir , 'README.md' ) ;
363371 const svcList = services . map ( s => `- ${ s . name } (${ s . type } ) port:${ s . port } ` ) . join ( '\n' ) ;
@@ -459,14 +467,16 @@ export async function scaffoldMonorepo(projectNameArg, options) {
459467 } ;
460468 await fs . writeJSON ( path . join ( projectDir , 'polyglot.json' ) , polyglotConfig , { spaces : 2 } ) ;
461469
462- console . log ( chalk . blueBright ( '\n🎉 Monorepo setup complete!\n' ) ) ;
463- console . log ( '👉 Next steps:' ) ;
464- console . log ( ` cd ${ projectName } ` ) ;
465- if ( options . noInstall ) console . log ( ` ${ pm } install` ) ;
466- console . log ( ` ${ pm } run list:services` ) ;
467- console . log ( ` ${ pm } run dev` ) ;
468- console . log ( ' docker compose up --build' ) ;
469- console . log ( '\nHappy hacking!\n' ) ;
470+ printBoxMessage ( [
471+ '🎉 Monorepo setup complete!' ,
472+ `cd ${ projectName } ` ,
473+ options . noInstall ? `${ pm } install` : '' ,
474+ `${ pm } run list:services # quick list (fancy table)` ,
475+ `${ pm } run dev # run local node/frontend services` ,
476+ 'docker compose up --build# run all via docker' ,
477+ '' ,
478+ 'Happy hacking!'
479+ ] . filter ( Boolean ) ) ;
470480 } catch ( err ) {
471481 console . error ( chalk . red ( 'Failed to scaffold project:' ) , err ) ;
472482 process . exit ( 1 ) ;
0 commit comments