Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ Add a new service later:
create-polyglot add service payments --type node --port 4100
```

### New Interactive Flow (Dynamic Service Count)
If you omit `--services`, the CLI now asks:
1. "How many services do you want to create?" (enter a number)
2. For each service: choose a type (Node, Python, Go, Java, Frontend)
3. Optionally enter a custom name (blank keeps the default type name)
4. Optionally override the suggested port (blank keeps default)

Example (interactive):
```bash
create-polyglot init my-org
# > How many services? 3
# > Service #1 type: Node.js (Express)
# > Name for node service: api
# > Port for api (node) (default 3001): 4001
# > Service #2 type: Python (FastAPI)
# > Name for python service: (enter) # keeps 'python'
# > Port for python (python) (default 3004): (enter)
# > Service #3 type: Frontend (Next.js)
# ...
```
Non-interactive (`--yes`) still scaffolds exactly one `node` service by default for speed.

## Installation
Global (recommended for repeated use):
```bash
Expand Down
60 changes: 51 additions & 9 deletions bin/lib/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,58 @@ export async function scaffoldMonorepo(projectNameArg, options) {
services.push({ type, name, port });
}
} else {
const answer = await prompts({
type: 'multiselect',
name: 'serviceTypes',
message: 'Select services to include:',
choices: allServiceChoices,
instructions: false,
min: 1
// New dynamic interactive flow: ask how many services, then collect each.
const countAns = await prompts({
type: 'number',
name: 'svcCount',
message: 'How many services do you want to create?',
initial: 1,
min: 1,
validate: v => Number.isInteger(v) && v > 0 && v <= 50 ? true : 'Enter a positive integer (max 50)'
});
const selected = answer.serviceTypes || [];
for (const type of selected) services.push({ type, name: type, port: defaultPorts[type] });
const svcCount = countAns.svcCount || 1;
for (let i = 0; i < svcCount; i++) {
const typeAns = await prompts({
type: 'select',
name: 'svcType',
message: `Service #${i+1} type:`,
choices: allServiceChoices.map(c => ({ title: c.title, value: c.value })),
initial: 0
});
const svcType = typeAns.svcType;
if (!svcType) {
console.log(chalk.red('No type selected; aborting.'));
process.exit(1);
}
const nameAns = await prompts({
type: 'text',
name: 'svcName',
message: `Name for ${svcType} service (leave blank for default '${svcType}'):`,
validate: v => !v || (/^[a-zA-Z0-9._-]+$/.test(v) ? true : 'Use alphanumerics, dash, underscore, dot')
});
let svcName = nameAns.svcName && nameAns.svcName.trim() ? nameAns.svcName.trim() : svcType;
if (reservedNames.has(svcName) || services.find(s => s.name === svcName)) {
console.log(chalk.red(`Name '${svcName}' is reserved or already used. Using '${svcType}'.`));
svcName = svcType;
}
const portDefault = defaultPorts[svcType];
const portAns = await prompts({
type: 'text',
name: 'svcPort',
message: `Port for ${svcName} (${svcType}) (default ${portDefault}):`,
validate: v => !v || (/^\d+$/.test(v) && +v > 0 && +v <= 65535) ? true : 'Enter a valid port 1-65535'
});
let svcPort = portDefault;
if (portAns.svcPort) {
const parsed = Number(portAns.svcPort);
if (services.find(s => s.port === parsed)) {
console.log(chalk.red(`Port ${parsed} already used; keeping ${portDefault}.`));
} else if (parsed >=1 && parsed <= 65535) {
svcPort = parsed;
}
}
services.push({ type: svcType, name: svcName, port: svcPort });
}
}

// Always allow customization of name & port in interactive mode (not nonInteractive)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-polyglot",
"version": "1.6.1",
"version": "1.6.2",
"description": "Scaffold polyglot microservice monorepos with built-in templates for Node, Python, Go, and more.",
"main": "bin/index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion tests/smoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('create-polyglot CLI smoke', () => {
it('scaffolds a project with a node service', async () => {
const repoRoot = process.cwd();
const cliPath = path.join(repoRoot, 'bin/index.js');
await execa('node', [cliPath, projName, '--services', 'node', '--no-install', '--yes'], { cwd: tmpDir });
await execa('node', [cliPath, projName, '--services', 'node', '--no-install', '--yes'], { cwd: tmpDir });
const projectPath = path.join(tmpDir, projName);
expect(fs.existsSync(path.join(projectPath, 'services/node'))).toBe(true);
expect(fs.existsSync(path.join(projectPath, 'package.json'))).toBe(true);
Expand Down
Loading