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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
[![npm downloads](https://img.shields.io/npm/dw/create-polyglot.svg)](https://www.npmjs.com/package/create-polyglot)

<strong>Scaffold a modern polyglot microservices monorepo in seconds.</strong><br/>
Generate Node.js, Python (FastAPI), Go, Java (Spring Boot), and Next.js frontend services with optional Turborepo or Nx presets, Docker Compose, shared packages, plugin hooks, and a persisted configuration file.
Generate Node.js, Python (FastAPI), Go, Java (Spring Boot), Next.js, plus optional modern frontend frameworks (Remix, Astro, SvelteKit) with Turborepo or Nx presets, Docker Compose, shared packages, plugin hooks, and a persisted configuration file.

</div>

Expand Down Expand Up @@ -54,7 +54,7 @@ Use it to prototype architectures, onboard teams faster, or spin up reproducible

## Features

- πŸš€ Rapid polyglot monorepo scaffolding (Node.js, Python/FastAPI, Go, Java Spring Boot, Next.js)
- πŸš€ Rapid polyglot monorepo scaffolding (Node.js, Python/FastAPI, Go, Java Spring Boot, Next.js, Remix, Astro, SvelteKit)
- 🧩 Optional presets: Turborepo, Nx, or Basic runner
- 🐳 Automatic Dockerfile + Docker Compose generation
- πŸ›  Interactive wizard (or fully non-interactive via flags)
Expand Down Expand Up @@ -174,7 +174,7 @@ create-polyglot dev --docker
## Init Options
| Flag | Description |
|------|-------------|
| `-s, --services <list>` | Comma separated services: `node,python,go,java,frontend` |
| `-s, --services <list>` | Comma separated services: `node,python,go,java,frontend,remix,astro,sveltekit` |
| `--preset <name>` | `turborepo`, `nx`, or `basic` (default auto -> asks) |
| `--git` | Initialize git repo & initial commit |
| `--no-install` | Skip dependency installation step |
Expand Down
9 changes: 6 additions & 3 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ program
.description('Add a new service or plugin')
.argument('<entity>', 'service | plugin')
.argument('<name>', 'Name of the service or plugin')
.option('--type <type>', 'Service type (node|python|go|java|frontend)')
.option('--type <type>', 'Service type (node|python|go|java|frontend|remix|astro|sveltekit)')
.option('--lang <type>', '(Deprecated) Alias of --type')
.option('--port <port>', 'Service port')
.option('--yes', 'Non-interactive defaults')
Expand All @@ -74,7 +74,10 @@ program
{ title: 'Python', value: 'python' },
{ title: 'Go', value: 'go' },
{ title: 'Java', value: 'java' },
{ title: 'Frontend (Next.js)', value: 'frontend' }
{ title: 'Frontend (Next.js)', value: 'frontend' },
{ title: 'Remix', value: 'remix' },
{ title: 'Astro', value: 'astro' },
{ title: 'SvelteKit', value: 'sveltekit' }
] });
type = ans.type;
}
Expand All @@ -83,7 +86,7 @@ program
if (ans.port) port = Number(ans.port);
}
}
const defaultPorts = { frontend: 3000, node: 3001, go: 3002, java: 3003, python: 3004 };
const defaultPorts = { frontend: 3000, node: 3001, go: 3002, java: 3003, python: 3004, remix: 3005, astro: 3006, sveltekit: 3007 };
if (!type) throw new Error('Service type required');
if (!port) port = defaultPorts[type];
await addService(projectDir, { type, name, port }, opts);
Expand Down
76 changes: 54 additions & 22 deletions bin/lib/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,15 @@ export async function scaffoldMonorepo(projectNameArg, options) {
{ title: 'Python (FastAPI)', value: 'python' },
{ title: 'Go (Fiber-like)', value: 'go' },
{ title: 'Java (Spring Boot)', value: 'java' },
{ title: 'Frontend (Next.js)', value: 'frontend' }
{ title: 'Frontend (Next.js)', value: 'frontend' },
{ title: 'Remix', value: 'remix' },
{ title: 'Astro', value: 'astro' },
{ title: 'SvelteKit', value: 'sveltekit' }
];
const templateMap = { java: 'spring-boot' };
let services = [];
const reservedNames = new Set(['scripts','packages','apps','node_modules','docker','compose','compose.yaml']);
const defaultPorts = { frontend: 3000, node: 3001, go: 3002, java: 3003, python: 3004 };
const defaultPorts = { frontend: 3000, node: 3001, go: 3002, java: 3003, python: 3004, remix: 3005, astro: 3006, sveltekit: 3007 };

