@tdk-landscape/tdk-cli-core 1.3.88 → 1.3.90

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.
@@ -0,0 +1,39 @@
1
+ export function getElysiaIndexTemplate(name) {
2
+ return `import { Elysia } from 'elysia';
3
+
4
+ const port = Number(process.env.PORT || 3000);
5
+
6
+ const app = new Elysia()
7
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
8
+ .get('/health', () => ({ status: 'ok', service: '${name}' }))
9
+ .get('/health/live', () => ({ status: 'alive', timestamp: Date.now() }))
10
+ .get('/health/ready', ({ set }) => {
11
+ const dependencies: Record<string, string> = {};
12
+ let allReady = true;
13
+
14
+ // Add dependency checks here (database, cache, etc.)
15
+ // Mark allReady = false if any dependency is unhealthy
16
+
17
+ set.status = allReady ? 200 : 503;
18
+ return { status: allReady ? 'ready' : 'not_ready', dependencies };
19
+ })
20
+ .get('/', () => ({
21
+ service: '${name}',
22
+ version: '1.0.0',
23
+ endpoints: ['/health', '/health/live', '/health/ready']
24
+ }))
25
+ // Bind beyond loopback: Traefik reaches the container over the Docker network.
26
+ .listen({ port, hostname: '0.0.0.0' });
27
+
28
+ console.log('\\nšŸš€ ${name} running on http://localhost:' + app.server?.port);
29
+ console.log('šŸ“Š Health check: http://localhost:' + app.server?.port + '/health\\n');
30
+ `;
31
+ }
32
+ export const elysiaBackendProvider = {
33
+ id: "elysia",
34
+ label: "Bun + Elysia",
35
+ dependencies: { elysia: "^1.3.0" },
36
+ devDependencies: {},
37
+ createIndex: getElysiaIndexTemplate,
38
+ };
39
+ //# sourceMappingURL=elysia.js.map
@@ -0,0 +1,44 @@
1
+ export function getFastifyIndexTemplate(name) {
2
+ return `import Fastify from 'fastify';
3
+
4
+ const app = Fastify();
5
+
6
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
7
+ app.get('/health', async () => ({ status: 'ok', service: '${name}' }));
8
+
9
+ app.get('/health/live', async () => ({ status: 'alive', timestamp: Date.now() }));
10
+
11
+ app.get('/health/ready', async (_request, reply) => {
12
+ const dependencies: Record<string, string> = {};
13
+ let allReady = true;
14
+
15
+ // Add dependency checks here (database, cache, etc.)
16
+ // Mark allReady = false if any dependency is unhealthy
17
+
18
+ const status = allReady ? 'ready' : 'not_ready';
19
+ return reply.code(allReady ? 200 : 503).send({ status, dependencies });
20
+ });
21
+
22
+ app.get('/', async () => ({
23
+ service: '${name}',
24
+ version: '1.0.0',
25
+ endpoints: ['/health', '/health/live', '/health/ready']
26
+ }));
27
+
28
+ const port = Number(process.env.PORT || 3000);
29
+
30
+ // Bind beyond loopback: Traefik reaches the container over the Docker network.
31
+ await app.listen({ port, host: '0.0.0.0' });
32
+
33
+ console.log('\\nšŸš€ ${name} running on http://localhost:' + port);
34
+ console.log('šŸ“Š Health check: http://localhost:' + port + '/health\\n');
35
+ `;
36
+ }
37
+ export const fastifyBackendProvider = {
38
+ id: "fastify",
39
+ label: "Bun + Fastify",
40
+ dependencies: { fastify: "^5.0.0" },
41
+ devDependencies: {},
42
+ createIndex: getFastifyIndexTemplate,
43
+ };
44
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1,56 @@
1
+ export function getNestIndexTemplate(name) {
2
+ return `import 'reflect-metadata';
3
+ import { Controller, Get, Module } from '@nestjs/common';
4
+ import { NestFactory } from '@nestjs/core';
5
+
6
+ @Controller()
7
+ class AppController {
8
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
9
+ @Get('health')
10
+ health() {
11
+ return { status: 'ok', service: '${name}' };
12
+ }
13
+
14
+ @Get('health/live')
15
+ live() {
16
+ return { status: 'alive', timestamp: Date.now() };
17
+ }
18
+
19
+ @Get()
20
+ root() {
21
+ return { service: '${name}', version: '1.0.0', endpoints: ['/health', '/health/live'] };
22
+ }
23
+ }
24
+
25
+ @Module({ controllers: [AppController] })
26
+ class AppModule {}
27
+
28
+ async function bootstrap() {
29
+ const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log'] });
30
+ const port = Number(process.env.PORT || 3000);
31
+
32
+ // Bind beyond loopback: Traefik reaches the container over the Docker network.
33
+ await app.listen(port, '0.0.0.0');
34
+ console.log('\\nšŸš€ ${name} running on http://localhost:' + port);
35
+ console.log('šŸ“Š Health check: http://localhost:' + port + '/health\\n');
36
+ }
37
+
38
+ bootstrap();
39
+ `;
40
+ }
41
+ export const nestBackendProvider = {
42
+ id: "nestjs",
43
+ label: "Bun + NestJS",
44
+ dependencies: {
45
+ "@nestjs/common": "^11.0.0",
46
+ "@nestjs/core": "^11.0.0",
47
+ "@nestjs/platform-express": "^11.0.0",
48
+ "reflect-metadata": "^0.2.2",
49
+ rxjs: "^7.8.1",
50
+ },
51
+ devDependencies: {},
52
+ // Nest's decorators need these; the engine's generated Docker tsconfig already sets them.
53
+ compilerOptions: { experimentalDecorators: true, emitDecoratorMetadata: true },
54
+ createIndex: getNestIndexTemplate,
55
+ };
56
+ //# sourceMappingURL=nestjs.js.map
@@ -1,6 +1,9 @@
1
1
  import { getBackendIndexTemplate } from "../backend-languages/bun.js";
