Skip to content

Commit 7175140

Browse files
Merge pull request #118 from open-source-cloud/feat/js-monorepo-templates
feat(templates): JS/TS + monorepo app templates with hot reload (spec 31)
2 parents 202e9b7 + d5abfd6 commit 7175140

17 files changed

Lines changed: 239 additions & 8 deletions

File tree

docs/guide/templates.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ what you need. A missing **required** param fails fast.
4646
| `php.nginx` | — (parent) | — | — | `phpVersion` ("8.3") |
4747
| `php.laravel.nginx` | — (extends `php.nginx`) | — | — | `appEnv` (local) |
4848
| `node.vite` | — | — | — | `nodeVersion` ("20"); runs `npm run dev` |
49+
| `node.express` | — | — | — | `nodeVersion` ("20"); `npm run dev`, hot reload |
50+
| `node.nestjs` | — | — | — | `nodeVersion` ("20"); `nest start --watch` |
51+
| `node.next` | — | — | — | `nodeVersion` ("20"); `next dev`, `WATCHPACK_POLLING` |
52+
| `react.vite` | — | — | — | `nodeVersion` ("20"); Vite HMR |
53+
| `bun.app` | — | — | — | `bunVersion` ("1"); `bun run dev` |
54+
| `turborepo` | — | — | — | `nodeVersion` ("20"); `turbo run dev` (monorepo) |
4955
| `kafka` (Redpanda) | kafka | host, port, adminPort | 9092 | `image` |
5056
| `nats` | nats | host, port, monitorPort | 4222 | `version` ("2") |
5157
| `rabbitmq` | amqp | host, port, mgmtPort | 5672 | `version` ("3"), `user` (devstack) |

docs/guide/whats-next.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,20 @@ task graph. We don't want to overclaim — today, if you need
7474
`build → test → deploy` task graphs across packages, use your existing task
7575
runner alongside devstack; devstack handles the infra those tasks talk to.
7676

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

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

8586
```bash
8687
devstack template new node.next --kind app
8788
```
8889

89-
A minimal `node.next` template that runs `next dev` with hot reload:
90+
A minimal template that runs `next dev` with hot reload:
9091

9192
```yaml
9293
# ~/.devstack/templates/node.next/template.yaml

internal/cli/js_templates_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cli
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/open-source-cloud/devstack/internal/generate"
8+
"github.com/open-source-cloud/devstack/internal/template"
9+
)
10+
11+
// TestJSTemplatesRenderAndValidate guards the built-in JS/monorepo app templates:
12+
// each resolves through the embedded source, validates via compose-go, and emits
13+
// the dev-mode hot-reload shape (a /app mount + a dev command).
14+
func TestJSTemplatesRenderAndValidate(t *testing.T) {
15+
src := builtinSource()
16+
for _, name := range []string{
17+
"node.express", "node.nestjs", "node.next", "react.vite", "bun.app", "turborepo",
18+
} {
19+
res, err := template.Resolve(src, name, nil)
20+
if err != nil {
21+
t.Errorf("%s resolve: %v", name, err)
22+
continue
23+
}
24+
compose, err := generate.LintResolved(name, res)
25+
if err != nil {
26+
t.Errorf("%s lint: %v", name, err)
27+
continue
28+
}
29+
doc := string(compose)
30+
if !strings.Contains(doc, "/app") {
31+
t.Errorf("%s: missing /app mount for hot reload:\n%s", name, doc)
32+
}
33+
if !strings.Contains(doc, "dev") {
34+
t.Errorf("%s: missing a dev command", name)
35+
}
36+
}
37+
}

templates/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ Built-in service templates, compiled into the binary via `go:embed`
1414
| `php.nginx` | project base | PHP-FPM build (`build/Dockerfile`); parent template |
1515
| `php.laravel.nginx` | project | `extends: php.nginx`; adds Laravel env + entrypoint |
1616
| `node.vite` | project | Node + Vite dev server build |
17+
| `node.express` | project | Express dev server (`npm run dev`), source bind-mount + hot reload |
18+
| `node.nestjs` | project | NestJS (`nest start --watch`), hot reload |
19+
| `node.next` | project | Next.js (`next dev`), `WATCHPACK_POLLING` for WSL2 |
20+
| `react.vite` | project | React + Vite dev server (HMR) |
21+
| `bun.app` | project | Bun app (`bun run dev`), `oven/bun` base |
22+
| `turborepo` | project (monorepo) | `turbo run dev` across packages; pairs with `devstack run` |
23+
24+
**Dev-mode hot reload.** The JS/TS app templates bind-mount the project source
25+
(`..:/app`, since the generated compose lives in `<project>/.devstack/`) with an
26+
anonymous `node_modules` volume, run `npm/bun install` then the dev server, and
27+
set the framework's file-watch polling env for WSL2/9p reliability.
1728

1829
## A template is a directory
1930

templates/bun.app/build/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# syntax=docker/dockerfile:1
2+
ARG BUN_VERSION=[[ .params.bunVersion ]]
3+
FROM oven/bun:${BUN_VERSION}-alpine
4+
WORKDIR /app
5+
EXPOSE 3000
6+
CMD ["bun", "run", "dev"]

templates/bun.app/template.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
schemaVersion: 1
2+
description: "Bun app dev server (bun run dev) with hot reload."
3+
params:
4+
bunVersion:
5+
type: string
6+
default: "1"
7+
description: "Bun major version image tag."
8+
service:
9+
build:
10+
context: build
11+
dockerfile: Dockerfile
12+
args:
13+
BUN_VERSION: "[[ .params.bunVersion ]]"
14+
restart: unless-stopped
15+
command: ["sh", "-lc", "bun install && bun run dev"]
16+
environment:
17+
NODE_ENV: development
18+
volumes:
19+
- "..:/app"
20+
- "/app/node_modules"

templates/embed.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package templates
77

88
import "embed"
99

10-
//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
10+
//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
1111
var builtinFS embed.FS
1212

1313
// FS is the embedded built-in templates root: template-name directories at the
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# syntax=docker/dockerfile:1
2+
ARG NODE_VERSION=[[ .params.nodeVersion ]]
3+
FROM node:${NODE_VERSION}-alpine
4+
WORKDIR /app
5+
EXPOSE 3000
6+
CMD ["npm", "run", "dev"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
schemaVersion: 1
2+
description: "Express dev server (npm run dev / nodemon) with hot reload."
3+
params:
4+
nodeVersion:
5+
type: string
6+
default: "20"
7+
description: "Node major version image tag."
8+
service:
9+
build:
10+
context: build
11+
dockerfile: Dockerfile
12+
args:
13+
NODE_VERSION: "[[ .params.nodeVersion ]]"
14+
restart: unless-stopped
15+
# Install deps into the container (anon node_modules volume) then run the dev
16+
# server. Source is bind-mounted from the project dir for hot reload.
17+
command: ["sh", "-lc", "npm install && npm run dev"]
18+
environment:
19+
NODE_ENV: development
20+
CHOKIDAR_USEPOLLING: "true"
21+
volumes:
22+
# compose lives in <project>/.devstack, so ".." is the project source.
23+
- "..:/app"
24+
- "/app/node_modules"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# syntax=docker/dockerfile:1
2+
ARG NODE_VERSION=[[ .params.nodeVersion ]]
3+
FROM node:${NODE_VERSION}-alpine
4+
WORKDIR /app
5+
EXPOSE 3000
6+
CMD ["npm", "run", "dev"]

0 commit comments

Comments
 (0)