@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.
- package/CHANGELOG.md +6 -0
- package/README.md +22 -3
- package/cli/dist/backend-frameworks/express.js +50 -0
- package/cli/dist/backend-frameworks/registry.js +41 -0
- package/cli/dist/backend-frameworks/types.js +2 -0
- package/cli/dist/backend-languages/bun.js +53 -0
- package/cli/dist/backend-languages/python.js +89 -0
- package/cli/dist/backend-languages/registry.js +30 -0
- package/cli/dist/backend-languages/types.js +2 -0
- package/cli/dist/commands/resource.js +67 -51
- package/cli/dist/commands/up.js +1 -1
- package/cli/dist/frontend-frameworks/lit.js +65 -0
- package/cli/dist/frontend-frameworks/preact.js +63 -0
- package/cli/dist/frontend-frameworks/qwik.js +62 -0
- package/cli/dist/frontend-frameworks/registry.js +14 -3
- package/cli/dist/frontend-frameworks/solid.js +63 -0
- package/cli/dist/frontend-frameworks/svelte.js +68 -0
- package/cli/dist/utils/doctor-wiring.js +9 -1
- package/cli/dist/utils/service-manifest.js +2 -0
- package/cli/package.json +2 -2
- package/engine/schemas/service-schema.json +31 -4
- package/engine/topologies/platform/docker/compose/compose.star +9 -1
- package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +62 -0
- package/engine/topologies/platform/docker/index.star +7 -0
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +10 -1
- package/engine/topologies/tilt/generators/validators.star +8 -4
- package/engine/topologies/tilt/generators/vite/frameworks/lit.star +90 -0
- package/engine/topologies/tilt/generators/vite/frameworks/preact.star +92 -0
- package/engine/topologies/tilt/generators/vite/frameworks/qwik.star +92 -0
- package/engine/topologies/tilt/generators/vite/frameworks/solid.star +92 -0
- package/engine/topologies/tilt/generators/vite/frameworks/svelte.star +92 -0
- package/engine/topologies/tilt/generators/vite/frontend.star +25 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +31 -4
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +25 -6
- 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
|
-
|
|
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
|
-

|
|
21
|
+

|
|
20
22
|
|
|
21
23
|
*Scaffolding a backend and frontend, then listing the stack.*
|
|
22
24
|
|
|
25
|
+

|
|
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,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
|
|
@@ -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: [
|
|
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
|
|
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>", "
|
|
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
|
});
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -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 "
|
|
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
|