2
2
  import { TdkError } from "../utils/errors.js";
3
+ import { elysiaBackendProvider } from "./elysia.js";
3
4
  import { expressBackendProvider } from "./express.js";
5
+ import { fastifyBackendProvider } from "./fastify.js";
6
+ import { nestBackendProvider } from "./nestjs.js";
4
7
  /** Hono is the historical default; omitting --framework keeps the exact historical output. */
5
8
  export const DEFAULT_BACKEND_FRAMEWORK = "hono";
6
9
  export const honoBackendProvider = {
@@ -13,6 +16,9 @@ export const honoBackendProvider = {
13
16
  export const BACKEND_FRAMEWORKS = {
14
17
  hono: honoBackendProvider,
15
18
  express: expressBackendProvider,
19
+ elysia: elysiaBackendProvider,
20
+ fastify: fastifyBackendProvider,
21
+ nestjs: nestBackendProvider,
16
22
  };
17
23
  export function getBackendFramework(frameworkId) {
18
24
  const id = (frameworkId ?? DEFAULT_BACKEND_FRAMEWORK).trim().toLowerCase();
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Go has no reload process inside the image, so the dev command is only what a developer would run by
3
+ * hand. Tilt rebuilds the image when a watched file changes (the engine returns no live-update rules).
4
+ */
5
+ export const GO_DEV_COMMAND = "go run .";
6
+ export const goBackendProvider = {
7
+ id: "go",
8
+ label: "Go 1.23 (net/http)",
9
+ devCommand: GO_DEV_COMMAND,
10
+ watch: ["**/*.go", "go.mod", "go.sum"],
11
+ installHint: "go mod tidy # optional: Docker builds the image for you",
12
+ createFiles(name) {
13
+ return [
14
+ {
15
+ filename: "go.mod",
16
+ content: `module ${name}
17
+
18
+ go 1.23
19
+ `,
20
+ description: "Generating go.mod",
21
+ emoji: "šŸ“¦",
22
+ },
23
+ {
24
+ filename: "main.go",
25
+ content: `package main
26
+
27
+ import (
28
+ "encoding/json"
29
+ "log"
30
+ "net/http"
31
+ "os"
32
+ )
33
+
34
+ func writeJSON(w http.ResponseWriter, status int, body any) {
35
+ w.Header().Set("Content-Type", "application/json")
36
+ w.WriteHeader(status)
37
+ _ = json.NewEncoder(w).Encode(body)
38
+ }
39
+
40
+ func newMux() *http.ServeMux {
41
+ mux := http.NewServeMux()
42
+
43
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
44
+ mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
45
+ writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "service": "${name}"})
46
+ })
47
+
48
+ mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
49
+ writeJSON(w, http.StatusOK, map[string]any{
50
+ "service": "${name}",
51
+ "version": "1.0.0",
52
+ "endpoints": []string{"/health"},
53
+ })
54
+ })
55
+
56
+ return mux
57
+ }
58
+
59
+ func main() {
60
+ port := os.Getenv("PORT")
61
+ if port == "" {
62
+ port = "3000"
63
+ }
64
+
65
+ // ":" + port listens on every interface, which Traefik needs inside the container network.
66
+ log.Printf("${name} listening on :%s", port)
67
+ log.Fatal(http.ListenAndServe(":"+port, newMux()))
68
+ }
69
+ `,
70
+ description: "Generating backend source",
71
+ emoji: "šŸ’»",
72
+ },
73
+ {
74
+ filename: "main_test.go",
75
+ content: `package main
76
+
77
+ import (
78
+ "net/http"
79
+ "net/http/httptest"
80
+ "testing"
81
+ )
82
+
83
+ func TestHealth(t *testing.T) {
84
+ rec := httptest.NewRecorder()
85
+ newMux().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/health", nil))
86
+
87
+ if rec.Code != http.StatusOK {
88
+ t.Fatalf("GET /health = %d, want %d", rec.Code, http.StatusOK)
89
+ }
90
+ }
91
+ `,
92
+ description: "Generating smoke test",
93
+ emoji: "🧪",
94
+ },
95
+ ];
96
+ },
97
+ };
98
+ //# sourceMappingURL=go.js.map
@@ -1,10 +1,14 @@
1
1
  import { TdkError } from "../utils/errors.js";
2
2
  import { bunBackendProvider } from "./bun.js";
3
+ import { goBackendProvider } from "./go.js";
3
4
  import { pythonBackendProvider } from "./python.js";
5
+ import { rustBackendProvider } from "./rust.js";
4
6
  export const DEFAULT_BACKEND_LANGUAGE = "bun";
5
7
  export const BACKEND_LANGUAGES = {
6
8
  bun: bunBackendProvider,
7
9
  python: pythonBackendProvider,
10
+ go: goBackendProvider,
11
+ rust: rustBackendProvider,
8
12
  };
9
13
  export function getBackendLanguage(languageId) {
10
14
  const id = (languageId ?? DEFAULT_BACKEND_LANGUAGE).trim().toLowerCase();
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Rust is compiled into the image and has no reload process, so the dev command is only what a developer
3
+ * would run by hand. Tilt rebuilds the image when a watched file changes (the engine returns no live-update rules).
4
+ */
5
+ export const RUST_DEV_COMMAND = "cargo run";
6
+ export const rustBackendProvider = {
7
+ id: "rust",
8
+ label: "Rust (axum)",
9
+ devCommand: RUST_DEV_COMMAND,
10
+ watch: ["src/**/*.rs", "Cargo.toml", "Cargo.lock"],
11
+ installHint: "cargo build # optional: Docker builds the image for you",
12
+ createFiles(name) {
13
+ return [
14
+ {
15
+ filename: "Cargo.toml",
16
+ content: `[package]
17
+ name = "${name}"
18
+ version = "0.1.0"
19
+ edition = "2021"
20
+
21
+ # The image copies the release binary by this fixed name.
22
+ [[bin]]
23
+ name = "app"
24
+ path = "src/main.rs"
25
+
26
+ [dependencies]
27
+ axum = "0.8"
28
+ serde_json = "1"
29
+ tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
30
+ `,
31
+ description: "Generating Cargo.toml",
32
+ emoji: "šŸ“¦",
33
+ },
34
+ {
35
+ filename: "src/main.rs",
36
+ content: `use axum::{routing::get, Json, Router};
37
+ use serde_json::{json, Value};
38
+
39
+ fn app() -> Router {
40
+ Router::new()
41
+ .route("/", get(root))
42
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
43
+ .route("/health", get(health))
44
+ }
45
+
46
+ async fn root() -> Json<Value> {
47
+ Json(json!({ "service": "${name}", "version": "1.0.0", "endpoints": ["/health"] }))
48
+ }
49
+
50
+ async fn health() -> Json<Value> {
51
+ Json(json!({ "status": "ok", "service": "${name}" }))
52
+ }
53
+
54
+ #[tokio::main]
55
+ async fn main() {
56
+ let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
57
+
58
+ // 0.0.0.0 listens on every interface, which Traefik needs inside the container network.
59
+ let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
60
+ .await
61
+ .expect("failed to bind the port");
62
+ println!("${name} listening on 0.0.0.0:{port}");
63
+ axum::serve(listener, app()).await.expect("server error");
64
+ }
65
+
66
+ #[cfg(test)]
67
+ mod tests {
68
+ use super::*;
69
+
70
+ #[tokio::test]
71
+ async fn health_reports_ok() {
72
+ let Json(body) = health().await;
73
+ assert_eq!(body["status"], "ok");
74
+ }
75
+ }
76
+ `,
77
+ description: "Generating backend source and smoke test",
78
+ emoji: "šŸ’»",
79
+ },
80
+ ];
81
+ },
82
+ };
83
+ //# sourceMappingURL=rust.js.map
@@ -154,11 +154,13 @@ export function createPackageJson(name, type, frameworkId) {
154
154
  }
155
155
  export function createResourceTsconfig(resourceType, frameworkId) {
156
156
  const framework = resolveFrontendFramework(resourceType, frameworkId);
157
+ const backendFramework = resolveBackendFramework(resourceType, frameworkId);
157
158
  return {
158
159
  ...TSCONFIG_TEMPLATE,
159
160
  compilerOptions: {
160
161
  ...TSCONFIG_TEMPLATE.compilerOptions,
161
162
  ...(framework?.compilerOptions ?? {}),
163
+ ...(backendFramework?.compilerOptions ?? {}),
162
164
  },
163
165
  };
164
166
  }
@@ -295,8 +297,8 @@ export const resourceCommand = new Command("resource")
295
297
  .description("Create a new resource (service) from scratch, or register an existing one")
296
298
  .argument("[name]", "Resource name (kebab-case)")
297
299
  .option("-t, --type <type>", "Resource type: backend, frontend, worker, bring-your-own, sdk", "backend")
298
- .option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik; backend hono (default), express")
299
- .option("--language <id>", "Backend language: bun (default), python")
300
+ .option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik, vanilla; backend hono (default), express, elysia, fastify, nestjs")
301
+ .option("--language <id>", "Backend language: bun (default), python, go, rust")
300
302
  .option("-s, --stack <stack>", "Stack to assign resource to", "default")
301
303
  .option("-p, --path <path>", "Custom path for resource directory")
302
304
  .option("--resource-path <path>", "Alias for --path (for backward compatibility)")
@@ -584,8 +586,13 @@ This file contains the resource configuration for TDK.
584
586
  // Create directory structure for new resources
585
587
  console.log(chalk.blue("\nšŸ“ Creating directory structure..."));
586
588
  mkdirSync(fullPath, { recursive: true });
587
- mkdirSync(resolve(fullPath, "src"), { recursive: true });
588
- mkdirSync(resolve(fullPath, "tests"), { recursive: true });
589
+ // A provider that owns its runtime files only gets the folders it writes into (Go keeps its files at the root).
590
+ const ownedTopLevel = new Set((backendLanguage?.createFiles?.(resourceName) ?? []).map((file) => file.filename.split("/")[0]));
591
+ for (const folder of ["src", "tests"]) {
592
+ if (!backendLanguage?.createFiles || ownedTopLevel.has(folder)) {
593
+ mkdirSync(resolve(fullPath, folder), { recursive: true });
594
+ }
595
+ }
589
596
  if (resourceType === "frontend") {
590
597
  mkdirSync(resolve(fullPath, "public"), { recursive: true });
591
598
  }
@@ -615,7 +622,7 @@ This file contains the resource configuration for TDK.
615
622
  {
616
623
  type: "json",
617
624
  filename: "tsconfig.json",
618
- content: createResourceTsconfig(resourceType, frontendFramework?.id),
625
+ content: createResourceTsconfig(resourceType, frontendFramework?.id ?? backendFramework?.id),
619
626
  description: "Generating tsconfig.json",
620
627
  emoji: "āš™ļø",
621
628
  },
@@ -5,6 +5,7 @@ import { qwikFrontendProvider } from "./qwik.js";
5
5
  import { reactFrontendProvider } from "./react.js";
6
6
  import { solidFrontendProvider } from "./solid.js";
7
7
  import { svelteFrontendProvider } from "./svelte.js";
8
+ import { vanillaFrontendProvider } from "./vanilla.js";
8
9
  import { vueFrontendProvider } from "./vue.js";
9
10
  export const DEFAULT_FRONTEND_FRAMEWORK = "react";
10
11
  export const FRONTEND_FRAMEWORKS = {
@@ -15,6 +16,7 @@ export const FRONTEND_FRAMEWORKS = {
15
16
  lit: litFrontendProvider,
16
17
  solid: solidFrontendProvider,
17
18
  qwik: qwikFrontendProvider,
19
+ vanilla: vanillaFrontendProvider,
18
20
  };
19
21
  export function getFrontendFramework(frameworkId) {
20
22
  const id = (frameworkId ?? DEFAULT_FRONTEND_FRAMEWORK).trim().toLowerCase();
@@ -0,0 +1,60 @@
1
+ export const vanillaFrontendProvider = {
2
+ id: "vanilla",
3
+ label: "Vanilla TypeScript + Vite",
4
+ dependencies: {},
5
+ devDependencies: {},
6
+ compilerOptions: {},
7
+ createFiles(name) {
8
+ return [
9
+ {
10
+ filename: "index.html",
11
+ content: `<!DOCTYPE html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="UTF-8" />
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
16
+ <title>${name}</title>
17
+ </head>
18
+ <body>
19
+ <div id="app"></div>
20
+ <script type="module" src="/src/main.ts"></script>
21
+ </body>
22
+ </html>
23
+ `,
24
+ description: "Generating HTML template",
25
+ emoji: "šŸ’»",
26
+ },
27
+ {
28
+ filename: "src/main.ts",
29
+ content: `import { renderApp } from './app';
30
+
31
+ const root = document.getElementById('app');
32
+ if (root) {
33
+ renderApp(root);
34
+ }
35
+ `,
36
+ description: "Generating vanilla entry",
37
+ emoji: "šŸ’»",
38
+ },
39
+ {
40
+ filename: "src/app.ts",
41
+ content: `export function renderApp(root: HTMLElement): void {
42
+ root.style.padding = '2rem';
43
+ root.style.fontFamily = 'system-ui';
44
+
45
+ const title = document.createElement('h1');
46
+ title.textContent = '${name}';
47
+
48
+ const note = document.createElement('p');
49
+ note.textContent = 'Frontend resource created with TDK';
50
+
51
+ root.replaceChildren(title, note);
52
+ }
53
+ `,
54
+ description: "Generating vanilla app",
55
+ emoji: "šŸ’»",
56
+ },
57
+ ];
58
+ },
59
+ };
60
+ //# sourceMappingURL=vanilla.js.map
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.88",
4
+ "version": "1.3.90",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
@@ -107,7 +107,7 @@
107
107
  "type": "string"
108
108
  },
109
109
  "appType": {
110
- "description": "Resource type determining build strategy:\n• backend - Hono/Express API server\n• frontend - Vite single-page application (React, Vue, Svelte, Preact, Lit, Solid or Qwik, see framework)\n• sdk - Shared library package\n• worker - Background job processor\n• migrator - Database migration runner\n• library - Shared code library\n• bring-your-own - Wrap an existing service. No app scaffold. Needs Dockerfile or --image.",
110
+ "description": "Resource type determining build strategy:\n• backend - Hono/Express API server\n• frontend - Vite single-page application (React, Vue, Svelte, Preact, Lit, Solid, Qwik or Vanilla TypeScript, see framework)\n• sdk - Shared library package\n• worker - Background job processor\n• migrator - Database migration runner\n• library - Shared code library\n• bring-your-own - Wrap an existing service. No app scaffold. Needs Dockerfile or --image.",
111
111
  "enum": [
112
112
  "backend",
113
113
  "frontend",
@@ -234,8 +234,12 @@
234
234
  "lit",
235
235
  "solid",
236
236
  "qwik",
237
+ "vanilla",
237
238
  "hono",
238
- "express"
239
+ "express",
240
+ "elysia",
241
+ "fastify",
242
+ "nestjs"
239
243
  ],
240
244
  "pattern": "^[a-z][a-z0-9-]*$",
241
245
  "type": "string"
@@ -244,7 +248,9 @@
244
248
  "description": "Backend language provider id used to scaffold and run the service. Only meaningful for backend resources; omitted means bun (legacy manifests). Valid ids come from the CLI provider registry.",
245
249
  "examples": [
246
250
  "bun",
247
- "python"
251
+ "python",
252
+ "go",
253
+ "rust"
248
254
  ],
249
255
  "pattern": "^[a-z][a-z0-9-]*$",
250
256
  "type": "string"
@@ -8,7 +8,14 @@
8
8
  BASE_PORT_BACKEND = 4000
9
9
 
10
10
  # Languages whose runtime files are owned by the provider (no Vite/tsconfig/Bun config).
11
- PROVIDER_OWNED_LANGUAGES = ["python"]
11
+ PROVIDER_OWNED_LANGUAGES = ["python", "go", "rust"]
12
+
13
+ # Files a provider-owned language must scaffold; checked before generating anything.
14
+ LANGUAGE_REQUIRED_FILES = {
15
+ "python": ["pyproject.toml", "src/main.py"],
16
+ "go": ["go.mod", "main.go"],
17
+ "rust": ["Cargo.toml", "src/main.rs"],
18
+ }
12
19
 
13
20
  _CURL = """RUN apt-get update && apt-get install -y --no-install-recommends curl \\
14
21
  && rm -rf /var/lib/apt/lists/*
@@ -42,9 +49,62 @@ EXPOSE {port}
42
49
  CMD ["sh", "-c", "uvicorn main:app --app-dir src --host 0.0.0.0 --port ${{PORT:-{port}}} --reload --reload-dir src"]
43
50
  """
44
51
 
52
+ _GO = """# Generated by TDK for a Go backend. Do not edit; change service.json instead.
53
+ FROM golang:1.23-bookworm AS build
54
+
55
+ WORKDIR /app/{res_path}
56
+
57
+ COPY {res_path}/go.* ./
58
+ RUN go mod download
59
+
60
+ COPY {res_path}/ ./
61
+ RUN CGO_ENABLED=0 go build -o /out/app .
62
+
63
+ FROM build AS test
64
+ RUN go test ./...
65
+
66
+ FROM debian:bookworm-slim AS production
67
+
68
+ {curl}
69
+ COPY --from=build /out/app /usr/local/bin/app
70
+
71
+ {healthcheck}
72
+ EXPOSE {port}
73
+
74
+ CMD ["app"]
75
+ """
76
+
77
+ _RUST = """# Generated by TDK for a Rust backend. Do not edit; change service.json instead.
78
+ # BuildKit cache mounts keep the registry and the target directory between builds, so a rebuild after a
79
+ # source change recompiles only the crate itself, not every dependency.
80
+ FROM rust:1-slim-bookworm AS build
81
+
82
+ WORKDIR /app/{res_path}
83
+
84
+ COPY {res_path}/ ./
85
+ RUN --mount=type=cache,target=/usr/local/cargo/registry \\
86
+ --mount=type=cache,target=/app/{res_path}/target \\
87
+ cargo build --release && mkdir -p /out && cp target/release/app /out/app
88
+
89
+ FROM build AS test
90
+ RUN --mount=type=cache,target=/usr/local/cargo/registry \\
91
+ --mount=type=cache,target=/app/{res_path}/target \\
92
+ cargo test
93
+
94
+ FROM debian:bookworm-slim AS production
95
+
96
+ {curl}
97
+ COPY --from=build /out/app /usr/local/bin/app
98
+
99
+ {healthcheck}
100
+ EXPOSE {port}
101
+
102
+ CMD ["app"]
103
+ """
104
+
45
105
  def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
46
106
  """Dockerfile text for a provider-owned backend language, or fail for an unknown one."""
47
- template = {"python": _PYTHON}.get(language)
107
+ template = {"python": _PYTHON, "go": _GO, "rust": _RUST}.get(language)
48
108
  if template == None:
49
109
  fail("No Dockerfile generator for backend language '{}'".format(language))
50
110
  return template.format(
@@ -56,7 +116,7 @@ def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
56
116
 
57
117
 
58
118
  def is_provider_owned_language(manifest):
59
- """True when the manifest selects Python (a missing language means Bun)."""
119
+ """True when the manifest selects a provider-owned language (a missing language means Bun)."""
60
120
  if not manifest:
61
121
  return False
62
122
  return manifest.get("appType", "backend") == "backend" and manifest.get("language", "bun") in PROVIDER_OWNED_LANGUAGES
@@ -8,6 +8,7 @@ load(
8
8
  "./dockerfile/language_dockerfile.star",
9
9
  "generate_language_dockerfile",
10
10
  "is_provider_owned_language",
11
+ "LANGUAGE_REQUIRED_FILES",
11
12
  )
12
13
  load("./config/dockerignore.star", "generate_dockerignore")
13
14
  load(
@@ -25,6 +26,7 @@ Docker = struct(
25
26
  migrator = generate_migrator_dockerfile,
26
27
  backend_language = generate_language_dockerfile,
27
28
  is_provider_owned_language = is_provider_owned_language,
29
+ language_required_files = LANGUAGE_REQUIRED_FILES,
28
30
  generate_dockerignore = generate_dockerignore,
29
31
  frontend_compose = generate_frontend_compose,
30
32
  backend_compose = generate_backend_compose_entry,
@@ -77,7 +77,7 @@ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_d
77
77
  resource_path
78
78
  ))
79
79
  print(" Docker build will resolve @{npm_scope}/* from node_modules")
80
- if framework not in ['react', 'vue', 'svelte', 'preact', 'lit', 'solid', 'qwik']:
80
+ if framework not in ['react', 'vue', 'svelte', 'preact', 'lit', 'solid', 'qwik', 'vanilla']:
81
81
  fail("Unknown frontend framework for TypeScript generation: " + str(framework))
82
82
 
83
83
  compiler_options = {
@@ -555,9 +555,11 @@ def generate_resource_health_check(resource_name, resource_path, manifest, check
555
555
  health_checks = []
556
556
 
557
557
  # Check 1: Required files exist
558
- frontend_entry = "src/main.ts" if manifest.get("framework", "react") in ["vue", "svelte", "lit"] else "src/main.tsx"
558
+ frontend_entry = "src/main.ts" if manifest.get("framework", "react") in ["vue", "svelte", "lit", "vanilla"] else "src/main.tsx"
559
559
  backend_files = {
560
560
  "python": ["pyproject.toml", "src/main.py"],
561
+ "go": ["go.mod", "main.go"],
562
+ "rust": ["Cargo.toml", "src/main.rs"],
561
563
  }.get(manifest.get("language", "bun"), ["package.json", "prisma/schema.prisma"])
562
564
  required_files = {
563
565
  "backend": backend_files,
@@ -573,8 +575,8 @@ if [ ! -f "{}" ]; then
573
575
  fi
574
576
  '''.format(req_file, req_file))
575
577
 
576
- # Check 2: Package.json is valid JSON (Python backends have no package.json)
577
- if manifest.get("language", "bun") != "python":
578
+ # Check 2: Package.json is valid JSON (Python and Go backends have no package.json)
579
+ if manifest.get("language", "bun") not in ["python", "go", "rust"]:
578
580
  health_checks.append('''
579
581
  if ! cat package.json | head -1 > /dev/null 2>&1; then
580
582
  echo "āŒ package.json is not valid"
@@ -0,0 +1,90 @@
1
+ # Vanilla TypeScript-specific Vite templates for frontend resources.
2
+
3
+ VANILLA_VITE_FRONTEND = """{header}
4
+ import {{ resolve }} from 'node:path';
5
+ import {{ defineConfig }} from 'vite';
6
+
7
+ export default defineConfig({{
8
+ base: '{base_path}',
9
+ build: {{
10
+ cssCodeSplit: false,
11
+ minify: false,
12
+ modulePreload: false,
13
+ rollupOptions: {{
14
+ output: {{
15
+ entryFileNames: '[name].js',
16
+ format: 'esm',
17
+ }},
18
+ }},
19
+ target: 'esnext',
20
+ }},
21
+ define: {{
22
+ global: 'globalThis',
23
+ }},
24
+ optimizeDeps: {{
25
+ include: [{optimize_deps_include}],
26
+ }},
27
+ plugins: [],
28
+ preview: {{
29
+ host: '0.0.0.0',
30
+ port: {port},
31
+ }},
32
+ resolve: {{
33
+ alias: {{
34
+ '@/autogenerated': resolve(__dirname),
35
+ '@': resolve(__dirname, '../src'),
36
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
37
+ {internal_aliases}
38
+ }},
39
+ }},
40
+ server: {{
41
+ host: '0.0.0.0',
42
+ port: {port},
43
+ hmr: {{
44
+ port: {hmr_port},
45
+ }},
46
+ {proxy_config}
47
+ }},
48
+ }});
49
+ """
50
+
51
+ VANILLA_VITE_FRONTEND_BUILD = """{header}
52
+ import {{ resolve }} from 'node:path';
53
+ import {{ defineConfig }} from 'vite';
54
+
55
+ export default defineConfig({{
56
+ mode: 'production',
57
+ base: '{base_path}',
58
+ build: {{
59
+ cssCodeSplit: false,
60
+ minify: true,
61
+ modulePreload: false,
62
+ rollupOptions: {{
63
+ output: {{
64
+ entryFileNames: '[name].js',
65
+ format: 'esm',
66
+ manualChunks: undefined,
67
+ }},
68
+ }},
69
+ target: 'esnext',
70
+ }},
71
+ define: {{
72
+ global: 'globalThis',
73
+ __DEV__: 'false',
74
+ 'process.env.NODE_ENV': '"production"',
75
+ }},
76
+ optimizeDeps: {{
77
+ include: [{optimize_deps_include}],
78
+ }},
79
+ plugins: [],
80
+ resolve: {{
81
+ alias: {{
82
+ '@/autogenerated': resolve(__dirname),
83
+ '@': resolve(__dirname, '../src'),
84
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
85
+ }},
86
+ conditions: ['browser', 'import', 'module', 'es2020', 'default'],
87
+ mainFields: ['browser', 'module', 'jsnext:main', 'jsnext', 'main'],
88
+ }},
89
+ }});
90
+ """
@@ -22,6 +22,7 @@ load('./frameworks/preact.star', 'PREACT_VITE_FRONTEND', 'PREACT_VITE_FRONTEND_B
22
22
  load('./frameworks/lit.star', 'LIT_VITE_FRONTEND', 'LIT_VITE_FRONTEND_BUILD')
23
23
  load('./frameworks/solid.star', 'SOLID_VITE_FRONTEND', 'SOLID_VITE_FRONTEND_BUILD')
24
24
  load('./frameworks/qwik.star', 'QWIK_VITE_FRONTEND', 'QWIK_VITE_FRONTEND_BUILD')
25
+ load('./frameworks/vanilla.star', 'VANILLA_VITE_FRONTEND', 'VANILLA_VITE_FRONTEND_BUILD')
25
26
  load('../../../platform/docker/networking/api_path_constants.star', 'get_frontend_base_path')
26
27
  load('./helpers.star',
27
28
  'build_header',
@@ -63,6 +64,10 @@ _FRAMEWORK_TEMPLATES = {
63
64
  'dev': QWIK_VITE_FRONTEND,
64
65
  'build': QWIK_VITE_FRONTEND_BUILD,
65
66
  },
67
+ 'vanilla': {
68
+ 'dev': VANILLA_VITE_FRONTEND,
69
+ 'build': VANILLA_VITE_FRONTEND_BUILD,
70
+ },
66
71
  }
67
72
 
68
73
  def _normalize_vite_base_path(base_path):
@@ -220,7 +220,14 @@ def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', la
220
220
 
221
221
  Python backends reload their own process after a sync (uvicorn --reload), so they have
222
222
  no Bun install step. A changed pyproject.toml is not synced, so Tilt rebuilds the image instead.
223
+ Go and Rust backends have no live update at all (see the early return).
223
224
  """
225
+ # Go and Rust are compiled into the image and have no reload process, so syncing sources would leave the
226
+ # old binary running. Return before any sync() step is built (Tilt rejects sync steps that are created
227
+ # but never used): no live-update rules means Tilt rebuilds the image when a watched file changes.
228
+ if language in ['go', 'rust']:
229
+ return []
230
+
224
231
  live_update_rules = []
225
232
  for sync_path in syncs:
226
233
  # Python images never read the npm/Bun registry config, and the engine does not
@@ -310,7 +310,7 @@ def _generate_all_configs_for_resource(
310
310
 
311
311
  app_type = manifest.get('appType', 'backend')
312
312
  is_frontend = resource_config.get('frontend', False) or app_type == 'frontend'
313
- # Python backends own their runtime files (package/pyproject, Dockerfile,
313
+ # Provider-owned backends (Python, Go) own their runtime files (package/pyproject, Dockerfile,
314
314
  # reload command). Bun-only configs below (Vite, tsconfig, Prisma, bunfig) are skipped.
315
315
  provider_owned = Docker.is_provider_owned_language(manifest)
316
316
  if provider_owned:
@@ -318,7 +318,7 @@ def _generate_all_configs_for_resource(
318
318
  # COPY error halfway through the image build.
319
319
  root = ctx.get('project_root', '')
320
320
  prefix = (root.rstrip('/') + '/' if root else '') + resource_path + '/'
321
- for required in ['pyproject.toml', 'src/main.py']:
321
+ for required in Docker.language_required_files.get(manifest.get('language'), []):
322
322
  if not os.path.exists(prefix + required):
323
323
  fail("{}: language '{}' requires {} but {}{} does not exist".format(
324
324
  resource_name, manifest.get('language'), required, resource_path + '/', required))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.88",
3
+ "version": "1.3.90",
4
4
  "description": "TDK CLI — start services on your laptop.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {