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: 6 additions & 0 deletions docs/guide/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ what you need. A missing **required** param fails fast.
| `php.nginx` | — (parent) | — | — | `phpVersion` ("8.3") |
| `php.laravel.nginx` | — (extends `php.nginx`) | — | — | `appEnv` (local) |
| `node.vite` | — | — | — | `nodeVersion` ("20"); runs `npm run dev` |
| `node.express` | — | — | — | `nodeVersion` ("20"); `npm run dev`, hot reload |
| `node.nestjs` | — | — | — | `nodeVersion` ("20"); `nest start --watch` |
| `node.next` | — | — | — | `nodeVersion` ("20"); `next dev`, `WATCHPACK_POLLING` |
| `react.vite` | — | — | — | `nodeVersion` ("20"); Vite HMR |
| `bun.app` | — | — | — | `bunVersion` ("1"); `bun run dev` |
| `turborepo` | — | — | — | `nodeVersion` ("20"); `turbo run dev` (monorepo) |
| `kafka` (Redpanda) | kafka | host, port, adminPort | 9092 | `image` |
| `nats` | nats | host, port, monitorPort | 4222 | `version` ("2") |
| `rabbitmq` | amqp | host, port, mgmtPort | 5672 | `version` ("3"), `user` (devstack) |
Expand Down
15 changes: 8 additions & 7 deletions docs/guide/whats-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,20 @@ task graph. We don't want to overclaim — today, if you need
`build → test → deploy` task graphs across packages, use your existing task
runner alongside devstack; devstack handles the infra those tasks talk to.

### Framework dev servers with watch mode (Next.js, NestJS)
### Framework dev servers with watch mode (Next.js, NestJS) — ✅ shipped

**Partially covered — the pattern already works, first-class templates don't ship
yet.** The `node.vite` built-in builds from `build/Dockerfile` and runs
`command: ["npm", "run", "dev"]` in watch mode. Next.js and NestJS are just **new
templates you can author right now** with `template new`, bind-mounting source
for hot reload:
**Now built-in.** `node.express`, `node.nestjs`, `node.next`, `react.vite`,
`bun.app`, and `turborepo` ship as templates with **dev-mode hot reload** — they
bind-mount the project source (`..:/app`), keep an anonymous `node_modules`
volume, run `install` + the dev server, and set the framework's file-watch polling
env for WSL2. See [templates.md](templates.md). You can still author your own
variant with `template new`; a minimal `node.next`-style template:

```bash
devstack template new node.next --kind app
```

A minimal `node.next` template that runs `next dev` with hot reload:
A minimal template that runs `next dev` with hot reload:

