@tdk-landscape/tdk-cli-core 1.3.73 → 1.3.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ export const vueFrontendProvider = {
2
+ id: "vue",
3
+ label: "Vue + Vite",
4
+ dependencies: {
5
+ vue: "^3.5.0",
6
+ },
7
+ devDependencies: {
8
+ "@vitejs/plugin-vue": "^5.2.4",
9
+ },
10
+ compilerOptions: {},
11
+ createFiles(name) {
12
+ return [
13
+ {
14
+ filename: "index.html",
15
+ content: `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8" />
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
+ <title>${name}</title>
21
+ </head>
22
+ <body>
23
+ <div id="app"></div>
24
+ <script type="module" src="/src/main.ts"></script>
25
+ </body>
26
+ </html>
27
+ `,
28
+ description: "Generating HTML template",
29
+ emoji: "💻",
30
+ },
31
+ {
32
+ filename: "src/main.ts",
33
+ content: `import { createApp } from 'vue';
34
+ import App from './App.vue';
35
+
36
+ createApp(App).mount('#app');
37
+ `,
38
+ description: "Generating Vue entry",
39
+ emoji: "💻",
40
+ },
41
+ {
42
+ filename: "src/App.vue",
43
+ content: `<script setup lang="ts">
44
+ const message = 'Frontend resource created with TDK';
45
+ </script>
46
+
47
+ <template>
48
+ <main style="padding: 2rem; font-family: system-ui">
49
+ <h1>${name}</h1>
50
+ <p>{{ message }}</p>
51
+ </main>
52
+ </template>
53
+ `,
54
+ description: "Generating Vue app",
55
+ emoji: "💻",
56
+ },
57
+ {
58
+ filename: "src/vite-env.d.ts",
59
+ content: `/// <reference types="vite/client" />
60
+
61
+ declare module '*.vue' {
62
+ import type { Component } from 'vue';
63
+ const component: Component;
64
+ export default component;
65
+ }
66
+ `,
67
+ description: "Generating Vue module types",
68
+ emoji: "⚙️",
69
+ },
70
+ ];
71
+ },
72
+ };
73
+ //# sourceMappingURL=vue.js.map
@@ -0,0 +1,67 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, relative, resolve, sep } from "node:path";
3
+ // Mirrors discovery/json_manifest_scanner.star: every `discovery.paths` entry is a shell
4
+ // glob of directories, and `find -maxdepth 3` looks for service.json under each match. So a
5
+ // resource is only seen by Tilt if some directory matching a pattern is the resource
6
+ // directory itself or at most this many levels above it.
7
+ const MAX_LEVELS_BELOW_MATCH = 2;
8
+ const DEFAULT_DISCOVERY_PATHS = ["services/*/*"];
9
+ function segments(path) {
10
+ return path.split(sep).filter(Boolean);
11
+ }
12
+ function segmentMatches(pattern, name) {
13
+ const source = pattern
14
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
15
+ .replace(/\*/g, "[^/]*")
16
+ .replace(/\?/g, "[^/]");
17
+ return new RegExp(`^${source}$`).test(name);
18
+ }
19
+ function globMatchesDir(patternSegments, dirSegments) {
20
+ return (patternSegments.length === dirSegments.length &&
21
+ patternSegments.every((segment, index) => segmentMatches(segment, dirSegments[index])));
22
+ }
23
+ /** `discovery.paths` from .tdk/project.json, or the generic default when unset/unreadable. */
24
+ export function readDiscoveryPaths(projectRoot) {
25
+ const configPath = join(projectRoot, ".tdk", "project.json");
26
+ if (!existsSync(configPath))
27
+ return DEFAULT_DISCOVERY_PATHS;
28
+ try {
29
+ const paths = JSON.parse(readFileSync(configPath, "utf-8"))?.discovery?.paths;
30
+ if (Array.isArray(paths) && paths.every((p) => typeof p === "string"))
31
+ return paths;
32
+ }
33
+ catch { }
34
+ return DEFAULT_DISCOVERY_PATHS;
35
+ }
36
+ /** Whether Tilt's discovery would find a service.json in `resourceDir` given `patterns`. */
37
+ export function isPathDiscovered(projectRoot, resourceDir, patterns) {
38
+ const resourceSegments = segments(relative(projectRoot, resolve(projectRoot, resourceDir)));
39
+ return patterns.some((pattern) => {
40
+ const patternSegments = segments(relative(projectRoot, resolve(projectRoot, pattern)));
41
+ for (let levels = 0; levels <= MAX_LEVELS_BELOW_MATCH; levels++) {
42
+ const depth = resourceSegments.length - levels;
43
+ if (depth > 0 && globMatchesDir(patternSegments, resourceSegments.slice(0, depth))) {
44
+ return true;
45
+ }
46
+ }
47
+ return false;
48
+ });
49
+ }
50
+ /**
51
+ * Picks the folder for a new resource without an explicit --path. The conventional folder
52
+ * per type (apps/, workers/, packages/) is kept when discovery covers it; otherwise the
53
+ * resource goes under services/<stack>/, which the default discovery paths cover, because a
54
+ * resource outside every discovery path is scaffolded but never started by `tdk up`.
55
+ */
56
+ export function chooseResourcePath(projectRoot, conventionalPath, stack, name) {
57
+ const patterns = readDiscoveryPaths(projectRoot);
58
+ if (isPathDiscovered(projectRoot, conventionalPath, patterns)) {
59
+ return { path: conventionalPath };
60
+ }
61
+ const fallback = `services/${stack}/${name}`;
62
+ if (isPathDiscovered(projectRoot, fallback, patterns)) {
63
+ return { path: fallback, adjustedFrom: conventionalPath };
64
+ }
65
+ return { path: conventionalPath };
66
+ }
67
+ //# sourceMappingURL=discovery-paths.js.map
@@ -12,7 +12,7 @@ export function logVerbose(message, err) {
12
12
  console.warn(chalk.gray(`${message}${errMsg}`));
13
13
  }
