@tdk-landscape/tdk-cli-core 1.3.88 ā 1.3.89
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/elysia.js +39 -0
- package/cli/dist/backend-frameworks/fastify.js +44 -0
- package/cli/dist/backend-frameworks/registry.js +4 -0
- package/cli/dist/backend-languages/go.js +98 -0
- package/cli/dist/backend-languages/registry.js +2 -0
- package/cli/dist/commands/resource.js +9 -4
- package/cli/dist/frontend-frameworks/registry.js +2 -0
- package/cli/dist/frontend-frameworks/vanilla.js +60 -0
- package/cli/package.json +1 -1
- package/engine/schemas/service-schema.json +7 -3
- package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +34 -3
- package/engine/topologies/platform/docker/index.star +2 -0
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +1 -1
- package/engine/topologies/tilt/generators/validators.star +4 -3
- package/engine/topologies/tilt/generators/vite/frameworks/vanilla.star +90 -0
- package/engine/topologies/tilt/generators/vite/frontend.star +5 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +7 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
4
6
|
/** Hono is the historical default; omitting --framework keeps the exact historical output. */
|
|
5
7
|
export const DEFAULT_BACKEND_FRAMEWORK = "hono";
|
|
6
8
|
export const honoBackendProvider = {
|
|
@@ -13,6 +15,8 @@ export const honoBackendProvider = {
|
|
|
13
15
|
export const BACKEND_FRAMEWORKS = {
|
|
14
16
|
hono: honoBackendProvider,
|
|
15
17
|
express: expressBackendProvider,
|
|
18
|
+
elysia: elysiaBackendProvider,
|
|
19
|
+
fastify: fastifyBackendProvider,
|
|
16
20
|
};
|
|
17
21
|
export function getBackendFramework(frameworkId) {
|
|
18
22
|
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,12 @@
|
|
|
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";
|
|
4
5
|
export const DEFAULT_BACKEND_LANGUAGE = "bun";
|
|
5
6
|
export const BACKEND_LANGUAGES = {
|
|
6
7
|
bun: bunBackendProvider,
|
|
7
8
|
python: pythonBackendProvider,
|
|
9
|
+
go: goBackendProvider,
|
|
8
10
|
};
|
|
9
11
|
export function getBackendLanguage(languageId) {
|
|
10
12
|
const id = (languageId ?? DEFAULT_BACKEND_LANGUAGE).trim().toLowerCase();
|
|
@@ -295,8 +295,8 @@ export const resourceCommand = new Command("resource")
|
|
|
295
295
|
.description("Create a new resource (service) from scratch, or register an existing one")
|
|
296
296
|
.argument("[name]", "Resource name (kebab-case)")
|
|
297
297
|
.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")
|
|
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
300
|
.option("-s, --stack <stack>", "Stack to assign resource to", "default")
|
|
301
301
|
.option("-p, --path <path>", "Custom path for resource directory")
|
|
302
302
|
.option("--resource-path <path>", "Alias for --path (for backward compatibility)")
|
|
@@ -584,8 +584,13 @@ This file contains the resource configuration for TDK.
|
|
|
584
584
|
// Create directory structure for new resources
|
|
585
585
|
console.log(chalk.blue("\nš Creating directory structure..."));
|
|
586
586
|
mkdirSync(fullPath, { recursive: true });
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
// A provider that owns its runtime files only gets the folders it writes into (Go keeps its files at the root).
|
|
588
|
+
const ownedTopLevel = new Set((backendLanguage?.createFiles?.(resourceName) ?? []).map((file) => file.filename.split("/")[0]));
|
|
589
|
+
for (const folder of ["src", "tests"]) {
|
|
590
|
+
if (!backendLanguage?.createFiles || ownedTopLevel.has(folder)) {
|
|
591
|
+
mkdirSync(resolve(fullPath, folder), { recursive: true });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
589
594
|
if (resourceType === "frontend") {
|
|
590
595
|
mkdirSync(resolve(fullPath, "public"), { recursive: true });
|
|
591
596
|
}
|
|
@@ -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
|
@@ -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
|
|
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,11 @@
|
|
|
234
234
|
"lit",
|
|
235
235
|
"solid",
|
|
236
236
|
"qwik",
|
|
237
|
+
"vanilla",
|
|
237
238
|
"hono",
|
|
238
|
-
"express"
|
|
239
|
+
"express",
|
|
240
|
+
"elysia",
|
|
241
|
+
"fastify"
|
|
239
242
|
],
|
|
240
243
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
241
244
|
"type": "string"
|
|
@@ -244,7 +247,8 @@
|
|
|
244
247
|
"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
248
|
"examples": [
|
|
246
249
|
"bun",
|
|
247
|
-
"python"
|
|
250
|
+
"python",
|
|
251
|
+
"go"
|
|
248
252
|
],
|
|
249
253
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
250
254
|
"type": "string"
|
|
@@ -8,7 +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"]
|
|
11
|
+
PROVIDER_OWNED_LANGUAGES = ["python", "go"]
|
|
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
|
+
}
|
|
12
18
|
|
|
13
19
|
_CURL = """RUN apt-get update && apt-get install -y --no-install-recommends curl \\
|
|
14
20
|
&& rm -rf /var/lib/apt/lists/*
|
|
@@ -42,9 +48,34 @@ EXPOSE {port}
|
|
|
42
48
|
CMD ["sh", "-c", "uvicorn main:app --app-dir src --host 0.0.0.0 --port ${{PORT:-{port}}} --reload --reload-dir src"]
|
|
43
49
|
"""
|
|
44
50
|
|
|
51
|
+
_GO = """# Generated by TDK for a Go backend. Do not edit; change service.json instead.
|
|
52
|
+
FROM golang:1.23-bookworm AS build
|
|
53
|
+
|
|
54
|
+
WORKDIR /app/{res_path}
|
|
55
|
+
|
|
56
|
+
COPY {res_path}/go.* ./
|
|
57
|
+
RUN go mod download
|
|
58
|
+
|
|
59
|
+
COPY {res_path}/ ./
|
|
60
|
+
RUN CGO_ENABLED=0 go build -o /out/app .
|
|
61
|
+
|
|
62
|
+
FROM build AS test
|
|
63
|
+
RUN go test ./...
|
|
64
|
+
|
|
65
|
+
FROM debian:bookworm-slim AS production
|
|
66
|
+
|
|
67
|
+
{curl}
|
|
68
|
+
COPY --from=build /out/app /usr/local/bin/app
|
|
69
|
+
|
|
70
|
+
{healthcheck}
|
|
71
|
+
EXPOSE {port}
|
|
72
|
+
|
|
73
|
+
CMD ["app"]
|
|
74
|
+
"""
|
|
75
|
+
|
|
45
76
|
def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
|
|
46
77
|
"""Dockerfile text for a provider-owned backend language, or fail for an unknown one."""
|
|
47
|
-
template = {"python": _PYTHON}.get(language)
|
|
78
|
+
template = {"python": _PYTHON, "go": _GO}.get(language)
|
|
48
79
|
if template == None:
|
|
49
80
|
fail("No Dockerfile generator for backend language '{}'".format(language))
|
|
50
81
|
return template.format(
|
|
@@ -56,7 +87,7 @@ def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
|
|
|
56
87
|
|
|
57
88
|
|
|
58
89
|
def is_provider_owned_language(manifest):
|
|
59
|
-
"""True when the manifest selects
|
|
90
|
+
"""True when the manifest selects a provider-owned language (a missing language means Bun)."""
|
|
60
91
|
if not manifest:
|
|
61
92
|
return False
|
|
62
93
|
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,10 @@ 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"],
|
|
561
562
|
}.get(manifest.get("language", "bun"), ["package.json", "prisma/schema.prisma"])
|
|
562
563
|
required_files = {
|
|
563
564
|
"backend": backend_files,
|
|
@@ -573,8 +574,8 @@ if [ ! -f "{}" ]; then
|
|
|
573
574
|
fi
|
|
574
575
|
'''.format(req_file, req_file))
|
|
575
576
|
|
|
576
|
-
# Check 2: Package.json is valid JSON (Python backends have no package.json)
|
|
577
|
-
if manifest.get("language", "bun")
|
|
577
|
+
# 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"]:
|
|
578
579
|
health_checks.append('''
|
|
579
580
|
if ! cat package.json | head -1 > /dev/null 2>&1; then
|
|
580
581
|
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):
|
package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star
CHANGED
|
@@ -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 backends have no live update at all (see the early return).
|
|
223
224
|
"""
|
|
225
|
+
# Go is compiled into the image and has no reload process, so syncing sources would leave the old
|
|
226
|
+
# 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 == 'go':
|
|
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
|
-
#
|
|
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
|
|
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))
|