@empire-builder-kit/containers 0.0.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/executors.json +3 -0
  4. package/dist/generators/service/files-go/Dockerfile.template +11 -0
  5. package/dist/generators/service/files-go/README.md.template +11 -0
  6. package/dist/generators/service/files-go/cmd/main.go.template +34 -0
  7. package/dist/generators/service/files-go/docs/contracts.md.template +9 -0
  8. package/dist/generators/service/files-go/go.mod.template +3 -0
  9. package/dist/generators/service/files-go/infra/service.ts.template +59 -0
  10. package/dist/generators/service/files-python/Dockerfile.template +12 -0
  11. package/dist/generators/service/files-python/README.md.template +11 -0
  12. package/dist/generators/service/files-python/docs/contracts.md.template +9 -0
  13. package/dist/generators/service/files-python/infra/service.ts.template +68 -0
  14. package/dist/generators/service/files-python/pyproject.toml.template +15 -0
  15. package/dist/generators/service/files-python/src/main.py.template +20 -0
  16. package/dist/generators/service/files-rust/Cargo.toml.template +16 -0
  17. package/dist/generators/service/files-rust/Dockerfile.template +12 -0
  18. package/dist/generators/service/files-rust/README.md.template +11 -0
  19. package/dist/generators/service/files-rust/docs/contracts.md.template +9 -0
  20. package/dist/generators/service/files-rust/infra/service.ts.template +59 -0
  21. package/dist/generators/service/files-rust/src/main.rs.template +29 -0
  22. package/dist/generators/service/files-typescript/Dockerfile.template +30 -0
  23. package/dist/generators/service/files-typescript/README.md.template +24 -0
  24. package/dist/generators/service/files-typescript/docs/architecture.md.template +18 -0
  25. package/dist/generators/service/files-typescript/docs/contracts.md.template +20 -0
  26. package/dist/generators/service/files-typescript/docs/operations.md.template +24 -0
  27. package/dist/generators/service/files-typescript/eslint.config.mjs.template +3 -0
  28. package/dist/generators/service/files-typescript/infra/service.ts.template +64 -0
  29. package/dist/generators/service/files-typescript/package.json.template +7 -0
  30. package/dist/generators/service/files-typescript/src/index.ts.template +2 -0
  31. package/dist/generators/service/files-typescript/src/main.ts.template +60 -0
  32. package/dist/generators/service/files-typescript/src/service.spec.ts.template +49 -0
  33. package/dist/generators/service/files-typescript/src/service.ts.template +57 -0
  34. package/dist/generators/service/files-typescript/tsconfig.app.json.template +12 -0
  35. package/dist/generators/service/files-typescript/tsconfig.json.template +7 -0
  36. package/dist/generators/service/files-typescript/tsconfig.spec.json.template +7 -0
  37. package/dist/generators/service/files-typescript/vitest.config.mts.template +17 -0
  38. package/dist/generators/service/schema.d.ts +8 -0
  39. package/dist/generators/service/schema.json +40 -0
  40. package/dist/generators/service/service.d.ts +5 -0
  41. package/dist/generators/service/service.d.ts.map +1 -0
  42. package/dist/generators/service/service.js +198 -0
  43. package/dist/generators/task/files-go/Dockerfile.template +10 -0
  44. package/dist/generators/task/files-go/README.md.template +11 -0
  45. package/dist/generators/task/files-go/cmd/main.go.template +22 -0
  46. package/dist/generators/task/files-go/docs/contracts.md.template +9 -0
  47. package/dist/generators/task/files-go/go.mod.template +3 -0
  48. package/dist/generators/task/files-go/infra/task.ts.template +38 -0
  49. package/dist/generators/task/files-python/Dockerfile.template +10 -0
  50. package/dist/generators/task/files-python/README.md.template +11 -0
  51. package/dist/generators/task/files-python/docs/contracts.md.template +9 -0
  52. package/dist/generators/task/files-python/infra/task.ts.template +38 -0
  53. package/dist/generators/task/files-python/pyproject.toml.template +12 -0
  54. package/dist/generators/task/files-python/src/main.py.template +26 -0
  55. package/dist/generators/task/files-rust/Cargo.toml.template +15 -0
  56. package/dist/generators/task/files-rust/Dockerfile.template +11 -0
  57. package/dist/generators/task/files-rust/README.md.template +11 -0
  58. package/dist/generators/task/files-rust/docs/contracts.md.template +9 -0
  59. package/dist/generators/task/files-rust/infra/task.ts.template +38 -0
  60. package/dist/generators/task/files-rust/src/main.rs.template +12 -0
  61. package/dist/generators/task/files-typescript/Dockerfile.template +25 -0
  62. package/dist/generators/task/files-typescript/README.md.template +24 -0
  63. package/dist/generators/task/files-typescript/docs/architecture.md.template +18 -0
  64. package/dist/generators/task/files-typescript/docs/contracts.md.template +20 -0
  65. package/dist/generators/task/files-typescript/docs/operations.md.template +24 -0
  66. package/dist/generators/task/files-typescript/eslint.config.mjs.template +3 -0
  67. package/dist/generators/task/files-typescript/infra/task.ts.template +35 -0
  68. package/dist/generators/task/files-typescript/package.json.template +7 -0
  69. package/dist/generators/task/files-typescript/src/index.ts.template +6 -0
  70. package/dist/generators/task/files-typescript/src/main.ts.template +45 -0
  71. package/dist/generators/task/files-typescript/src/task.spec.ts.template +66 -0
  72. package/dist/generators/task/files-typescript/src/task.ts.template +71 -0
  73. package/dist/generators/task/files-typescript/tsconfig.app.json.template +13 -0
  74. package/dist/generators/task/files-typescript/tsconfig.json.template +19 -0
  75. package/dist/generators/task/files-typescript/tsconfig.spec.json.template +11 -0
  76. package/dist/generators/task/files-typescript/vitest.config.mts.template +9 -0
  77. package/dist/generators/task/schema.d.ts +8 -0
  78. package/dist/generators/task/schema.json +40 -0
  79. package/dist/generators/task/task.d.ts +5 -0
  80. package/dist/generators/task/task.d.ts.map +1 -0
  81. package/dist/generators/task/task.js +198 -0
  82. package/dist/generators.json +14 -0
  83. package/dist/index.d.ts +3 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +7 -0
  86. package/executors.json +3 -0
  87. package/generators.json +14 -0
  88. package/package.json +93 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Empire Builder Kit Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @empire-builder-kit/containers
