Skip to content

Commit b8d1864

Browse files
committed
feat: implement new interactive flow for service creation in CLI
1 parent d9ae7b5 commit b8d1864

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,28 @@ Add a new service later:
105105
create-polyglot add service payments --type node --port 4100
106106
```
107107

108+
### New Interactive Flow (Dynamic Service Count)
109+
If you omit `--services`, the CLI now asks:
110+
1. "How many services do you want to create?" (enter a number)
111+
2. For each service: choose a type (Node, Python, Go, Java, Frontend)
112+
3. Optionally enter a custom name (blank keeps the default type name)
113+
4. Optionally override the suggested port (blank keeps default)
114+
115+
Example (interactive):
116+
```bash
117+
create-polyglot init my-org
118+
# > How many services? 3
119+
# > Service #1 type: Node.js (Express)
120+
# > Name for node service: api
121+
# > Port for api (node) (default 3001): 4001
122+
# > Service #2 type: Python (FastAPI)
123+
# > Name for python service: (enter) # keeps 'python'
124+
# > Port for python (python) (default 3004): (enter)
125+
# > Service #3 type: Frontend (Next.js)
126+
# ...
127+
```
128+
Non-interactive (`--yes`) still scaffolds exactly one `node` service by default for speed.
129+
108130
## Installation
109131
Global (recommended for repeated use):
110132
```bash

bin/lib/scaffold.js

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,58 @@ export async function scaffoldMonorepo(projectNameArg, options) {
173173
services.push({ type, name, port });
174174
}
175175
} else {
176-
const answer = await prompts({
177-
type: 'multiselect',
178-
name: 'serviceTypes',
179-
message: 'Select services to include:',
180-
choices: allServiceChoices,
181-
instructions: false,
182-
min: 1
176+
// New dynamic interactive flow: ask how many services, then collect each.
177+
const countAns = await prompts({
178+
type: 'number',
179+
name: 'svcCount',
180+
message: 'How many services do you want to create?',
181+
initial: 1,
182+
min: 1,
183+
validate: v => Number.isInteger(v) && v > 0 && v <= 50 ? true : 'Enter a positive integer (max 50)'
183184
});
184-
const selected = answer.serviceTypes || [];
185-
for (const type of selected) services.push({ type, name: type, port: defaultPorts[type] });
185+
const svcCount = countAns.svcCount || 1;
186+
for (let i = 0; i < svcCount; i++) {
187+
const typeAns = await prompts({
188+
type: 'select',
189+
name: 'svcType',
190+
message: `Service #${i+1} type:`,
191+
choices: allServiceChoices.map(c => ({ title: c.title, value: c.value })),
192+
initial: 0
193+
});
194+
const svcType = typeAns.svcType;
195+
if (!svcType) {
196+
console.log(chalk.red('No type selected; aborting.'));
197+
process.exit(1);
198+
}
199+
const nameAns = await prompts({
200+
type: 'text',
201+
name: 'svcName',
202+
message: `Name for ${svcType} service (leave blank for default '${svcType}'):`,
203+
validate: v => !v || (/^[a-zA-Z0-9._-]+$/.test(v) ? true : 'Use alphanumerics, dash, underscore, dot')
204+
});
205+
let svcName = nameAns.svcName && nameAns.svcName.trim() ? nameAns.svcName.trim() : svcType;
206+
if (reservedNames.has(svcName) || services.find(s => s.name === svcName)) {
207+
console.log(chalk.red(`Name '${svcName}' is reserved or already used. Using '${svcType}'.`));
208+
svcName = svcType;
209+
}
210+
const portDefault = defaultPorts[svcType];
211+
const portAns = await prompts({
212+
type: 'text',
213+
name: 'svcPort',
214+
message: `Port for ${svcName} (${svcType}) (default ${portDefault}):`,
215+
validate: v => !v || (/^\d+$/.test(v) && +v > 0 && +v <= 65535) ? true : 'Enter a valid port 1-65535'
216+
});
217+
let svcPort = portDefault;
218+
if (portAns.svcPort) {
219+
const parsed = Number(portAns.svcPort);
220+
if (services.find(s => s.port === parsed)) {
221+
console.log(chalk.red(`Port ${parsed} already used; keeping ${portDefault}.`));
222+
} else if (parsed >=1 && parsed <= 65535) {
223+
svcPort = parsed;
224+
}
225+
}
226+
services.push({ type: svcType, name: svcName, port: svcPort });
227+
}
186228
}
187229

188230
// Always allow customization of name & port in interactive mode (not nonInteractive)

tests/smoke.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('create-polyglot CLI smoke', () => {
2020
it('scaffolds a project with a node service', async () => {
2121
const repoRoot = process.cwd();
2222
const cliPath = path.join(repoRoot, 'bin/index.js');
23-
await execa('node', [cliPath, projName, '--services', 'node', '--no-install', '--yes'], { cwd: tmpDir });
23+
await execa('node', [cliPath, projName, '--services', 'node', '--no-install', '--yes'], { cwd: tmpDir });
2424
const projectPath = path.join(tmpDir, projName);
2525
expect(fs.existsSync(path.join(projectPath, 'services/node'))).toBe(true);
2626
expect(fs.existsSync(path.join(projectPath, 'package.json'))).toBe(true);

0 commit comments

Comments
 (0)