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

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 (35) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +22 -3
  3. package/cli/dist/backend-frameworks/express.js +50 -0
  4. package/cli/dist/backend-frameworks/registry.js +41 -0
  5. package/cli/dist/backend-frameworks/types.js +2 -0
  6. package/cli/dist/backend-languages/bun.js +53 -0
  7. package/cli/dist/backend-languages/python.js +89 -0
  8. package/cli/dist/backend-languages/registry.js +30 -0
  9. package/cli/dist/backend-languages/types.js +2 -0
  10. package/cli/dist/commands/resource.js +67 -51
  11. package/cli/dist/commands/up.js +1 -1
  12. package/cli/dist/frontend-frameworks/lit.js +65 -0
  13. package/cli/dist/frontend-frameworks/preact.js +63 -0
  14. package/cli/dist/frontend-frameworks/qwik.js +62 -0
  15. package/cli/dist/frontend-frameworks/registry.js +14 -3
  16. package/cli/dist/frontend-frameworks/solid.js +63 -0
  17. package/cli/dist/frontend-frameworks/svelte.js +68 -0
  18. package/cli/dist/utils/doctor-wiring.js +9 -1
  19. package/cli/dist/utils/service-manifest.js +2 -0
  20. package/cli/package.json +2 -2
  21. package/engine/schemas/service-schema.json +31 -4
  22. package/engine/topologies/platform/docker/compose/compose.star +9 -1
  23. package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +62 -0
  24. package/engine/topologies/platform/docker/index.star +7 -0
  25. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +10 -1
  26. package/engine/topologies/tilt/generators/validators.star +8 -4
  27. package/engine/topologies/tilt/generators/vite/frameworks/lit.star +90 -0
  28. package/engine/topologies/tilt/generators/vite/frameworks/preact.star +92 -0
  29. package/engine/topologies/tilt/generators/vite/frameworks/qwik.star +92 -0
  30. package/engine/topologies/tilt/generators/vite/frameworks/solid.star +92 -0
  31. package/engine/topologies/tilt/generators/vite/frameworks/svelte.star +92 -0
  32. package/engine/topologies/tilt/generators/vite/frontend.star +25 -0
  33. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +31 -4
  34. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +25 -6
  35. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ Notable changes to the `tdk` CLI. Versions match [npm](https://www.npmjs.com/pac
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ - Add an opt-in Python backend: `tdk resource <name> --type backend --language python` scaffolds a FastAPI service (`pyproject.toml`, `src/main.py`, a pytest smoke test) and TDK generates its Docker image (`python:3.12-slim`, with `test` and `production` stages). The choice is saved as `language` in `service.json` only when you pass the flag; omitting it keeps the existing Bun + Hono output byte for byte. Unknown ids and `--language` on non-backend types fail before any file is written. A runnable example is in `examples/one-backend-python`, and the provider contract is in `docs/backend-language-providers.md`. Node.js is not included.
8
+ - `tdk doctor` no longer reports "resource without a package.json" for a Python backend, and a Python service missing `pyproject.toml` or `src/main.py` now fails early with the missing file named.
9
+ - Live reload now works: Tilt `live_update` sync paths were relative to the project root while Tilt resolves them against `.tdk/.tdk-out`, so they never matched and every source edit rebuilt the image ("Found file(s) not matching any sync"). An edit under `src/` is now copied into the running container without a rebuild, for Bun and Python services. Dependency file changes (`package.json`, `pyproject.toml`) still rebuild the image.
10
+ - The service schema (`https://tdk-landscape.github.io/schema.service.json`) gains the `language` field (`bun`, `python`).
11
+ - Fix the routed health URL in the `one-backend` example README.
12
+
7
13
  ## 1.3.80 (2026-09-30)
8
14
 
9
15
  - Add `tdk doctor --json` with schema-versioned readiness, checks, and structured errors; diagnostics remain on stderr.
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  TDK CLI starts your services on your laptop. It is not a deploy and not a Compose file: define each service in `service.json`, then run `tdk up`. No Kubernetes is needed on the machine.
4
4
 
5
- Docker runs the containers. Tilt runs the development loop. TDK CLI writes that configuration. Production deployment stays with Helm, Argo CD, or Kustomize.
5
+ Stability: 1.x local dev. Generated files are a contract; verify with `tdk config verify`. Core CLI is MIT and needs no key. Premium is optional.
6
+
7
+ Docker runs the containers. Tilt watches services and live-updates containers while you code. TDK CLI writes the configuration Tilt uses. Production deployment stays with Helm, Argo CD, or Kustomize.
6
8
 
7
9
  [Website](https://tdk-landscape.github.io/tdk-website/) · [Quickstart](https://tdk-landscape.github.io/tdk-website/docs/quickstart/) · [Examples](https://tdk-landscape.github.io/tdk-website/docs/examples/) · [Awesome TDK](https://github.com/tdk-landscape/awesome-tdk-framework) · [Demo](https://tdk-landscape.github.io/tdk-demo-animation/) · [Report a bug](https://github.com/tdk-landscape/tdk-cli-core/issues)
8
10
 
@@ -16,10 +18,12 @@ tdk up shop
16
18
  curl http://api.shop.localhost/api/orders-api/health
17
19
  ```
18
20
 
19
- ![TDK scaffolding a backend and a frontend, then listing the stack](docs/demo.svg)
21
+ ![TDK scaffolding a backend and a frontend, then listing the stack](docs/assets/demo.svg)
20
22
 
21
23
  *Scaffolding a backend and frontend, then listing the stack.*
22
24
 
25
+ ![Top 9 places to use TDK CLI: scaffold a service, start a stack, hot reload, port management, auto-discovery, boot order, proxy routing, include infra, and config verify](docs/assets/tdk-cli-top-9-uses.jpg)
26
+
23
27
  If Helm, Compose, or your existing Tilt setup already gives you a working local environment, keep using it. TDK CLI is for engineers managing several services who want a clear local service contract and one command to start the stack.
24
28
 
25
29
  See [how TDK CLI works alongside Helm](https://tdk-landscape.github.io/tdk-website/docs/with-helm/), the [service schema](engine/schemas/service-schema.json), and the [project configuration schema](engine/schemas/project-schema.json).
@@ -57,16 +61,31 @@ curl -fsSL https://tdk-landscape.github.io/install.sh | sh
57
61
  - [Working alongside Helm](docs/with-helm.md)
58
62
  - [Configuration and editor schemas](docs/configuration.md)
59
63
  - [Runnable one-backend example](examples/one-backend/README.md)
64
+ - [Runnable Python backend example](examples/one-backend-python/README.md)
60
65
  - [Full multi-service example](examples/tdk-example/README.md)
61
66
  - [Features and license limits](docs/FEATURES.md)
62
67
  - [Honest comparison and known limits](docs/compare-honest.md)
68
+ - [Public claims registry](docs/claims.md)
69
+ - [Show HN draft](docs/drafts/show-hn.md)
63
70
  - [Architecture and repository map](docs/project-overview.md)
64
- - [Scale fixture measurements and caveats](docs/scale-bench.md)
71
+ - [Scale fixture measurements and caveats](docs/benchmarks/scale-bench.md)
65
72
 
66
73
  ## Requirements and support
67
74
 
68
75
  For the local runtime, install Docker (Desktop, OrbStack, or Colima; Engine 25+, Compose 2.20+) and [Tilt](https://docs.tilt.dev/install.html). Bun 1.2+ is used by the default generated services. TDK selects host ports from bounded fallback ranges for HTTP, HTTPS, and Postgres; set `TDK_HTTP_PORT`, `TDK_HTTPS_PORT`, or `TDK_POSTGRES_PORT` to override them. TDK supports macOS, Linux, and Windows through WSL2 Ubuntu; native Windows supports CLI inspection only. Run `tdk doctor` to check local readiness. See [WSL2 setup](docs/wsl2.md).
69
76
 
77
+ On native Windows, `tdk --version`, `tdk doctor`, and `tdk up --dry-run` are inspect-only commands. `tdk up` exits 2 with “Landscape startup needs Ubuntu on WSL2. Native Windows is inspect-only.”
78
+
79
+ ## License matrix
80
+
81
+ | Capability | Free | Premium |
82
+ | --- | --- | --- |
83
+ | `tdk up`, scaffold, Traefik, Postgres, Tilt live update, golden layers | yes | yes |
84
+ | Verdaccio, DDD scaffold, Sablier idle stop | no | key |
85
+ | Playwright, C4, AGENTS.md | free if generated in-repo | only if the implementation is downloaded with a key |
86
+
87
+ Core stays free. Premium is a separate key for the extras above; no key required to run `tdk up`.
88
+
70
89
  **Ecosystem map:** examples, articles, related tools, and notes live in [awesome-tdk-framework](https://github.com/tdk-landscape/awesome-tdk-framework). Star this repo (`tdk-cli-core`) if the CLI is what you run; use the awesome list to browse the rest.
71
90
 
72
91
  ## Contributing and license
@@ -0,0 +1,50 @@
1
+ export function getExpressIndexTemplate(name) {
2
+ return `import express from 'express';
3
+
4
+ const app = express();
5
+
6
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
7
+ app.get('/health', (_req, res) => {
8
+ res.json({ status: 'ok', service: '${name}' });
9
+ });
10
+
11
+ app.get('/health/live', (_req, res) => {
12
+ res.json({ status: 'alive', timestamp: Date.now() });
13
+ });
14
+
15
+ app.get('/health/ready', async (_req, res) => {
16
+ const dependencies: Record<string, string> = {};
17
+ let allReady = true;
18
+
19
+ // Add dependency checks here (database, cache, etc.)
20
+ // Mark allReady = false if any dependency is unhealthy
21
+
22
+ const status = allReady ? 'ready' : 'not_ready';
23
+ res.status(allReady ? 200 : 503).json({ status, dependencies });
24
+ });
25
+
26
+ app.get('/', (_req, res) => {
27
+ res.json({
28
+ service: '${name}',
29
+ version: '1.0.0',
30
+ endpoints: ['/health', '/health/live', '/health/ready']
31
+ });
32
+ });
33
+
34
+ const port = Number(process.env.PORT || 3000);
35
+
36
+ // Bind beyond loopback: Traefik reaches the container over the Docker network.
37
+ app.listen(port, '0.0.0.0', () => {
38
+ console.log('\\n🚀 ${name} running on http://localhost:' + port);
39
+ console.log('📊 Health check: http://localhost:' + port + '/health\\n');
40
+ });
41
+ `;
42
+ }
43
+ export const expressBackendProvider = {
44
+ id: "express",
45
+ label: "Bun + Express",
46
+ dependencies: { express: "^5.1.0" },
47
+ devDependencies: { "@types/express": "^5.0.0" },
48
+ createIndex: getExpressIndexTemplate,
49
+ };
50
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1,41 @@
1
+ import { getBackendIndexTemplate } from "../backend-languages/bun.js";
2
+ import { TdkError } from "../utils/errors.js";
3
+ import { expressBackendProvider } from "./express.js";
4
+ /** Hono is the historical default; omitting --framework keeps the exact historical output. */
5
+ export const DEFAULT_BACKEND_FRAMEWORK = "hono";
6
+ export const honoBackendProvider = {
7
+ id: "hono",
8
+ label: "Bun + Hono",
9
+ dependencies: { hono: "^4.0.0" },
10
+ devDependencies: {},
11
+ createIndex: getBackendIndexTemplate,
12
+ };
13
+ export const BACKEND_FRAMEWORKS = {
14
+ hono: honoBackendProvider,
15
+ express: expressBackendProvider,
16
+ };
17
+ export function getBackendFramework(frameworkId) {
18
+ const id = (frameworkId ?? DEFAULT_BACKEND_FRAMEWORK).trim().toLowerCase();
19
+ const provider = Object.hasOwn(BACKEND_FRAMEWORKS, id) ? BACKEND_FRAMEWORKS[id] : undefined;
20
+ if (!provider) {
21
+ const supportedIds = Object.keys(BACKEND_FRAMEWORKS).join(", ");
22
+ throw new TdkError(`Unknown backend framework "${frameworkId}". Supported frameworks: ${supportedIds}.`, [`Use one of: ${supportedIds}`, `Omit --framework to use ${DEFAULT_BACKEND_FRAMEWORK}`]);
23
+ }
24
+ return provider;
25
+ }
26
+ /**
27
+ * Returns the provider for an explicit `--framework` on a backend, or undefined when none was given
28
+ * (the historical Hono scaffold). Python owns its runtime, so a framework cannot be combined with it.
29
+ */
30
+ export function resolveBackendFramework(resourceType, frameworkId, language) {
31
+ if (resourceType !== "backend" || frameworkId === undefined)
32
+ return undefined;
33
+ if (language?.createFiles) {
34
+ throw new TdkError(`--framework cannot be combined with --language ${language.id}.`, [
35
+ `The ${language.id} provider owns its runtime and framework`,
36
+ "Drop --framework, or use --language bun",
37
+ ]);
38
+ }
39
+ return getBackendFramework(frameworkId);
40
+ }
41
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,53 @@
1
+ export function getBackendIndexTemplate(name) {
2
+ return `import { Hono } from 'hono';
3
+
4
+ const app = new Hono();
5
+
6
+ // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
7
+ app.get('/health', (c) => {
8
+ return c.json({ status: 'ok', service: '${name}' });
9
+ });
10
+
11
+ app.get('/health/live', (c) => {
12
+ return c.json({ status: 'alive', timestamp: Date.now() });
13
+ });
14
+
15
+ app.get('/health/ready', async (c) => {
16
+ const dependencies: Record<string, string> = {};
17
+ let allReady = true;
18
+
19
+ // Add dependency checks here (database, cache, etc.)
20
+ // Mark allReady = false if any dependency is unhealthy
21
+
22
+ const status = allReady ? 'ready' : 'not_ready';
23
+ return c.json({ status, dependencies }, allReady ? 200 : 503);
24
+ });
25
+
26
+ app.get('/', (c) => {
27
+ return c.json({
28
+ service: '${name}',
29
+ version: '1.0.0',
30
+ endpoints: ['/health', '/health/live', '/health/ready']
31
+ });
32
+ });
33
+
34
+ const port = process.env.PORT || 3000;
35
+ console.log('\\n🚀 ${name} running on http://localhost:' + port);
36
+ console.log('📊 Health check: http://localhost:' + port + '/health\\n');
37
+
38
+ export default {
39
+ port,
40
+ fetch: app.fetch,
41
+ };
42
+ `;
43
+ }
44
+ /**
45
+ * The historical default. Package scripts, tsconfig, Dockerfile, and the vitest smoke test stay in
46
+ * the shared scaffold so omitted-language output is byte-identical to before providers existed.
47
+ */
48
+ export const bunBackendProvider = {
49
+ id: "bun",
50
+ label: "Bun + Hono",
51
+ installHint: "bun install",
52
+ };
53
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1,89 @@
1
+ export const PYTHON_DEV_COMMAND = "uvicorn main:app --app-dir src --host 0.0.0.0 --port ${PORT:-4000} --reload --reload-dir src";
2
+ export const pythonBackendProvider = {
3
+ id: "python",
4
+ label: "Python 3.12 + FastAPI",
5
+ devCommand: PYTHON_DEV_COMMAND,
6
+ watch: ["src/**/*"],
7
+ installHint: 'pip install -e ".[dev]" # optional: Docker builds the image for you',
8
+ createFiles(name) {
9
+ return [
10
+ {
11
+ filename: "pyproject.toml",
12
+ content: `[project]
13
+ name = "${name}"
14
+ version = "0.0.1"
15
+ requires-python = ">=3.12"
16
+ dependencies = [
17
+ "fastapi==0.115.6",
18
+ "uvicorn[standard]==0.34.0",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "pytest==8.3.4",
24
+ "httpx==0.28.1",
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["setuptools>=68"]
29
+ build-backend = "setuptools.build_meta"
30
+
31
+ # Only the dependencies are installed into the image; the source is synced for live reload.
32
+ [tool.setuptools]
33
+ py-modules = []
34
+
35
+ [tool.pytest.ini_options]
36
+ pythonpath = ["src"]
37
+ testpaths = ["tests"]
38
+ `,
39
+ description: "Generating pyproject.toml",
40
+ emoji: "📦",
41
+ },
42
+ {
43
+ filename: "src/main.py",
44
+ content: `import os
45
+
46
+ from fastapi import FastAPI
47
+
48
+ app = FastAPI(title="${name}")
49
+
50
+
51
+ @app.get("/health")
52
+ def health() -> dict[str, str]:
53
+ return {"status": "ok", "service": "${name}"}
54
+
55
+
56
+ @app.get("/")
57
+ def root() -> dict[str, object]:
58
+ return {"service": "${name}", "version": "1.0.0", "endpoints": ["/health"]}
59
+
60
+
61
+ if __name__ == "__main__":
62
+ import uvicorn
63
+
64
+ uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "3000")))
65
+ `,
66
+ description: "Generating backend source",
67
+ emoji: "💻",
68
+ },
69
+ {
70
+ filename: "tests/test_health.py",
71
+ content: `from fastapi.testclient import TestClient
72
+
73
+ from main import app
74
+
75
+ client = TestClient(app)
76
+
77
+
78
+ def test_health() -> None:
79
+ response = client.get("/health")
80
+ assert response.status_code == 200
81
+ assert response.json() == {"status": "ok", "service": "${name}"}
82
+ `,
83
+ description: "Generating test file",
84
+ emoji: "🧪",
85
+ },
86
+ ];
87
+ },
88
+ };
89
+ //# sourceMappingURL=python.js.map
@@ -0,0 +1,30 @@
1
+ import { TdkError } from "../utils/errors.js";
2
+ import { bunBackendProvider } from "./bun.js";
3
+ import { pythonBackendProvider } from "./python.js";
4
+ export const DEFAULT_BACKEND_LANGUAGE = "bun";
5
+ export const BACKEND_LANGUAGES = {
6
+ bun: bunBackendProvider,
7
+ python: pythonBackendProvider,
8
+ };
9
+ export function getBackendLanguage(languageId) {
10
+ const id = (languageId ?? DEFAULT_BACKEND_LANGUAGE).trim().toLowerCase();
11
+ const provider = Object.hasOwn(BACKEND_LANGUAGES, id) ? BACKEND_LANGUAGES[id] : undefined;
12
+ if (!provider) {
13
+ const supportedIds = Object.keys(BACKEND_LANGUAGES).join(", ");
14
+ throw new TdkError(`Unknown backend language "${languageId}". Supported languages: ${supportedIds}.`, [`Use one of: ${supportedIds}`, `Omit --language to use ${DEFAULT_BACKEND_LANGUAGE}`]);
15
+ }
16
+ return provider;
17
+ }
18
+ /** Returns the provider for a backend, or undefined for other types. Rejects --language elsewhere. */
19
+ export function resolveBackendLanguage(resourceType, languageId) {
20
+ if (resourceType !== "backend") {
21
+ if (languageId !== undefined) {
22
+ throw new TdkError("--language can only be used with --type backend.", [
23
+ "Add --type backend, or drop --language for this resource type",
24
+ ]);
25
+ }
26
+ return undefined;
27
+ }
28
+ return getBackendLanguage(languageId);
29
+ }
30
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -2,6 +2,9 @@ import { existsSync, mkdirSync } from "node:fs";
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
3
  import chalk from "chalk";
4
4
  import { Command } from "commander";
5
+ import { resolveBackendFramework } from "../backend-frameworks/registry.js";
6
+ import { getBackendIndexTemplate } from "../backend-languages/bun.js";
7
+ import { resolveBackendLanguage } from "../backend-languages/registry.js";
5
8
  import { resolveFrontendFramework } from "../frontend-frameworks/registry.js";
6
9
  import { hasDddLicense } from "../generator/extension-fetch.js";
7
10
  import { CREATABLE_RESOURCE_TYPES } from "../types/index.js";
@@ -48,6 +51,7 @@ export const TYPE_SPECIFIC = {
48
51
  healthCheck: "/health",
49
52
  },
50
53
  };
54
+ const BYO_RESTART_POLICIES = ["no", "on-failure", "unless-stopped", "always"];
51
55
  export function resolveByoPort(value, assignedPort, resources) {
52
56
  if (!value)
53
57
  return assignedPort;
@@ -63,8 +67,12 @@ export function resolveByoPort(value, assignedPort, resources) {
63
67
  }
64
68
  return port;
65
69
  }
66
- export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId) {
70
+ export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId) {
67
71
  const framework = resolveFrontendFramework(type, frameworkId);
72
+ // Only an explicit selection is persisted: a missing `language` has always meant Bun.
73
+ const language = languageId === undefined ? undefined : resolveBackendLanguage(type, languageId);
74
+ // Likewise a missing backend `framework` has always meant Hono.
75
+ const backendFramework = resolveBackendFramework(type, frameworkId, language);
68
76
  const typeSpecific = TYPE_SPECIFIC[type];
69
77
  const base = JSON.parse(JSON.stringify(BASE_TEMPLATE));
70
78
  for (const [key, value] of Object.entries(typeSpecific)) {
@@ -75,23 +83,40 @@ export function createServiceJson(name, type, stack, port, extraFeatures = [], f
75
83
  base[key] = value;
76
84
  }
77
85
  }
86
+ if (language?.devCommand) {
87
+ base.dev = { command: language.devCommand, watch: language.watch ?? base.dev.watch };
88
+ // Provider-owned runtimes declare the probe path the engine reads; Bun keeps its historical key.
89
+ base.healthCheckPath = base.healthCheck;
90
+ delete base.healthCheck;
91
+ // The engine generates the image (.autogenerated/Dockerfile.app.autogenerated); a build
92
+ // block would point at a Dockerfile that does not exist.
93
+ delete base.build;
94
+ }
78
95
  return {
79
96
  ...base,
80
97
  $schema: SERVICE_MANIFEST_SCHEMA_URL,
81
98
  schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
82
99
  appName: name,
83
100
  appType: type,
84
- featuresEnabled: [...getDefaultFeaturesForResourceType(type), ...extraFeatures],
101
+ featuresEnabled: [
102
+ ...getDefaultFeaturesForResourceType(type).filter(
103
+ // Prisma wiring is Bun-only; the Python provider owns their data layer.
104
+ (feature) => !(language?.createFiles && feature === "prisma")),
105
+ ...extraFeatures,
106
+ ],
85
107
  name,
86
108
  type,
87
109
  stack,
88
110
  port,
89
111
  ...(framework ? { framework: framework.id } : {}),
112
+ ...(backendFramework ? { framework: backendFramework.id } : {}),
113
+ ...(language ? { language: language.id } : {}),
90
114
  };
91
115
  }
92
116
  export function createPackageJson(name, type, frameworkId) {
93
117
  const isFrontend = type === "frontend";
94
118
  const framework = resolveFrontendFramework(type, frameworkId);
119
+ const backendFramework = resolveBackendFramework(type, frameworkId);
95
120
  return {
96
121
  name: `@project/${name}`,
97
122
  version: "0.0.1",
@@ -110,7 +135,7 @@ export function createPackageJson(name, type, frameworkId) {
110
135
  "lint:fix": "biome check . --write",
111
136
  },
112
137
  dependencies: {
113
- ...(isFrontend ? {} : { hono: "^4.0.0" }),
138
+ ...(isFrontend ? {} : (backendFramework?.dependencies ?? { hono: "^4.0.0" })),
114
139
  ...(framework?.dependencies ?? {}),
115
140
  },
116
141
  devDependencies: {
@@ -123,6 +148,7 @@ export function createPackageJson(name, type, frameworkId) {
123
148
  "@biomejs/biome": "^2.5.13",
124
149
  ...(isFrontend ? { vite: "^5.0.0" } : {}),
125
150
  ...(framework?.devDependencies ?? {}),
151
+ ...(backendFramework?.devDependencies ?? {}),
126
152
  },
127
153
  };
128
154
  }
@@ -178,49 +204,7 @@ EXPOSE 3000
178
204
 
179
205
  CMD ["bun", "run", "start"]
180
206
  `;
