@tdk-landscape/tdk-cli-core 1.3.89 ā 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.
- package/cli/dist/backend-frameworks/nestjs.js +56 -0
- package/cli/dist/backend-frameworks/registry.js +2 -0
- package/cli/dist/backend-languages/registry.js +2 -0
- package/cli/dist/backend-languages/rust.js +83 -0
- package/cli/dist/commands/resource.js +5 -3
- package/cli/package.json +1 -1
- package/engine/schemas/service-schema.json +4 -2
- package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +31 -2
- package/engine/topologies/tilt/generators/validators.star +2 -1
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +4 -4
- package/package.json +1 -1
|
@@ -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
|
|
@@ -3,6 +3,7 @@ import { TdkError } from "../utils/errors.js";
|
|
|
3
3
|
import { elysiaBackendProvider } from "./elysia.js";
|
|
4
4
|
import { expressBackendProvider } from "./express.js";
|
|
5
5
|
import { fastifyBackendProvider } from "./fastify.js";
|
|
6
|
+
import { nestBackendProvider } from "./nestjs.js";
|
|
6
7
|
/** Hono is the historical default; omitting --framework keeps the exact historical output. */
|
|
7
8
|
export const DEFAULT_BACKEND_FRAMEWORK = "hono";
|
|
8
9
|
export const honoBackendProvider = {
|
|
@@ -17,6 +18,7 @@ export const BACKEND_FRAMEWORKS = {
|
|
|
17
18
|
express: expressBackendProvider,
|
|
18
19
|
elysia: elysiaBackendProvider,
|
|
19
20
|
fastify: fastifyBackendProvider,
|
|
21
|
+
nestjs: nestBackendProvider,
|
|
20
22
|
};
|
|
21
23
|
export function getBackendFramework(frameworkId) {
|
|
22
24
|
const id = (frameworkId ?? DEFAULT_BACKEND_FRAMEWORK).trim().toLowerCase();
|
|
@@ -2,11 +2,13 @@ import { TdkError } from "../utils/errors.js";
|
|
|
2
2
|
import { bunBackendProvider } from "./bun.js";
|
|
3
3
|
import { goBackendProvider } from "./go.js";
|
|
4
4
|
import { pythonBackendProvider } from "./python.js";
|
|
5
|
+
import { rustBackendProvider } from "./rust.js";
|
|
5
6
|
export const DEFAULT_BACKEND_LANGUAGE = "bun";
|
|
6
7
|
export const BACKEND_LANGUAGES = {
|
|
7
8
|
bun: bunBackendProvider,
|
|
8
9
|
python: pythonBackendProvider,
|
|
9
10
|
go: goBackendProvider,
|
|
11
|
+
rust: rustBackendProvider,
|
|
10
12
|
};
|
|
11
13
|
export function getBackendLanguage(languageId) {
|
|
12
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, vanilla; backend hono (default), express, elysia, fastify")
|
|
299
|
-
.option("--language <id>", "Backend language: bun (default), python, go")
|
|
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)")
|
|
@@ -620,7 +622,7 @@ This file contains the resource configuration for TDK.
|
|
|
620
622
|
{
|
|
621
623
|
type: "json",
|
|
622
624
|
filename: "tsconfig.json",
|
|
623
|
-
content: createResourceTsconfig(resourceType, frontendFramework?.id),
|
|
625
|
+
content: createResourceTsconfig(resourceType, frontendFramework?.id ?? backendFramework?.id),
|
|
624
626
|
description: "Generating tsconfig.json",
|
|
625
627
|
emoji: "āļø",
|
|
626
628
|
},
|
package/cli/package.json
CHANGED
|
@@ -238,7 +238,8 @@
|
|
|
238
238
|
"hono",
|
|
239
239
|
"express",
|
|
240
240
|
"elysia",
|
|
241
|
-
"fastify"
|
|
241
|
+
"fastify",
|
|
242
|
+
"nestjs"
|
|
242
243
|
],
|
|
243
244
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
244
245
|
"type": "string"
|
|
@@ -248,7 +249,8 @@
|
|
|
248
249
|
"examples": [
|
|
249
250
|
"bun",
|
|
250
251
|
"python",
|
|
251
|
-
"go"
|
|
252
|
+
"go",
|
|
253
|
+
"rust"
|
|
252
254
|
],
|
|
253
255
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
254
256
|
"type": "string"
|
|
@@ -8,12 +8,13 @@
|
|
|
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", "go"]
|
|
11
|
+
PROVIDER_OWNED_LANGUAGES = ["python", "go", "rust"]
|
|
12
12
|
|
|
13
13
|
# Files a provider-owned language must scaffold; checked before generating anything.
|
|
14
14
|
LANGUAGE_REQUIRED_FILES = {
|
|
15
15
|
"python": ["pyproject.toml", "src/main.py"],
|
|
16
16
|
"go": ["go.mod", "main.go"],
|
|
17
|
+
"rust": ["Cargo.toml", "src/main.rs"],
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
_CURL = """RUN apt-get update && apt-get install -y --no-install-recommends curl \\
|
|
@@ -73,9 +74,37 @@ EXPOSE {port}
|
|
|
73
74
|
CMD ["app"]
|
|
74
75
|
"""
|
|
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
|
+
|
|
76
105
|
def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
|
|
77
106
|
"""Dockerfile text for a provider-owned backend language, or fail for an unknown one."""
|
|
78
|
-
template = {"python": _PYTHON, "go": _GO}.get(language)
|
|
107
|
+
template = {"python": _PYTHON, "go": _GO, "rust": _RUST}.get(language)
|
|
79
108
|
if template == None:
|
|
80
109
|
fail("No Dockerfile generator for backend language '{}'".format(language))
|
|
81
110
|
return template.format(
|
|
@@ -559,6 +559,7 @@ def generate_resource_health_check(resource_name, resource_path, manifest, check
|
|
|
559
559
|
backend_files = {
|
|
560
560
|
"python": ["pyproject.toml", "src/main.py"],
|
|
561
561
|
"go": ["go.mod", "main.go"],
|
|
562
|
+
"rust": ["Cargo.toml", "src/main.rs"],
|
|
562
563
|
}.get(manifest.get("language", "bun"), ["package.json", "prisma/schema.prisma"])
|
|
563
564
|
required_files = {
|
|
564
565
|
"backend": backend_files,
|
|
@@ -575,7 +576,7 @@ fi
|
|
|
575
576
|
'''.format(req_file, req_file))
|
|
576
577
|
|
|
577
578
|
# Check 2: Package.json is valid JSON (Python and Go backends have no package.json)
|
|
578
|
-
if manifest.get("language", "bun") not in ["python", "go"]:
|
|
579
|
+
if manifest.get("language", "bun") not in ["python", "go", "rust"]:
|
|
579
580
|
health_checks.append('''
|
|
580
581
|
if ! cat package.json | head -1 > /dev/null 2>&1; then
|
|
581
582
|
echo "ā package.json is not valid"
|
package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star
CHANGED
|
@@ -220,12 +220,12 @@ 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 backends have no live update at all (see the early return).
|
|
223
|
+
Go and Rust backends have no live update at all (see the early return).
|
|
224
224
|
"""
|
|
225
|
-
# Go
|
|
226
|
-
# binary running. Return before any sync() step is built (Tilt rejects sync steps that are created
|
|
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
227
|
# but never used): no live-update rules means Tilt rebuilds the image when a watched file changes.
|
|
228
|
-
if language
|
|
228
|
+
if language in ['go', 'rust']:
|
|
229
229
|
return []
|
|
230
230
|
|
|
231
231
|
live_update_rules = []
|