14
14
  }
15
- class TdkError extends Error {
15
+ export class TdkError extends Error {
16
16
  suggestions;
17
17
  exitCode;
18
18
  constructor(message, suggestions = [], exitCode = 1) {
@@ -78,6 +78,9 @@ export function requireProjectRoot() {
78
78
  return projectRoot;
79
79
  }
80
80
  function handleCommandError(err) {
81
+ if (err instanceof TdkError) {
82
+ err.exit();
83
+ }
81
84
  console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
82
85
  process.exit(1);
83
86
  }
package/cli/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tdk-cli-internal",
3
3
  "private": true,
4
- "version": "1.3.73",
4
+ "version": "1.3.75",
5
5
  "description": "TDK CLI - Tilt Development Kit command line interface",
6
6
  "keywords": [
7
7
  "tdk",
package/engine/AGENTS.md CHANGED
@@ -200,10 +200,11 @@ def generate_for_manifest(manifest, backend_manifest, write_fn):
200
200
  elif app_type == 'sdk':
201
201
  return _generate_sdk(manifest, write_fn)
202
202
 
203
- # Generates:
204
- # - vite.config.ts (local development)
205
- # - vite.config.docker.ts (Docker build)
206
- # - vite.config.build.ts (production build)
203
+ # Generates under each frontend resource's .autogenerated/ directory:
204
+ # - vite.config.frontend.autogenerated.ts (local development)
205
+ # - vite.config.build.autogenerated.ts (production build)
206
+ # The CLI scaffolds root index.html; nginx generates nginx.autogenerated.conf
207
+ # and serves the built index.html.
207
208
  ```
208
209
 
209
210
  **TSConfig Generator:**
@@ -86,7 +86,7 @@
86
86
  "type": "string"
87
87
  },
88
88
  "appType": {
89
- "description": "Resource type determining build strategy:\n• backend - Hono/Express API server\n• frontend - Vite/React application\n• sdk - Shared library package\n• worker - Background job processor\n• migrator - Database migration runner\n• library - Shared code library",
89
+ "description": "Resource type determining build strategy:\n• backend - Hono/Express API server\n• frontend - Vite single-page application (React or Vue, see framework)\n• sdk - Shared library package\n• worker - Background job processor\n• migrator - Database migration runner\n• library - Shared code library",
90
90
  "enum": [
91
91
  "backend",
92
92
  "frontend",
@@ -199,6 +199,14 @@
199
199
  "type": "array",
200
200
  "uniqueItems": true
201
201
  },
202
+ "framework": {
203
+ "description": "Frontend SPA provider used to generate Vite and TypeScript config. Only meaningful for frontend resources; omitted means react (legacy manifests).",
204
+ "enum": [
205
+ "react",
206
+ "vue"
207
+ ],
208
+ "type": "string"
209
+ },
202
210
  "healthCheckPath": {
203
211
  "default": "/health",
204
212
  "description": "HTTP endpoint for health checks. Must return 200 OK.",
@@ -18,7 +18,7 @@ if _is_verbose:
18
18
  # Frontend TSConfig Generator
19
19
  # =============================================================================
20
20
 
21
- def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_docker=True):
21
+ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_docker=True, framework='react'):
22
22
  """
23
23
  Generate tsconfig for frontend services.
24
24
 
@@ -27,6 +27,7 @@ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_d
27
27
  write_fn: Write function for file output
28
28
  internal_deps: Optional internal dependencies map
29
29
  is_docker: Whether generating for Docker context
30
+ framework: Frontend framework id; omitted in legacy manifests means React
30
31
  """
31
32
  # Check verbose mode
32
33
  is_verbose = os.environ.get('TILT_LOG_LEVEL') == 'verbose'
@@ -76,25 +77,31 @@ def generate_frontend_tsconfig(resource_path, write_fn, internal_deps=None, is_d
76
77
  resource_path
77
78
  ))
78
79
  print(" Docker build will resolve @{npm_scope}/* from node_modules")
80
+ if framework not in ['react', 'vue']:
81
+ fail("Unknown frontend framework for TypeScript generation: " + str(framework))
82
+
83
+ compiler_options = {
84
+ "target": "ES2022",
85
+ "module": "ES2022",
86
+ "moduleResolution": "bundler",
87
+ "lib": ["dom", "dom.iterable", "esnext"],
88
+ "paths": paths,
89
+ "skipLibCheck": True,
90
+ "strict": True,
91
+ "esModuleInterop": True,
92
+ "allowSyntheticDefaultImports": True,
93
+ # "bun" (not "node"): matches backend_tsconfig.star - @types/bun is a direct
94
+ # devDependency so it's always linked at the top level under any linker mode
95
+ # (including Docker's isolated linker), while @types/node (when only pulled in
96
+ # transitively, e.g. via bun-types) isn't hoisted under the isolated linker and
97
+ # "types": ["node"] fails to resolve there even though it works locally.
98
+ "types": ["node"],
99
+ }
100
+ if framework == 'react':
101
+ compiler_options['jsx'] = 'react-jsx'
102
+
79
103
  content = {
80
- "compilerOptions": {
81
- "target": "ES2022",
82
- "module": "ES2022",
83
- "moduleResolution": "node",
84
- "lib": ["dom", "dom.iterable", "esnext"],
85
- "jsx": "react-jsx",
86
- "paths": paths,
87
- "skipLibCheck": True,
88
- "strict": True,
89
- "esModuleInterop": True,
90
- "allowSyntheticDefaultImports": True,
91
- # "bun" (not "node"): matches backend_tsconfig.star - @types/bun is a direct
92
- # devDependency so it's always linked at the top level under any linker mode
93
- # (including Docker's isolated linker), while @types/node (when only pulled in
94
- # transitively, e.g. via bun-types) isn't hoisted under the isolated linker and
95
- # "types": ["node"] fails to resolve there even though it works locally.
96
- "types": ["node"],
97
- },
104
+ "compilerOptions": compiler_options,
98
105
  "exclude": [
99
106
  "node_modules",
100
107
  "dist",
@@ -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") == "vue" else "src/main.tsx"
558
559
  required_files = {
559
560
  "backend": ["package.json", "prisma/schema.prisma"],
560
- "frontend": ["package.json", "index.html", "src/main.tsx"],
561
+ "frontend": ["package.json", "index.html", frontend_entry],
561
562
  "library": ["package.json"],
562
563
  }.get(app_type, ["package.json"])
563
564
 
@@ -0,0 +1,126 @@
1
+ # React-specific Vite templates for frontend resources.
2
+
3
+ REACT_VITE_FRONTEND = """{header}
4
+ import {{ resolve }} from 'node:path';
5
+ import react from '@vitejs/plugin-react';
6
+ import {{ defineConfig }} from 'vite';
7
+
8
+ export default defineConfig({{
9
+ base: '{base_path}',
10
+ build: {{
11
+ cssCodeSplit: false,
12
+ minify: false,
13
+ modulePreload: false,
14
+ rollupOptions: {{
15
+ output: {{
16
+ entryFileNames: '[name].js',
17
+ format: 'esm',
18
+ }},
19
+ }},
20
+ target: 'esnext',
21
+ }},
22
+ define: {{
23
+ global: 'globalThis',
24
+ }},
25
+ optimizeDeps: {{
26
+ include: [{optimize_deps_include}],
27
+ }},
28
+ plugins: [react()],
29
+ preview: {{
30
+ host: '0.0.0.0',
31
+ port: {port},
32
+ }},
33
+ resolve: {{
34
+ alias: {{
35
+ // 🎯 IMPORTANT: Order matters! More specific aliases must come FIRST
36
+ '@/autogenerated': resolve(__dirname),
37
+ '@': resolve(__dirname, '../src'),
38
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
39
+ {internal_aliases}
40
+ }},
41
+ }},
42
+ server: {{
43
+ host: '0.0.0.0',
44
+ port: {port},
45
+ hmr: {{
46
+ port: {hmr_port},
47
+ }},
48
+ {proxy_config}
49
+ }},
50
+ }});
51
+ """
52
+
53
+ REACT_VITE_FRONTEND_BUILD = """{header}
54
+ import {{ resolve }} from 'node:path';
55
+ import react from '@vitejs/plugin-react';
56
+ import {{ defineConfig }} from 'vite';
57
+
58
+ export default defineConfig({{
59
+ mode: 'production',
60
+ base: '{base_path}',
61
+ build: {{
62
+ cssCodeSplit: false,
63
+ minify: true,
64
+ modulePreload: false,
65
+ rollupOptions: {{
66
+ output: {{
67
+ entryFileNames: '[name].js',
68
+ format: 'esm',
69
+ // Inline all node_modules to prevent lazy dynamic imports for CJS modules
70
+ manualChunks: undefined,
71
+ }},
72
+ }},
73
+ target: 'esnext',
74
+ }},
75
+ define: {{
76
+ global: 'globalThis',
77
+ __DEV__: 'false',
78
+ 'process.env.NODE_ENV': '"production"',
79
+ }},
80
+ esbuild: {{
81
+ // Force production JSX runtime
82
+ jsx: 'automatic',
83
+ jsxImportSource: 'react',
84
+ // CRITICAL: Disable development JSX (jsxDEV) for Vite 8 + Rolldown compatibility
85
+ jsxDev: false,
86
+ }},
87
+ optimizeDeps: {{
88
+ include: [
89
+ 'react',
90
+ 'react-dom',
91
+ 'react-dom/client',
92
+ 'react/jsx-runtime',
93
+ '@tanstack/react-query',
94
+ // Internal packages (auto-generated)
95
+ {optimize_deps_include}
96
+ ],
97
+ esbuildOptions: {{
98
+ format: 'esm',
99
+ target: 'esnext',
100
+ // Use production JSX runtime
101
+ jsx: 'automatic',
102
+ jsxImportSource: 'react',
103
+ jsxDev: false,
104
+ }},
105
+ }},
106
+ plugins: [
107
+ react({{
108
+ // Force automatic JSX runtime in production mode
109
+ jsxRuntime: 'automatic',
110
+ jsxImportSource: 'react',
111
+ }}),
112
+ ],
113
+ resolve: {{
114
+ alias: {{
115
+ // 🎯 IMPORTANT: Order matters! More specific aliases must come FIRST
116
+ '@/autogenerated': resolve(__dirname),
117
+ '@': resolve(__dirname, '../src'),
118
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
119
+ // Force production JSX runtime - alias dev runtime to production runtime
120
+ 'react/jsx-dev-runtime': 'react/jsx-runtime',
121
+ }},
122
+ conditions: ['browser', 'import', 'module', 'es2020', 'default'],
123
+ mainFields: ['browser', 'module', 'jsnext:main', 'jsnext', 'main'],
124
+ }},
125
+ }});
126
+ """
@@ -0,0 +1,92 @@
1
+ # Vue-specific Vite templates for frontend resources.
2
+
3
+ VUE_VITE_FRONTEND = """{header}
4
+ import {{ resolve }} from 'node:path';
5
+ import vue from '@vitejs/plugin-vue';
6
+ import {{ defineConfig }} from 'vite';
7
+
8
+ export default defineConfig({{
9
+ base: '{base_path}',
10
+ build: {{
11
+ cssCodeSplit: false,
12
+ minify: false,
13
+ modulePreload: false,
14
+ rollupOptions: {{
15
+ output: {{
16
+ entryFileNames: '[name].js',
17
+ format: 'esm',
18
+ }},
19
+ }},
20
+ target: 'esnext',
21
+ }},
22
+ define: {{
23
+ global: 'globalThis',
24
+ }},
25
+ optimizeDeps: {{
26
+ include: [{optimize_deps_include}],
27
+ }},
28
+ plugins: [vue()],
29
+ preview: {{
30
+ host: '0.0.0.0',
31
+ port: {port},
32
+ }},
33
+ resolve: {{
34
+ alias: {{
35
+ '@/autogenerated': resolve(__dirname),
36
+ '@': resolve(__dirname, '../src'),
37
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
38
+ {internal_aliases}
39
+ }},
40
+ }},
41
+ server: {{
42
+ host: '0.0.0.0',
43
+ port: {port},
44
+ hmr: {{
45
+ port: {hmr_port},
46
+ }},
47
+ {proxy_config}
48
+ }},
49
+ }});
50
+ """
51
+
52
+ VUE_VITE_FRONTEND_BUILD = """{header}
53
+ import {{ resolve }} from 'node:path';
54
+ import vue from '@vitejs/plugin-vue';
55
+ import {{ defineConfig }} from 'vite';
56
+
57
+ export default defineConfig({{
58
+ mode: 'production',
59
+ base: '{base_path}',
60
+ build: {{
61
+ cssCodeSplit: false,
62
+ minify: true,
63
+ modulePreload: false,
64
+ rollupOptions: {{
65
+ output: {{
66
+ entryFileNames: '[name].js',
67
+ format: 'esm',
68
+ manualChunks: undefined,
69
+ }},
70
+ }},
71
+ target: 'esnext',
72
+ }},
73
+ define: {{
74
+ global: 'globalThis',
75
+ __DEV__: 'false',
76
+ 'process.env.NODE_ENV': '"production"',
77
+ }},
78
+ optimizeDeps: {{
79
+ include: [{optimize_deps_include}],
80
+ }},
81
+ plugins: [vue()],
82
+ resolve: {{
83
+ alias: {{
84
+ '@/autogenerated': resolve(__dirname),
85
+ '@': resolve(__dirname, '../src'),
86
+ 'set-cookie-parser': resolve(__dirname, '../src/utils/empty-module.js'),
87
+ }},
88
+ conditions: ['browser', 'import', 'module', 'es2020', 'default'],
89
+ mainFields: ['browser', 'module', 'jsnext:main', 'jsnext', 'main'],
90
+ }},
91
+ }});
92
+ """
@@ -15,7 +15,8 @@ load('../../../../topologies/tilt/manifest/constants.star', 'GENERATED_CONFIG_FI
15
15
  # =============================================================================
16
16
 
17
17
 
18
- load('./templates.star', 'TEMPLATE_VITE_FRONTEND', 'TEMPLATE_VITE_FRONTEND_BUILD')
18
+ load('./frameworks/react.star', 'REACT_VITE_FRONTEND', 'REACT_VITE_FRONTEND_BUILD')
19
+ load('./frameworks/vue.star', 'VUE_VITE_FRONTEND', 'VUE_VITE_FRONTEND_BUILD')
19
20
  load('../../../platform/docker/networking/api_path_constants.star', 'get_frontend_base_path')
20
21
  load('./helpers.star',
21
22
  'build_header',
@@ -28,6 +29,17 @@ load('./helpers.star',
28
29
  VITE_FRONTEND_CONFIG_PATH = '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['vite_frontend']
29
30
  VITE_FRONTEND_BUILD_CONFIG_PATH = '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['vite_build']
30
31
 
32
+ _FRAMEWORK_TEMPLATES = {
33
+ 'react': {
34
+ 'dev': REACT_VITE_FRONTEND,
35
+ 'build': REACT_VITE_FRONTEND_BUILD,
36
+ },
37
+ 'vue': {
38
+ 'dev': VUE_VITE_FRONTEND,
39
+ 'build': VUE_VITE_FRONTEND_BUILD,
40
+ },
41
+ }
42
+
31
43
  def _normalize_vite_base_path(base_path):
32
44
  """Vite subpath deploys are more reliable with a trailing slash."""
33
45
  if not base_path:
@@ -40,7 +52,7 @@ def _normalize_vite_base_path(base_path):
40
52
 
41
53
  def generate_frontend(manifest, backend_manifest=None, write_fn=None):
42
54
  """
43
- Generate vite.config.ts for FRONTEND services.
55
+ Generate dev and build Vite configs under the resource's .autogenerated/ directory.
44
56
 
45
57
  Features:
46
58
  - Auto-proxy to backend based on backend manifest port
@@ -52,6 +64,11 @@ def generate_frontend(manifest, backend_manifest=None, write_fn=None):
52
64
  """
53
65
  if manifest.get('appType') != 'frontend':
54
66
  fail("Vite.frontend() requires a frontend manifest, got: " + manifest.get('appType', 'unknown'))
67
+
68
+ framework_id = manifest.get('framework', 'react')
69
+ framework_templates = _FRAMEWORK_TEMPLATES.get(framework_id)
70
+ if framework_templates == None:
71
+ fail("Unknown frontend framework for Vite generation: " + str(framework_id))
55
72
 
56
73
  resource_path = manifest.get('_resource_path', '')
57
74
  stack = manifest.get('stack', 'app')
@@ -75,7 +92,7 @@ def generate_frontend(manifest, backend_manifest=None, write_fn=None):
75
92
  internal_aliases = generate_frontend_internal_aliases(resource_path, manifest, 1)
76
93
  optimize_deps_include = generate_frontend_optimize_deps(manifest)
77
94
 
78
- config = TEMPLATE_VITE_FRONTEND.format(
95
+ config = framework_templates['dev'].format(
79
96
  header=header,
80
97
  base_path=vite_base_path,
81
98
  port=port,
@@ -86,7 +103,7 @@ def generate_frontend(manifest, backend_manifest=None, write_fn=None):
86
103
  )
87
104
 
88
105
  build_cfg_header = build_header('Vite.frontend()', app_name, 'Build-only Vite config')
89
- build_config = TEMPLATE_VITE_FRONTEND_BUILD.format(
106
+ build_config = framework_templates['build'].format(
90
107
  header=build_cfg_header,
91
108
  base_path=vite_base_path,
92
109
  optimize_deps_include=optimize_deps_include,