181
- export function getBackendIndexTemplate(name) {
182
- return `import { Hono } from 'hono';
183
-
184
- const app = new Hono();
185
-
186
- // Health check endpoint (required by TILT_RESOURCE_DEFAULTS.star)
187
- app.get('/health', (c) => {
188
- return c.json({ status: 'ok', service: '${name}' });
189
- });
190
-
191
- app.get('/health/live', (c) => {
192
- return c.json({ status: 'alive', timestamp: Date.now() });
193
- });
194
-
195
- app.get('/health/ready', async (c) => {
196
- const dependencies: Record<string, string> = {};
197
- let allReady = true;
198
-
199
- // Add dependency checks here (database, cache, etc.)
200
- // Mark allReady = false if any dependency is unhealthy
201
-
202
- const status = allReady ? 'ready' : 'not_ready';
203
- return c.json({ status, dependencies }, allReady ? 200 : 503);
204
- });
205
-
206
- app.get('/', (c) => {
207
- return c.json({
208
- service: '${name}',
209
- version: '1.0.0',
210
- endpoints: ['/health', '/health/live', '/health/ready']
211
- });
212
- });
213
-
214
- const port = process.env.PORT || 3000;
215
- console.log('\\n🚀 ${name} running on http://localhost:' + port);
216
- console.log('📊 Health check: http://localhost:' + port + '/health\\n');
217
-
218
- export default {
219
- port,
220
- fetch: app.fetch,
221
- };
222
- `;
223
- }
207
+ export { getBackendIndexTemplate };
224
208
  function getShutdownHandlerTemplate(signal) {
225
209
  return `process.on('${signal}', () => {
226
210
  console.log('[Worker] ${signal} received, shutting down gracefully...');
@@ -311,7 +295,8 @@ export const resourceCommand = new Command("resource")
311
295
  .description("Create a new resource (service) from scratch, or register an existing one")
312
296
  .argument("[name]", "Resource name (kebab-case)")
313
297
  .option("-t, --type <type>", "Resource type: backend, frontend, worker, bring-your-own, sdk", "backend")
314
- .option("--framework <id>", "Frontend framework: react (default), vue")
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")
315
300
  .option("-s, --stack <stack>", "Stack to assign resource to", "default")
316
301
  .option("-p, --path <path>", "Custom path for resource directory")
317
302
  .option("--resource-path <path>", "Alias for --path (for backward compatibility)")
@@ -321,6 +306,7 @@ export const resourceCommand = new Command("resource")
321
306
  .option("--dockerfile <path>", "Path to Dockerfile relative to resource dir (for bring-your-own)")
322
307
  .option("--health-path <path>", "HTTP health check path (for bring-your-own)", "/health")
323
308
  .option("--no-proxy", "Disable the Traefik route (bring-your-own only)")
309
+ .option("--restart <policy>", "Compose restart policy: no, on-failure, unless-stopped, always (bring-your-own only; use no for one-shot jobs)")
324
310
  .option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
325
311
  .option("--port <port>", "Port number (default: next free in 4000-5999)")
326
312
  .action(async (name, options) => {
@@ -371,6 +357,8 @@ export const resourceCommand = new Command("resource")
371
357
  resourceType = normalizedType;
372
358
  }
373
359
  const frontendFramework = resolveFrontendFramework(resourceType, options.framework);
360
+ const backendLanguage = resolveBackendLanguage(resourceType, options.language);
361
+ const backendFramework = resolveBackendFramework(resourceType, options.framework, backendLanguage);
374
362
  let dddEnabled = false;
375
363
  if (options.ddd) {
376
364
  if (resourceType !== "backend" && resourceType !== "worker") {
@@ -467,6 +455,18 @@ export const resourceCommand = new Command("resource")
467
455
  const nextPort = resourceType === "sdk"
468
456
  ? 0
469
457
  : assignPort(resourceType, allResources);
458
+ if (options.restart !== undefined) {
459
+ if (resourceType !== "bring-your-own") {
460
+ throw new TdkError("--restart can only be used with --type bring-your-own.", [
461
+ "Add --type bring-your-own, or drop --restart",
462
+ ]);
463
+ }
464
+ if (!BYO_RESTART_POLICIES.includes(options.restart)) {
465
+ throw new TdkError(`Unknown --restart policy "${options.restart}".`, [
466
+ `Use one of: ${BYO_RESTART_POLICIES.join(", ")}`,
467
+ ]);
468
+ }
469
+ }
470
470
  const assignedPort = resourceType === "bring-your-own"
471
471
  ? resolveByoPort(options.port, nextPort, allResources)
472
472
  : nextPort;
@@ -536,6 +536,7 @@ export const resourceCommand = new Command("resource")
536
536
  healthCheckPath: options.healthPath || "/health",
537
537
  ...(options.image ? { image: options.image } : { dockerfile }),
538
538
  ...(options.proxy === false ? { exposeViaProxy: false } : {}),
539
+ ...(options.restart ? { restart: options.restart } : {}),
539
540
  };
540
541
  const { writeFileSync } = await import("node:fs");
541
542
  writeFileSync(resolve(fullPath, "service.json"), JSON.stringify(byoServiceJson, null, 2));
@@ -594,8 +595,8 @@ This file contains the resource configuration for TDK.
594
595
  }
595
596
  }
596
597
  // Prepare file generation tasks
597
- const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, dddEnabled ? ["ddd"] : [], frontendFramework?.id);
598
- const packageJson = createPackageJson(resourceName, resourceType, frontendFramework?.id);
598
+ const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, dddEnabled ? ["ddd"] : [], frontendFramework?.id ?? backendFramework?.id, options.language === undefined ? undefined : backendLanguage?.id);
599
+ const packageJson = createPackageJson(resourceName, resourceType, frontendFramework?.id ?? backendFramework?.id);
599
600
  const tasks = [
600
601
  {
601
602
  type: "json",
@@ -631,7 +632,7 @@ This file contains the resource configuration for TDK.
631
632
  tasks.push({
632
633
  type: "text",
633
634
  filename: "src/index.ts",
634
- content: getBackendIndexTemplate(resourceName),
635
+ content: (backendFramework?.createIndex ?? getBackendIndexTemplate)(resourceName),
635
636
  description: "Generating backend source",
636
637
  emoji: "💻",
637
638
  });
@@ -669,6 +670,21 @@ This file contains the resource configuration for TDK.
669
670
  description: "Generating test file",
670
671
  emoji: "🧪",
671
672
  });
673
+ // A language provider that owns its runtime files replaces the shared Bun/TypeScript
674
+ // scaffold (package.json, tsconfig, source, test, and the Dockerfile, which the engine generates); service.json stays shared.
675
+ const languageFiles = backendLanguage?.createFiles?.(resourceName);
676
+ if (languageFiles) {
677
+ const owned = new Set(languageFiles.map((file) => file.filename));
678
+ const sharedTasks = tasks.filter((task) => task.filename === "service.json" ||
679
+ !(owned.has(task.filename) ||
680
+ task.filename === "Dockerfile" ||
681
+ task.filename === "src/index.ts" ||
682
+ task.filename === "package.json" ||
683
+ task.filename === "tsconfig.json" ||
684
+ task.filename.endsWith(".test.ts")));
685
+ tasks.length = 0;
686
+ tasks.push(...sharedTasks, ...languageFiles.map((file) => ({ ...file, type: "text" })));
687
+ }
672
688
  // Execute all file writes with progress
673
689
  console.log(chalk.blue("💻 Generating source files..."));
674
690
  writeFilesWithProgress(fullPath, tasks);
@@ -688,7 +704,7 @@ This file contains the resource configuration for TDK.
688
704
  }
689
705
  console.log(chalk.gray(`\nNext steps:`));
690
706
  console.log(chalk.gray(` cd ${finalResourcePath}`));
691
- console.log(chalk.gray(` bun install`));
707
+ console.log(chalk.gray(` ${backendLanguage?.installHint ?? "bun install"}`));
692
708
  console.log(chalk.gray(` tdk up ${stackName}`));
693
709
  });
694
710
  });
@@ -28,7 +28,7 @@ export function formatUpSuccess(port, appUrls = []) {
28
28
  export function nativeWindowsUpRefusal(platform, allowNativeWindows) {
29
29
  if (platform !== "win32" || allowNativeWindows === "1")
30
30
  return null;
31
- return "Native Windows is not supported for `tdk up`. Use WSL2 with Docker Desktop integration, then run `tdk doctor` inside your WSL project.";
31
+ return "Landscape startup needs Ubuntu on WSL2. Native Windows is inspect-only.";
32
32
  }
33
33
  function waitForTiltUi(port, timeoutMs = 30_000) {
34
34
  return new Promise((resolve) => {
@@ -0,0 +1,65 @@
1
+ export const litFrontendProvider = {
2
+ id: "lit",
3
+ label: "Lit + Vite",
4
+ dependencies: {
5
+ lit: "^3.2.0",
6
+ },
7
+ devDependencies: {},
8
+ compilerOptions: {},
9
+ createFiles(name) {
10
+ return [
11
+ {
12
+ filename: "index.html",
13
+ content: `<!DOCTYPE html>
14
+ <html lang="en">
15
+ <head>
16
+ <meta charset="UTF-8" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
18
+ <title>${name}</title>
19
+ </head>
20
+ <body>
21
+ <app-root></app-root>
22
+ <script type="module" src="/src/main.ts"></script>
23
+ </body>
24
+ </html>
25
+ `,
26
+ description: "Generating HTML template",
27
+ emoji: "💻",
28
+ },
29
+ {
30
+ filename: "src/main.ts",
31
+ content: `import './app-root';
32
+ `,
33
+ description: "Generating Lit entry",
34
+ emoji: "💻",
35
+ },
36
+ {
37
+ filename: "src/app-root.ts",
38
+ content: `import { LitElement, css, html } from 'lit';
39
+
40
+ export class AppRoot extends LitElement {
41
+ static styles = css\`
42
+ :host {
43
+ display: block;
44
+ padding: 2rem;
45
+ font-family: system-ui;
46
+ }
47
+ \`;
48
+
49
+ render() {
50
+ return html\`
51
+ <h1>${name}</h1>
52
+ <p>Frontend resource created with TDK</p>
53
+ \`;
54
+ }
55
+ }
56
+
57
+ customElements.define('app-root', AppRoot);
58
+ `,
59
+ description: "Generating Lit app",
60
+ emoji: "💻",
61
+ },
62
+ ];
63
+ },
64
+ };
65
+ //# sourceMappingURL=lit.js.map