if (options.services) {
const validValues = allServiceChoices.map(c => c.value);
Expand Down Expand Up @@ -330,29 +333,58 @@ export async function scaffoldMonorepo(projectNameArg, options) {
console.log(chalk.yellow(`⚠️ create-next-app failed: ${e.message}. Using template.`));
}
}
// Strict external generators for new frameworks: abort on failure (no internal fallback yet)
if (svcType === 'remix') {
try {
console.log(chalk.cyan('βš™οΈ Running Remix generator (create-react-router with basic template)...'));
await execa('npx', ['--yes', 'create-react-router@latest', '.', '--template', 'remix-run/react-router/examples/basic', '--no-git-init', '--no-install'], { cwd: dest, stdio: 'inherit' });
usedGenerator = true;
} catch (e) {
console.error(chalk.red(`❌ create-react-router failed: ${e.message}. Aborting scaffold for this service.`));
continue; // skip creating this service
}
} else if (svcType === 'astro') {
try {
console.log(chalk.cyan('βš™οΈ Running Astro generator (create-astro)...'));
await execa('npx', ['--yes', 'create-astro@latest', '.', '--template', 'minimal', '--no-install', '--no-git'], { cwd: dest, stdio: 'inherit' });
usedGenerator = true;
} catch (e) {
console.error(chalk.red(`❌ create-astro failed: ${e.message}. Aborting scaffold for this service.`));
continue;
}
} else if (svcType === 'sveltekit') {
try {
console.log(chalk.cyan('βš™οΈ Running SvelteKit generator (sv create)...'));
await execa('npx', ['sv', 'create', '.', '--template', 'minimal', '--types', 'ts', '--no-install', '--no-add-ons'], { cwd: dest, stdio: 'inherit' });
usedGenerator = true;
} catch (e) {
console.error(chalk.red(`❌ sv create failed: ${e.message}. Aborting scaffold for this service.`));
continue;
}
}
if (!usedGenerator) {
if (await fs.pathExists(src) && (await fs.readdir(src)).length > 0) {
await fs.copy(src, dest, { overwrite: true });

// Dynamically update the name field in package.json for Node.js services
if (svcType === 'node') {
const packageJsonPath = path.join(dest, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJSON(packageJsonPath);
packageJson.name = `@${projectNameArg || 'polyglot'}/${svcName}`; // Ensure unique name
await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
// Non-framework services use internal templates
if (svcType !== 'remix' && svcType !== 'astro' && svcType !== 'sveltekit') {
if (await fs.pathExists(src) && (await fs.readdir(src)).length > 0) {
await fs.copy(src, dest, { overwrite: true });
if (svcType === 'node') {
const packageJsonPath = path.join(dest, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJSON(packageJsonPath);
packageJson.name = `@${projectNameArg || 'polyglot'}/${svcName}`;
await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
}
}
}

if (templateFolder === 'spring-boot') {
const propTxt = path.join(dest, 'src/main/resources/application.properties.txt');
const prop = path.join(dest, 'src/main/resources/application.properties');
if (await fs.pathExists(propTxt) && !(await fs.pathExists(prop))) {
await fs.move(propTxt, prop);
if (templateFolder === 'spring-boot') {
const propTxt = path.join(dest, 'src/main/resources/application.properties.txt');
const prop = path.join(dest, 'src/main/resources/application.properties');
if (await fs.pathExists(propTxt) && !(await fs.pathExists(prop))) {
await fs.move(propTxt, prop);
}
}
} else {
await fs.writeFile(path.join(dest, 'README.md'), `# ${svcName} service\n\nScaffolded by create-polyglot.`);
}
} else {
await fs.writeFile(path.join(dest, 'README.md'), `# ${svcName} service\n\nScaffolded by create-polyglot.`);
}
}

Expand Down Expand Up @@ -444,7 +476,7 @@ export async function scaffoldMonorepo(projectNameArg, options) {
const dockerfile = path.join(svcDir, 'Dockerfile');
if (!(await fs.pathExists(dockerfile))) {
let dockerContent = '';
if (svcObj.type === 'node' || svcObj.type === 'frontend') {
if (svcObj.type === 'node' || svcObj.type === 'frontend' || ['remix','astro','sveltekit'].includes(svcObj.type)) {
dockerContent = `# ${svcObj.name} (${svcObj.type}) service\nFROM node:20-alpine AS deps\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install --omit=dev || true\nCOPY . .\nEXPOSE ${port}\nCMD [\"npm\", \"run\", \"dev\"]\n`;
} else if (svcObj.type === 'python') {
dockerContent = `FROM python:3.12-slim\nWORKDIR /app\nCOPY requirements.txt ./\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY . .\nEXPOSE ${port}\nCMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"${port}\"]\n`;
Expand Down
9 changes: 6 additions & 3 deletions docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export default defineConfig({
// sets VITEPRESS_BASE automatically; local dev stays '/'.
base: process.env.VITEPRESS_BASE || '/',
title: 'create-polyglot',
description: 'Scaffold polyglot microservice monorepos (Node, Python, Go, Java, Next.js)',
// Updated to reflect newly supported frontend frameworks (Remix, Astro, SvelteKit)
description: 'Scaffold polyglot microservice monorepos (Node, Python, Go, Java, Next.js, Remix, Astro, SvelteKit)',
head: [
['meta', { name: 'theme-color', content: '#3e62ad' }],
['meta', { name: 'og:title', content: 'create-polyglot' }],
Expand All @@ -26,7 +27,8 @@ export default defineConfig({
{ text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Presets', link: '/guide/presets' },
{ text: 'Docker & Compose', link: '/guide/docker' },
{ text: 'Extending (New Service)', link: '/guide/extending-service' },
{ text: 'Extending (New Service)', link: '/guide/extending-service' },
{ text: 'Frontend Frameworks', link: '/guide/frontend-frameworks' },
{ text: 'Service Logs', link: '/logs-feature' },
{ text: 'Plugin System', link: '/plugin-system' },
{ text: 'Service Controls', link: '/service-controls-feature' },
Expand All @@ -46,7 +48,8 @@ export default defineConfig({
{ text: 'Python', link: '/templates/python' },
{ text: 'Go', link: '/templates/go' },
{ text: 'Java (Spring Boot)', link: '/templates/java' },
{ text: 'Frontend (Next.js)', link: '/templates/frontend' }
{ text: 'Frontend (Next.js)', link: '/templates/frontend' },
{ text: 'Frontend Frameworks (Remix/Astro/SvelteKit)', link: '/guide/frontend-frameworks' }
]
},
socialLinks: [
Expand Down
112 changes: 112 additions & 0 deletions docs/guide/frontend-frameworks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Modern Frontend Framework Support

create-polyglot can scaffold additional frontend frameworks beyond the default Next.js.

## Supported Frameworks

- **Next.js** (internal template or `create-next-app` when `--frontend-generator` is passed)
- **Remix** (via `npx create-remix@latest . --template remix`)
- **Astro** (via `npx create-astro@latest -- --template minimal`)
- **SvelteKit** (via `npx sv create .`; falls back to `npx create-svelte@latest . --template skeleton` if the new command fails)

## Selecting Frameworks

Specify them in the `--services` list during init or with `create-polyglot add service`:

```bash
create-polyglot init my-stack -s remix,astro,sveltekit --yes
create-polyglot add service docs --type astro --port 3030
```

## Ports

Default ports:

```text
remix: 3005
astro: 3006
sveltekit: 3007
```

Override any port via `type:name:port` syntax:

```bash
create-polyglot init web-app -s remix:web:3100,astro:site:3200,sveltekit:kit:3300 --yes
```

## Generation Behavior

| Framework | Generation Method | Fallback | Notes |
|-----------|-------------------|----------|-------|
| Remix | `create-remix` | None (skip on failure) | Skipped if generator errors. |
| Astro | `create-astro` | None (skip on failure) | Uses `--template minimal`. |
| SvelteKit | `sv create` | `create-svelte` | Deprecation handled gracefully. |

Failed generators log an error and the service is skipped (not partially scaffolded) to avoid broken directories.

## Docker

All Node-based frameworks (Remix, Astro, SvelteKit) reuse the generic Node Dockerfile pattern:

```Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev || true
COPY . .
EXPOSE <PORT>
CMD ["npm", "run", "dev"]
```

Adjust after generation if framework-specific build or preview commands are desired.

## Service Manager

Runtime start uses the detected package manager and `npm run dev` (or equivalent) for these frameworks. Ensure the generator produces a `dev` script. If not, add one manually.

## Caveats & Future Plans

- No internal fallback templates (kept lean). Potential future flag: `--allow-fallback`.
- Post-generation customization (eslint, prettier) left to user.
- May add automatic build scripts & production Docker variants later.

## Example Full Init

```bash
npx create-polyglot init multi-web -s node,remix,astro,sveltekit --git --yes
```

After scaffold:

```bash
cd multi-web
npm run list:services
create-polyglot dev
```

## Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| Generator network failure | Offline or registry issue | Retry with stable connection; consider adding fallback templates. |
| Missing dev script | Generator changed defaults | Add `"dev": "<framework command>"` to `package.json`. |
| Port collision | Duplicate specified port | Re-run with adjusted port list or edit `polyglot.json` then restart. |
| SvelteKit deprecation warning | Using legacy command | Ensure `sv` is available; keep fallback until ecosystem fully migrates. |

## Updating Existing Workspace

Add a new framework to an existing project:

```bash
create-polyglot add service ui-new --type sveltekit --port 3400
```

Remove it later:

```bash
create-polyglot remove service ui-new --yes
```

---

Need another framework (e.g., Nuxt, SolidStart)? Open an issue or PR with a proposed generator command.
13 changes: 6 additions & 7 deletions docs/templates/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Templates Overview

All service templates live in `templates/` and are copied verbatim (with a Spring Boot properties rename step). Current templates:
Service scaffolding comes from internal templates or official generators:

- `node/` Express + `/health` endpoint
- `python/` FastAPI + `/health`
- `go/` net/http server + `/health`
- `spring-boot/` Java with `/health` REST controller
- `frontend/` Next.js minimal (or `create-next-app` output when generator used)
- Internal templates (copied verbatim): `node/`, `python/`, `go/`, `spring-boot/`, `frontend/` (minimal Next.js when not using `--frontend-generator`).
- External generators (no internal fallback unless added later): Remix (`create-remix`), Astro (`create-astro`), SvelteKit (`sv create` with legacy `create-svelte` fallback).

Each template should stay minimal & dependency-light.
Spring Boot still performs a post-copy rename of `application.properties.txt` β†’ `application.properties`.

Keep internal templates minimal & dependency-light; external generator outputs are left intact.
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.17.0",
"version": "1.18.0",
"description": "Scaffold polyglot microservice monorepos with built-in templates for Node, Python, Go, and more.",
"main": "bin/index.js",
"scripts": {
Expand Down
Loading