2
+
3
+ Empire Builder Kit container workload generators.
4
+
5
+ ## Implemented Surface
6
+
7
+ - `@empire-builder-kit/containers:service`: scaffold a TypeScript-first backend service at `packages/<app>/apps/service`
8
+ - `@empire-builder-kit/containers:task`: scaffold a TypeScript-first backend task at `packages/<app>/apps/task`
9
+
10
+ ## Current Direction
11
+
12
+ This milestone focuses on a mergeable TypeScript-first backend slice: a minimal HTTP service plus a batch-oriented task with shared container build conventions, Nx targets, Vitest coverage, app-local docs, and generated SST/ECS composition templates with a consistent local-dev command contract. Broader language presets can land in follow-on milestones.
13
+
14
+ ## Building
15
+
16
+ Run `pnpm nx build @empire-builder-kit/containers` to build the library.
17
+
18
+ ## Running unit tests
19
+
20
+ Run `pnpm nx test @empire-builder-kit/containers` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,3 @@
1
+ {
2
+ "executors": {}
3
+ }
@@ -0,0 +1,11 @@
1
+ FROM golang:1-bookworm AS builder
2
+ WORKDIR /app
3
+ COPY go.mod go.sum* ./
4
+ RUN go mod download
5
+ COPY . .
6
+ RUN CGO_ENABLED=0 go build -o /service ./cmd/...
7
+
8
+ FROM gcr.io/distroless/static-debian12
9
+ COPY --from=builder /service /service
10
+ EXPOSE 3000
11
+ CMD ["/service"]
@@ -0,0 +1,11 @@
1
+ # <%= projectTitle %>
2
+
3
+ Go service for the `<%= app %>` app group.
4
+
5
+ ## Commands
6
+
7
+ - `pnpm nx run <%= projectName %>:build` — compile binary
8
+ - `pnpm nx run <%= projectName %>:dev` — run locally
9
+ - `pnpm nx run <%= projectName %>:test` — run tests
10
+ - `pnpm nx run <%= projectName %>:lint` — run golangci-lint
11
+ - `pnpm nx run <%= projectName %>:docker-build` — build container image
@@ -0,0 +1,34 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "log/slog"
6
+ "net/http"
7
+ "os"
8
+ )
9
+
10
+ type healthResponse struct {
11
+ Status string `json:"status"`
12
+ Service string `json:"service"`
13
+ }
14
+
15
+ func main() {
16
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
17
+ slog.SetDefault(logger)
18
+
19
+ mux := http.NewServeMux()
20
+ mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
21
+ w.Header().Set("Content-Type", "application/json")
22
+ json.NewEncoder(w).Encode(healthResponse{
23
+ Status: "ok",
24
+ Service: "<%= projectName %>",
25
+ })
26
+ })
27
+
28
+ addr := ":3000"
29
+ slog.Info("listening", "addr", addr)
30
+ if err := http.ListenAndServe(addr, mux); err != nil {
31
+ slog.Error("server failed", "error", err)
32
+ os.Exit(1)
33
+ }
34
+ }
@@ -0,0 +1,9 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Health Endpoint
4
+
5
+ - `GET /health` — returns `{ "status": "ok", "service": "<%= projectName %>" }`
6
+
7
+ ## Local Development
8
+
9
+ - `pnpm nx run <%= projectName %>:dev`
@@ -0,0 +1,3 @@
1
+ module github.com/org/<%= projectName %>
2
+
3
+ go 1.22
@@ -0,0 +1,59 @@
1
+ export interface Register<%= className %>ServiceArgs {
2
+ cluster: sst.aws.Cluster;
3
+ domain?: string | { name: string };
4
+ environment?: Record<string, string>;
5
+ link?: any[];
6
+ publicLoadBalancer?: boolean;
7
+ }
8
+
9
+ // Go service runs a static binary — no node/curl in the runtime image,
10
+ // so the Docker HEALTHCHECK is omitted here and the load balancer's
11
+ // target-group health check on `/health` gates traffic instead. Add a
12
+ // HEALTHCHECK back in Dockerfile + pass `health` here explicitly if you
13
+ // need task-level health visibility in ECS.
14
+ export function register<%= className %>Service({
15
+ cluster,
16
+ domain,
17
+ environment = {},
18
+ link = [],
19
+ publicLoadBalancer = false,
20
+ }: Register<%= className %>ServiceArgs) {
21
+ const service = new sst.aws.Service('<%= projectName %>', {
22
+ cluster,
23
+ image: {
24
+ context: './apps/service/<%= name %>',
25
+ dockerfile: 'Dockerfile',
26
+ },
27
+ dev: {
28
+ command: 'pnpm nx run <%= projectName %>:dev',
29
+ directory: '../../..',
30
+ url: 'http://127.0.0.1:3000',
31
+ },
32
+ environment: {
33
+ PORT: '3000',
34
+ SERVICE_NAME: '<%= className %>Service',
35
+ SERVICE_VERSION: '0.0.1',
36
+ ...environment,
37
+ },
38
+ link,
39
+ logging: {
40
+ retention: '1 month',
41
+ },
42
+ serviceRegistry: {
43
+ port: 3000,
44
+ },
45
+ loadBalancer: {
46
+ public: publicLoadBalancer,
47
+ ...(domain ? { domain } : {}),
48
+ rules: [{ listen: '80/http', forward: '3000/http' }],
49
+ health: {
50
+ '3000/http': {
51
+ path: '/health',
52
+ successCodes: '200-399',
53
+ },
54
+ },
55
+ },
56
+ });
57
+
58
+ return { service };
59
+ }
@@ -0,0 +1,12 @@
1
+ FROM python:3.12-slim AS builder
2
+ WORKDIR /app
3
+ COPY pyproject.toml ./
4
+ RUN pip install --no-cache-dir .
5
+
6
+ FROM python:3.12-slim
7
+ WORKDIR /app
8
+ COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
9
+ COPY --from=builder /usr/local/bin /usr/local/bin
10
+ COPY src/ src/
11
+ EXPOSE 3000
12
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "3000"]
@@ -0,0 +1,11 @@
1
+ # <%= projectTitle %>
2
+
3
+ Python (FastAPI) service for the `<%= app %>` app group.
4
+
5
+ ## Commands
6
+
7
+ - `pnpm nx run <%= projectName %>:dev` — run with auto-reload
8
+ - `pnpm nx run <%= projectName %>:test` — run pytest
9
+ - `pnpm nx run <%= projectName %>:lint` — run ruff
10
+ - `pnpm nx run <%= projectName %>:typecheck` — run mypy
11
+ - `pnpm nx run <%= projectName %>:docker-build` — build container image
@@ -0,0 +1,9 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Health Endpoint
4
+
5
+ - `GET /health` — returns `{ "status": "ok", "service": "<%= projectName %>" }`
6
+
7
+ ## Local Development
8
+
9
+ - `pnpm nx run <%= projectName %>:dev`
@@ -0,0 +1,68 @@
1
+ export interface Register<%= className %>ServiceArgs {
2
+ cluster: sst.aws.Cluster;
3
+ domain?: string | { name: string };
4
+ environment?: Record<string, string>;
5
+ link?: any[];
6
+ publicLoadBalancer?: boolean;
7
+ }
8
+
9
+ // Python service uses `python -c` for the Docker HEALTHCHECK — the
10
+ // runtime image already has python + stdlib `urllib.request`, so no
11
+ // extra system package is needed. The load balancer's target-group
12
+ // health check on `/health` still gates traffic independently.
13
+ export function register<%= className %>Service({
14
+ cluster,
15
+ domain,
16
+ environment = {},
17
+ link = [],
18
+ publicLoadBalancer = false,
19
+ }: Register<%= className %>ServiceArgs) {
20
+ const service = new sst.aws.Service('<%= projectName %>', {
21
+ cluster,
22
+ image: {
23
+ context: './apps/service/<%= name %>',
24
+ dockerfile: 'Dockerfile',
25
+ },
26
+ dev: {
27
+ command: 'pnpm nx run <%= projectName %>:dev',
28
+ directory: '../../..',
29
+ url: 'http://127.0.0.1:3000',
30
+ },
31
+ environment: {
32
+ PORT: '3000',
33
+ SERVICE_NAME: '<%= className %>Service',
34
+ SERVICE_VERSION: '0.0.1',
35
+ ...environment,
36
+ },
37
+ health: {
38
+ command: [
39
+ 'CMD-SHELL',
40
+ 'python -c "import urllib.request,sys;sys.exit(0 if urllib.request.urlopen(\'http://127.0.0.1:3000/health\',timeout=2).status<400 else 1)"',
41
+ ],
42
+ interval: '30 seconds',
43
+ timeout: '5 seconds',
44
+ retries: 3,
45
+ startPeriod: '10 seconds',
46
+ },
47
+ link,
48
+ logging: {
49
+ retention: '1 month',
50
+ },
51
+ serviceRegistry: {
52
+ port: 3000,
53
+ },
54
+ loadBalancer: {
55
+ public: publicLoadBalancer,
56
+ ...(domain ? { domain } : {}),
57
+ rules: [{ listen: '80/http', forward: '3000/http' }],
58
+ health: {
59
+ '3000/http': {
60
+ path: '/health',
61
+ successCodes: '200-399',
62
+ },
63
+ },
64
+ },
65
+ });
66
+
67
+ return { service };
68
+ }
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "<%= projectName %>"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.12"
5
+ dependencies = [
6
+ "fastapi>=0.115",
7
+ "uvicorn[standard]>=0.34",
8
+ ]
9
+
10
+ [project.optional-dependencies]
11
+ dev = [
12
+ "pytest>=8",
13
+ "ruff>=0.8",
14
+ "mypy>=1.13",
15
+ ]
@@ -0,0 +1,20 @@
1
+ """<%= projectTitle %> — FastAPI service for the <%= app %> app group."""
2
+
3
+ import logging
4
+ import sys
5
+
6
+ from fastapi import FastAPI
7
+
8
+ logging.basicConfig(
9
+ stream=sys.stdout,
10
+ level=logging.INFO,
11
+ format="%(message)s",
12
+ )
13
+ logger = logging.getLogger("<%= projectName %>")
14
+
15
+ app = FastAPI(title="<%= projectTitle %>")
16
+
17
+
18
+ @app.get("/health")
19
+ async def health():
20
+ return {"status": "ok", "service": "<%= projectName %>"}
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "<%= projectName %>"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [[bin]]
7
+ name = "service"
8
+ path = "src/main.rs"
9
+
10
+ [dependencies]
11
+ axum = "0.8"
12
+ serde = { version = "1", features = ["derive"] }
13
+ serde_json = "1"
14
+ tokio = { version = "1", features = ["full"] }
15
+ tracing = "0.1"
16
+ tracing-subscriber = { version = "0.3", features = ["json"] }
@@ -0,0 +1,12 @@
1
+ FROM rust:1-bookworm AS builder
2
+ WORKDIR /app
3
+ COPY Cargo.toml Cargo.lock* ./
4
+ RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src
5
+ COPY src/ src/
6
+ RUN cargo build --release
7
+
8
+ FROM debian:bookworm-slim
9
+ RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
10
+ COPY --from=builder /app/target/release/service /usr/local/bin/service
11
+ EXPOSE 3000
12
+ CMD ["service"]
@@ -0,0 +1,11 @@
1
+ # <%= projectTitle %>
2
+
3
+ Rust service for the `<%= app %>` app group.
4
+
5
+ ## Commands
6
+
7
+ - `pnpm nx run <%= projectName %>:build` — compile release binary
8
+ - `pnpm nx run <%= projectName %>:dev` — run with cargo-watch
9
+ - `pnpm nx run <%= projectName %>:test` — run tests
10
+ - `pnpm nx run <%= projectName %>:lint` — run clippy
11
+ - `pnpm nx run <%= projectName %>:docker-build` — build container image
@@ -0,0 +1,9 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Health Endpoint
4
+
5
+ - `GET /health` — returns `{ "status": "ok", "service": "<%= projectName %>" }`
6
+
7
+ ## Local Development
8
+
9
+ - `pnpm nx run <%= projectName %>:dev`
@@ -0,0 +1,59 @@
1
+ export interface Register<%= className %>ServiceArgs {
2
+ cluster: sst.aws.Cluster;
3
+ domain?: string | { name: string };
4
+ environment?: Record<string, string>;
5
+ link?: any[];
6
+ publicLoadBalancer?: boolean;
7
+ }
8
+
9
+ // Rust service runs a native binary inside debian-slim — no node/curl in
10
+ // the runtime image, so the Docker HEALTHCHECK is omitted here and the
11
+ // load balancer's target-group health check on `/health` gates traffic
12
+ // instead. Add a HEALTHCHECK back in Dockerfile + pass `health` here
13
+ // explicitly if you need task-level health visibility in ECS.
14
+ export function register<%= className %>Service({
15
+ cluster,
16
+ domain,
17
+ environment = {},
18
+ link = [],
19
+ publicLoadBalancer = false,
20
+ }: Register<%= className %>ServiceArgs) {
21
+ const service = new sst.aws.Service('<%= projectName %>', {
22
+ cluster,
23
+ image: {
24
+ context: './apps/service/<%= name %>',
25
+ dockerfile: 'Dockerfile',
26
+ },
27
+ dev: {
28
+ command: 'pnpm nx run <%= projectName %>:dev',
29
+ directory: '../../..',
30
+ url: 'http://127.0.0.1:3000',
31
+ },
32
+ environment: {
33
+ PORT: '3000',
34
+ SERVICE_NAME: '<%= className %>Service',
35
+ SERVICE_VERSION: '0.0.1',
36
+ ...environment,
37
+ },
38
+ link,
39
+ logging: {
40
+ retention: '1 month',
41
+ },
42
+ serviceRegistry: {
43
+ port: 3000,
44
+ },
45
+ loadBalancer: {
46
+ public: publicLoadBalancer,
47
+ ...(domain ? { domain } : {}),
48
+ rules: [{ listen: '80/http', forward: '3000/http' }],
49
+ health: {
50
+ '3000/http': {
51
+ path: '/health',
52
+ successCodes: '200-399',
53
+ },
54
+ },
55
+ },
56
+ });
57
+
58
+ return { service };
59
+ }
@@ -0,0 +1,29 @@
1
+ use axum::{routing::get, Json, Router};
2
+ use serde::Serialize;
3
+ use std::net::SocketAddr;
4
+
5
+ #[derive(Serialize)]
6
+ struct HealthResponse {
7
+ status: &'static str,
8
+ service: &'static str,
9
+ }
10
+
11
+ async fn health() -> Json<HealthResponse> {
12
+ Json(HealthResponse {
13
+ status: "ok",
14
+ service: "<%= projectName %>",
15
+ })
16
+ }
17
+
18
+ #[tokio::main]
19
+ async fn main() {
20
+ tracing_subscriber::fmt().json().init();
21
+
22
+ let app = Router::new().route("/health", get(health));
23
+
24
+ let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
25
+ tracing::info!("listening on {}", addr);
26
+
27
+ let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
28
+ axum::serve(listener, app).await.unwrap();
29
+ }
@@ -0,0 +1,30 @@
1
+ # syntax=docker/dockerfile:1.7
2
+
3
+ FROM node:22-bookworm-slim AS build
4
+ WORKDIR /workspace
5
+
6
+ ENV CI=true
7
+
8
+ RUN corepack enable
9
+
10
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml nx.json tsconfig.base.json tsconfig.json eslint.config.mjs ./
11
+ COPY packages ./packages
12
+ COPY tools ./tools
13
+
14
+ RUN pnpm install --frozen-lockfile
15
+ RUN pnpm nx run <%= projectName %>:build
16
+
17
+ FROM node:22-bookworm-slim AS runtime
18
+ WORKDIR /app
19
+
20
+ ENV NODE_ENV=production
21
+ ENV PORT=3000
22
+ ENV SERVICE_NAME=<%= className %>Service
23
+
24
+ COPY --from=build /workspace/dist/<%= projectRoot %>/ ./
25
+
26
+ EXPOSE 3000
27
+
28
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || '3000') + '/health').then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))"
29
+
30
+ CMD ["node", "main.js"]
@@ -0,0 +1,24 @@
1
+ # <%= projectTitle %>
2
+
3
+ TypeScript-first backend container service for the `<%= appName %>` app group.
4
+
5
+ ## Purpose
6
+
7
+ This package provides a first mergeable ECS-style service slice with a health endpoint, structured log output, and container build conventions that fit the Empire Builder Kit app-group layout.
8
+
9
+ ## Nx Targets
10
+
11
+ - `pnpm nx run <%= projectName %>:build`
12
+ - `pnpm nx run <%= projectName %>:lint`
13
+ - `pnpm nx run <%= projectName %>:typecheck`
14
+ - `pnpm nx run <%= projectName %>:test`
15
+ - `pnpm nx run <%= projectName %>:dev`
16
+ - `pnpm nx run <%= projectName %>:docker-build`
17
+
18
+ ## Current Conventions
19
+
20
+ - keep long-running backend code in `src/`
21
+ - expose health behavior through `GET /health`
22
+ - prefer structured JSON logs so ECS and CloudWatch parsing stay predictable
23
+ - keep SST composition scaffolding in `infra/service.ts` so app-group infra can opt into the workload without rewriting the local-dev contract
24
+ - treat this package as app-group-internal until a supported SDK or API contract is published
@@ -0,0 +1,18 @@
1
+ # <%= projectTitle %> Architecture
2
+
3
+ ## Scope
4
+
5
+ `<%= projectName %>` is the first TypeScript-first container workload slice for `<%= appName %>`. It gives the app group a long-running backend process under `packages/<%= appName %>/apps/service` without introducing language-preset sprawl in the first milestone.
6
+
7
+ ## Runtime Shape
8
+
9
+ - `src/main.ts` starts a small Node HTTP server
10
+ - `src/service.ts` keeps config, health payloads, and structured log formatting testable
11
+ - `GET /health` is the default readiness endpoint for local and container checks
12
+ - `infra/service.ts` captures the recommended SST `Cluster` + `Service` composition seam, image path, service registry, and `sst dev` command contract
13
+
14
+ ## Current Boundaries
15
+
16
+ - keep app-specific background processing and internal APIs here
17
+ - publish cross-app contracts through the owning app group's SDK instead of importing internals directly
18
+ - compose this workload into the owning app group's root `infra/` entrypoint instead of letting app code instantiate SST resources ad hoc
@@ -0,0 +1,20 @@
1
+ # <%= projectTitle %> Contracts
2
+
3
+ ## Local Development
4
+
5
+ - `pnpm nx run <%= projectName %>:dev` is the canonical local command for this workload.
6
+ - `infra/service.ts` passes the same command to SST via `dev.command` so `sst dev` and direct Nx usage stay aligned.
7
+ - `PORT`, `SERVICE_NAME`, and `SERVICE_VERSION` are the supported starter env overrides.
8
+
9
+ ## Infra Composition
10
+
11
+ - import `register<%= className %>Service` from `apps/service/infra/service` into the owning app group's root `infra/` entrypoint
12
+ - provide an existing `sst.aws.Cluster` from app-group infrastructure instead of constructing a cluster inside runtime code
13
+ - use the generated `GET /health` path as the default ECS and load balancer health contract
14
+ - pass linked SST resources through `link` so runtime code can consume them through `Resource.*`
15
+
16
+ ## Deployment Defaults
17
+
18
+ - image build context stays rooted at `packages/<%= appName %>/apps/service`
19
+ - local dev assumes `http://127.0.0.1:3000`
20
+ - load-balanced deployments default to forwarding `80/http` to `3000/http`
@@ -0,0 +1,24 @@
1
+ # <%= projectTitle %> Operations
2
+
3
+ ## Local Verification
4
+
5
+ - `pnpm nx run <%= projectName %>:build`
6
+ - `pnpm nx run <%= projectName %>:test`
7
+ - `pnpm nx run <%= projectName %>:dev`
8
+ - `node dist/<%= projectRoot %>/main.js`
9
+
10
+ Set `PORT`, `SERVICE_NAME`, or `SERVICE_VERSION` in the environment to override the defaults exposed by the starter service.
11
+
12
+ `<%= projectName %>:dev` uses `@swc-node/register` so the same command can be handed to `sst dev` from `infra/service.ts` without introducing a second local entrypoint contract.
13
+
14
+ ## Container Workflow
15
+
16
+ - `pnpm nx run <%= projectName %>:docker-build`
17
+ - image build runs the workspace install plus `pnpm nx run <%= projectName %>:build`
18
+ - the container exposes `GET /health` and ships with a matching Docker `HEALTHCHECK`
19
+ - `infra/service.ts` wires the same health path into SST load balancer and ECS health checks for a mergeable default
20
+
21
+ ## Next Steps
22
+
23
+ - replace the starter route handling with app-specific workload logic
24
+ - import `register<%= className %>Service` into the owning app group's `infra/` entrypoint when the workload is ready to deploy
@@ -0,0 +1,3 @@
1
+ import baseConfig from '<%= offsetFromRoot %>eslint.config.mjs';
2
+
3
+ export default [...baseConfig];
@@ -0,0 +1,64 @@
1
+ export interface Register<%= className %>ServiceArgs {
2
+ cluster: sst.aws.Cluster;
3
+ domain?: string | { name: string };
4
+ environment?: Record<string, string>;
5
+ link?: any[];
6
+ publicLoadBalancer?: boolean;
7
+ }
8
+
9
+ export function register<%= className %>Service({
10
+ cluster,
11
+ domain,
12
+ environment = {},
13
+ link = [],
14
+ publicLoadBalancer = false,
15
+ }: Register<%= className %>ServiceArgs) {
16
+ const service = new sst.aws.Service('<%= projectName %>', {
17
+ cluster,
18
+ image: {
19
+ context: './apps/service/<%= name %>',
20
+ dockerfile: 'Dockerfile',
21
+ },
22
+ dev: {
23
+ command: 'pnpm nx run <%= projectName %>:dev',
24
+ directory: '../../..',
25
+ url: 'http://127.0.0.1:3000',
26
+ },
27
+ environment: {
28
+ PORT: '3000',
29
+ SERVICE_NAME: '<%= className %>Service',
30
+ SERVICE_VERSION: '0.0.1',
31
+ ...environment,
32
+ },
33
+ health: {
34
+ command: [
35
+ 'CMD-SHELL',
36
+ 'node -e "fetch(\'http://127.0.0.1:3000/health\').then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))"',
37
+ ],
38
+ interval: '30 seconds',
39
+ timeout: '5 seconds',
40
+ retries: 3,
41
+ startPeriod: '10 seconds',
42
+ },
43
+ link,
44
+ logging: {
45
+ retention: '1 month',
46
+ },
47
+ serviceRegistry: {
48
+ port: 3000,
49
+ },
50
+ loadBalancer: {
51
+ public: publicLoadBalancer,
52
+ ...(domain ? { domain } : {}),
53
+ rules: [{ listen: '80/http', forward: '3000/http' }],
54
+ health: {
55
+ '3000/http': {
56
+ path: '/health',
57
+ successCodes: '200-399',
58
+ },
59
+ },
60
+ },
61
+ });
62
+
63
+ return { service };
64
+ }