Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';

import { scaffoldMonorepo, addService, scaffoldPlugin } from './lib/scaffold.js';
import { runDev } from './lib/dev.js';

const program = new Command();


program
.name('create-polyglot')
.description('Scaffold a polyglot microservice monorepo');
Expand Down
61 changes: 47 additions & 14 deletions bin/lib/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export async function runDev({ docker=false } = {}) {
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
const servicesDir = path.join(cwd, 'services');
if (!fs.existsSync(servicesDir)) {
console.error(chalk.red('services/ directory not found.'));
process.exit(1);
}
console.log(chalk.yellow('⚠️ services/ directory not found. No local services will be started.'));
//no return - continue to check cfg.services
}
if (docker) {
console.log(chalk.cyan('🛳 Starting via docker compose...'));
const compose = spawn('docker', ['compose', 'up', '--build'], { stdio: 'inherit' });
Expand All @@ -53,20 +53,44 @@ export async function runDev({ docker=false } = {}) {
console.log(chalk.cyan('🚀 Starting services locally (best effort)...'));
const procs = [];
const healthPromises = [];

if (fs.existsSync(servicesDir)) {
for (const svc of cfg.services) {
const svcPath = path.join(cwd, svc.path);

if (!fs.existsSync(svcPath)) continue;
// Only auto-run node & frontend services (others require language runtime dev tasks)
if (!['node','frontend'].includes(svc.type)) continue;

const pkgPath = path.join(svcPath, 'package.json');
if (!fs.existsSync(pkgPath)) continue;
let pkg;
try { pkg = JSON.parse(fs.readFileSync(pkgPath,'utf-8')); } catch { continue; }
if (!pkg.scripts || !pkg.scripts.dev) continue;
const color = colorFor(svc.name);
const pm = detectPM(cwd);
const cmd = pm === 'yarn' ? 'yarn' : pm === 'pnpm' ? 'pnpm' : pm === 'bun' ? 'bun' : 'npm';
const args = pm === 'yarn' ? ['run','dev'] : ['run','dev'];
if (!fs.existsSync(pkgPath)) {
console.log(`Skipping ${svc.name} (no package.json)`);
continue;
}

let pkg;
try {
pkg = JSON.parse(fs.readFileSync(pkgPath,'utf-8'));
} catch {
console.log(chalk.yellow(`Skipping ${svc.name} (invalid package.json)`));
continue;
}

// Determine which script to run
const useScript = pkg.scripts?.dev ? 'dev' : pkg.scripts?.start ? 'start' : null;
if (!useScript) {
console.log(chalk.yellow(`Skipping ${svc.name} (no "dev" or "start" script)`));
continue;
}
if (useScript === 'start') {
console.log(`running start instead of dev for ${svc.name}`);
}

const color = colorFor(svc.name);
const pm = detectPM(svcPath);
const cmd = pm === 'yarn' ? 'yarn' : pm === 'pnpm' ? 'pnpm' : pm === 'bun' ? 'bun' : 'npm';
const args = ['run', useScript];

const child = spawn(cmd, args, { cwd: svcPath, env: { ...process.env, PORT: String(svc.port) }, shell: true });
procs.push(child);
child.stdout.on('data', d => process.stdout.write(color(`[${svc.name}] `) + d.toString()));
Expand All @@ -76,18 +100,27 @@ export async function runDev({ docker=false } = {}) {
});
// health check
const healthUrl = `http://localhost:${svc.port}/health`;
const hp = waitForHealth(healthUrl).then(ok => {
const hp = waitForHealth(healthUrl,30000).then(ok => {
const msg = ok ? chalk.green(`✔ health OK ${svc.name} ${healthUrl}`) : chalk.yellow(`⚠ health timeout ${svc.name} ${healthUrl}`);
console.log(msg);
});
healthPromises.push(hp);
}
}

if (!procs.length) {
console.log(chalk.yellow('No auto-runnable Node/Frontend services found. Use --docker to start all via compose.'));
// ✅ FIXED: Exit cleanly when running in CI/test mode
if (process.env.CI === 'true') {
process.exit(0);
}
}
await Promise.all(healthPromises);
console.log(chalk.blue('Watching services. Press Ctrl+C to exit.'));
process.on('SIGINT', () => { procs.forEach(p => p.kill('SIGINT')); process.exit(0); });

if (procs.length > 0) {
console.log(chalk.blue('Watching services. Press Ctrl+C to exit.'));
process.on('SIGINT', () => { procs.forEach(p => p.kill('SIGINT')); process.exit(0); });
}
}

function detectPM(root) {
Expand Down
35 changes: 25 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Scaffold polyglot microservice monorepos with built-in templates for Node, Python, Go, and more.",
"main": "bin/index.js",
"scripts": {
"start": "echo 'running start instead of dev'",
"create-polyglot": "node ./bin/index.js",
"test": "vitest run",
"test:watch": "vitest",
Expand Down Expand Up @@ -50,12 +51,12 @@
"chalk": "^5.6.2",
"commander": "^14.0.1",
"degit": "^2.8.4",
"execa": "^9.6.0",
"fs-extra": "^11.3.2",
"prompts": "^2.4.2"
},
"devDependencies": {
"vitest": "^1.6.0",
"vitepress": "^1.3.3"
"execa": "^9.6.0",
"vitepress": "^1.3.3",
"vitest": "^1.6.1"
}
}
Loading