```yaml
# ~/.devstack/templates/node.next/template.yaml
Expand Down
37 changes: 37 additions & 0 deletions internal/cli/js_templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cli

import (
"strings"
"testing"

"github.com/open-source-cloud/devstack/internal/generate"
"github.com/open-source-cloud/devstack/internal/template"
)

// TestJSTemplatesRenderAndValidate guards the built-in JS/monorepo app templates:
// each resolves through the embedded source, validates via compose-go, and emits
// the dev-mode hot-reload shape (a /app mount + a dev command).
func TestJSTemplatesRenderAndValidate(t *testing.T) {
src := builtinSource()
for _, name := range []string{
"node.express", "node.nestjs", "node.next", "react.vite", "bun.app", "turborepo",
} {
res, err := template.Resolve(src, name, nil)
if err != nil {
t.Errorf("%s resolve: %v", name, err)
continue
}
compose, err := generate.LintResolved(name, res)
if err != nil {
t.Errorf("%s lint: %v", name, err)
continue
}
doc := string(compose)
if !strings.Contains(doc, "/app") {
t.Errorf("%s: missing /app mount for hot reload:\n%s", name, doc)
}
if !strings.Contains(doc, "dev") {
t.Errorf("%s: missing a dev command", name)
}
}
}
11 changes: 11 additions & 0 deletions templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ Built-in service templates, compiled into the binary via `go:embed`
| `php.nginx` | project base | PHP-FPM build (`build/Dockerfile`); parent template |
| `php.laravel.nginx` | project | `extends: php.nginx`; adds Laravel env + entrypoint |
| `node.vite` | project | Node + Vite dev server build |
| `node.express` | project | Express dev server (`npm run dev`), source bind-mount + hot reload |
| `node.nestjs` | project | NestJS (`nest start --watch`), hot reload |
| `node.next` | project | Next.js (`next dev`), `WATCHPACK_POLLING` for WSL2 |
| `react.vite` | project | React + Vite dev server (HMR) |
| `bun.app` | project | Bun app (`bun run dev`), `oven/bun` base |
| `turborepo` | project (monorepo) | `turbo run dev` across packages; pairs with `devstack run` |

**Dev-mode hot reload.** The JS/TS app templates bind-mount the project source
(`..:/app`, since the generated compose lives in `<project>/.devstack/`) with an
anonymous `node_modules` volume, run `npm/bun install` then the dev server, and
set the framework's file-watch polling env for WSL2/9p reliability.

## A template is a directory

Expand Down
6 changes: 6 additions & 0 deletions templates/bun.app/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG BUN_VERSION=[[ .params.bunVersion ]]
FROM oven/bun:${BUN_VERSION}-alpine
WORKDIR /app
EXPOSE 3000
CMD ["bun", "run", "dev"]
20 changes: 20 additions & 0 deletions templates/bun.app/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
schemaVersion: 1
description: "Bun app dev server (bun run dev) with hot reload."
params:
bunVersion:
type: string
default: "1"
description: "Bun major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
BUN_VERSION: "[[ .params.bunVersion ]]"
restart: unless-stopped
command: ["sh", "-lc", "bun install && bun run dev"]
environment:
NODE_ENV: development
volumes:
- "..:/app"
- "/app/node_modules"
2 changes: 1 addition & 1 deletion templates/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package templates

import "embed"

//go:embed all:postgres all:redis all:minio all:php.nginx all:php.laravel.nginx all:node.vite all:localstack all:ministack all:nats all:kafka all:rabbitmq
//go:embed all:postgres all:redis all:minio all:php.nginx all:php.laravel.nginx all:node.vite all:localstack all:ministack all:nats all:kafka all:rabbitmq all:node.express all:node.nestjs all:node.next all:react.vite all:bun.app all:turborepo
var builtinFS embed.FS

// FS is the embedded built-in templates root: template-name directories at the
Expand Down
6 changes: 6 additions & 0 deletions templates/node.express/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=[[ .params.nodeVersion ]]
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
EXPOSE 3000
CMD ["npm", "run", "dev"]
24 changes: 24 additions & 0 deletions templates/node.express/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: 1
description: "Express dev server (npm run dev / nodemon) with hot reload."
params:
nodeVersion:
type: string
default: "20"
description: "Node major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
NODE_VERSION: "[[ .params.nodeVersion ]]"
restart: unless-stopped
# Install deps into the container (anon node_modules volume) then run the dev
# server. Source is bind-mounted from the project dir for hot reload.
command: ["sh", "-lc", "npm install && npm run dev"]
environment:
NODE_ENV: development
CHOKIDAR_USEPOLLING: "true"
volumes:
# compose lives in <project>/.devstack, so ".." is the project source.
- "..:/app"
- "/app/node_modules"
6 changes: 6 additions & 0 deletions templates/node.nestjs/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=[[ .params.nodeVersion ]]
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
EXPOSE 3000
CMD ["npm", "run", "dev"]
24 changes: 24 additions & 0 deletions templates/node.nestjs/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: 1
description: "NestJS dev server (nest start --watch) with hot reload."
params:
nodeVersion:
type: string
default: "20"
description: "Node major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
NODE_VERSION: "[[ .params.nodeVersion ]]"
restart: unless-stopped
# Install deps into the container (anon node_modules volume) then run the dev
# server. Source is bind-mounted from the project dir for hot reload.
command: ["sh", "-lc", "npm install && npm run start:dev"]
environment:
NODE_ENV: development
CHOKIDAR_USEPOLLING: "true"
volumes:
# compose lives in <project>/.devstack, so ".." is the project source.
- "..:/app"
- "/app/node_modules"
6 changes: 6 additions & 0 deletions templates/node.next/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=[[ .params.nodeVersion ]]
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
EXPOSE 3000
CMD ["npm", "run", "dev"]
24 changes: 24 additions & 0 deletions templates/node.next/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: 1
description: "Next.js dev server (next dev) with hot reload."
params:
nodeVersion:
type: string
default: "20"
description: "Node major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
NODE_VERSION: "[[ .params.nodeVersion ]]"
restart: unless-stopped
# Install deps into the container (anon node_modules volume) then run the dev
# server. Source is bind-mounted from the project dir for hot reload.
command: ["sh", "-lc", "npm install && npm run dev"]
environment:
NODE_ENV: development
WATCHPACK_POLLING: "true"
volumes:
# compose lives in <project>/.devstack, so ".." is the project source.
- "..:/app"
- "/app/node_modules"
6 changes: 6 additions & 0 deletions templates/react.vite/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=[[ .params.nodeVersion ]]
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
EXPOSE 5173
CMD ["npm", "run", "dev"]
24 changes: 24 additions & 0 deletions templates/react.vite/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: 1
description: "React + Vite dev server with HMR."
params:
nodeVersion:
type: string
default: "20"
description: "Node major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
NODE_VERSION: "[[ .params.nodeVersion ]]"
restart: unless-stopped
# Install deps into the container (anon node_modules volume) then run the dev
# server. Source is bind-mounted from the project dir for hot reload.
command: ["sh", "-lc", "npm install && npm run dev -- --host"]
environment:
NODE_ENV: development
CHOKIDAR_USEPOLLING: "true"
volumes:
# compose lives in <project>/.devstack, so ".." is the project source.
- "..:/app"
- "/app/node_modules"
6 changes: 6 additions & 0 deletions templates/turborepo/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=[[ .params.nodeVersion ]]
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
EXPOSE 3000
CMD ["npm", "run", "dev"]
24 changes: 24 additions & 0 deletions templates/turborepo/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schemaVersion: 1
description: "Turborepo monorepo dev (turbo run dev across packages)."
params:
nodeVersion:
type: string
default: "20"
description: "Node major version image tag."
service:
build:
context: build
dockerfile: Dockerfile
args:
NODE_VERSION: "[[ .params.nodeVersion ]]"
restart: unless-stopped
# Install deps into the container (anon node_modules volume) then run the dev
# server. Source is bind-mounted from the project dir for hot reload.
command: ["sh", "-lc", "npm install && npx turbo run dev"]
environment:
NODE_ENV: development
TURBO_TELEMETRY_DISABLED: "1"
volumes:
# compose lives in <project>/.devstack, so ".." is the project source.
- "..:/app"
- "/app/node_modules"
Loading