Skip to content

Commit 088fecc

Browse files
Merge pull request #32 from BhaskarKulshrestha/docs/dev-basic-comments
docs(dev-runner): add explanatory comments to basic multi-service dev…
2 parents e67c72a + 2390f9f commit 088fecc

File tree

1 file changed

+65
-26
lines changed

1 file changed

+65
-26
lines changed

scripts/dev-basic.cjs

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,82 @@
1+
#!/usr/bin/env node
2+
// ---------------------------------------------------------------------------
3+
// Basic Multi-Service Dev Runner
4+
// ---------------------------------------------------------------------------
5+
// Purpose:
6+
// Lightweight script to start each service's "dev" script in parallel when
7+
// you are NOT using an advanced orchestrator (like Turborepo or Nx).
8+
// Intended for the scaffold's "basic" preset.
9+
//
10+
// High-Level Flow:
11+
// 1. Identify the root directory and the conventional services/ folder.
12+
// 2. Detect which JavaScript package manager to use (npm / pnpm / yarn / bun).
13+
// 3. Enumerate each subfolder in services/.
14+
// 4. For every folder containing a package.json with a dev script, spawn it.
15+
// 5. Stream each child process' output with a readable prefix.
16+
//
17+
// Design Constraints:
18+
// - Zero external dependencies (Node built‑ins only) to stay portable.
19+
// - Does NOT attempt file-change restarts (leave that to each service's own
20+
// tooling, e.g. nodemon, ts-node-dev, next dev, etc.).
21+
// - Ignores non-Node services (e.g., Go, Python, Java) since they don't have
22+
// a package.json dev script; those are expected to run via their own tools.
23+
// - CommonJS for maximum compatibility regardless of root "type: module".
24+
//
25+
// Extending Tips:
26+
// - To include non-Node services, add detection logic (e.g., main.go, pom.xml)
27+
// and spawn the appropriate commands.
28+
// - To implement auto-restart, consider integrating chokidar to watch files
29+
// and restart specific child processes.
30+
// - To add coloring per service, map service names to a color palette and
31+
// wrap the prefix() output (keep it optional for plain CI logs).
32+
// ---------------------------------------------------------------------------
33+
134
const { spawn } = require('node:child_process');
235
const fs = require('fs');
336
const path = require('path');
437

38+
// Root of the repo (assumes script is run from project root via npm script)
539
const root = process.cwd();
40+
41+
// Convention: all services generated by the scaffold live under services/
42+
// (Older versions used apps/; that was replaced to avoid confusion with Next.js)
643
const servicesDir = path.join(root, 'services');
744

845
if (!fs.existsSync(servicesDir)) {
946
console.warn('⚠️ services/ directory not found. No local services will be started.');
1047
process.exit(0);
1148
}
1249

50+
// Snapshot the list of entries inside services/ (folders assumed to be services)
1351
const services = fs.readdirSync(servicesDir);
1452

1553
// Determine the root package manager heuristically
1654
function detectPM() {
55+
// Heuristic: look for lock files in order of preference.
56+
// If multiple exist (rare / misconfigured), first match wins.
1757
if (fs.existsSync(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';
1858
if (fs.existsSync(path.join(root, 'yarn.lock'))) return 'yarn';
1959
if (fs.existsSync(path.join(root, 'bun.lockb'))) return 'bun';
60+
// Fallback to npm when no known lockfile is detected.
2061
return 'npm';
2162
}
2263
const pm = detectPM();
2364

2465
if (services.length === 0) {
25-
console.warn('⚠️ No services found inside services/.');
66+
// Nothing inside services/; we exit early (not an error condition).
67+
console.log('No Node-based services with package.json to run.');
2668
process.exit(0);
2769
}
2870

2971
console.log(`Using package manager: ${pm}`);
3072
console.log(`Discovered services: ${services.join(', ')}`);
3173

74+
// Keep references to spawned child processes so we can forward signals.
3275
const procs = [];
3376

3477
function prefix(name, data) {
78+
// Prepend the service name to each line of output for readability.
79+
// NOTE: data may contain multiple lines; we leave as‑is to avoid splitting.
3580
process.stdout.write(`[${name}] ${data}`);
3681
}
3782

@@ -40,45 +85,39 @@ services.forEach((svc) => {
4085
const pkgPath = path.join(svcPath, 'package.json');
4186

4287
if (!fs.existsSync(pkgPath)) {
43-
console.warn(`⚠️ Skipping ${svc} (no package.json; likely non-Node service)`);
88+
// Non-Node service (e.g., python, go, java). Skip silently with context.
89+
console.log(`Skipping ${svc} (no package.json; likely non-Node service)`);
4490
return;
4591
}
4692

4793
try {
4894
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
49-
const script = pkg.scripts?.dev || pkg.scripts?.start;
50-
51-
if (!script) {
52-
console.warn(`⚠️ Skipping ${svc} (no "dev" or "start" script in package.json)`);
95+
if (!pkg.scripts || !pkg.scripts.dev) {
96+
// Node project present but without a dev script; user may add later.
97+
console.log(`Skipping ${svc} (no dev script)`);
5398
return;
5499
}
55-
56-
const cmd = pm === 'bun' ? 'bun' : pm;
57-
const args =
58-
pm === 'yarn'
59-
? ['run', script === pkg.scripts.dev ? 'dev' : 'start']
60-
: pm === 'bun'
61-
? ['run', script === pkg.scripts.dev ? 'dev' : 'start']
62-
: ['run', script === pkg.scripts.dev ? 'dev' : 'start'];
63-
64-
const child = spawn(cmd, args, {
65-
cwd: svcPath,
66-
shell: true,
67-
env: process.env,
68-
});
69-
100+
// Determine spawn command + arguments.
101+
// For yarn classic, `yarn dev` would also work, but keeping uniform form.
102+
const cmd = pm === 'bun' ? 'bun' : pm; // direct binary (npm, pnpm, yarn, bun)
103+
const args = pm === 'yarn' ? ['run', 'dev'] : pm === 'bun' ? ['run', 'dev'] : ['run', 'dev'];
104+
// Could special-case: if (pm==='yarn') args=['dev'] but current pattern is fine.
105+
const child = spawn(cmd, args, { cwd: svcPath, shell: true, env: process.env });
70106
procs.push(child);
71-
child.stdout.on('data', (d) => prefix(svc, d));
72-
child.stderr.on('data', (d) => prefix(svc, d));
73-
child.on('exit', (code) => {
107+
child.stdout.on('data', d => prefix(svc, d));
108+
child.stderr.on('data', d => prefix(svc, d));
109+
child.on('exit', code => {
110+
// Log exit so the developer sees early failures.
74111
console.log(`[${svc}] exited with code ${code}`);
75112
});
76113
} catch (e) {
77-
console.error(`❌ Failed to start ${svc}:`, e.message);
114+
// JSON parse errors or spawn failures bubble here.
115+
console.log(`Failed to start ${svc}:`, e.message);
78116
}
79117
});
80118

81119
process.on('SIGINT', () => {
82-
procs.forEach((p) => p.kill('SIGINT'));
120+
// Graceful shutdown: forward Ctrl+C to each child, then exit.
121+
procs.forEach(p => p.kill('SIGINT'));
83122
process.exit(0);
84123
});

0 commit comments

Comments
 (0)