@tdk-landscape/tdk-cli-core 1.3.97 → 1.3.99

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # TDK CLI — start services on your laptop
2
2
 
3
+ English | [简体中文](README-zh_cn.md) | [繁體中文](README-zh_tw.md) | [日本語](README-ja.md) | [한국어](README-ko.md)
4
+
3
5
  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
6
 
5
7
  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.
@@ -66,6 +66,13 @@ export function validateServiceManifest(value, displayPath) {
66
66
  if ("schemaVersion" in manifest && manifest.schemaVersion !== SERVICE_MANIFEST_SCHEMA_VERSION) {
67
67
  errors.push(`${displayPath}.schemaVersion: unsupported version ${String(manifest.schemaVersion)} (supported: ${SERVICE_MANIFEST_SCHEMA_VERSION})`);
68
68
  }
69
+ const dev = manifest.dev;
70
+ const liveReload = dev && typeof dev === "object" && !Array.isArray(dev)
71
+ ? dev.liveReload
72
+ : undefined;
73
+ if (liveReload !== undefined && typeof liveReload !== "boolean") {
74
+ errors.push(`${displayPath}.dev.liveReload: expected true or false`);
75
+ }
69
76
  if ("smoke" in manifest) {
70
77
  for (const message of validateSmoke(manifest.smoke))
71
78
  errors.push(`${displayPath}.${message}`);
@@ -78,6 +85,9 @@ export function validateServiceManifest(value, displayPath) {
78
85
  return [`${displayPath}.${field}: unknown field is preserved`];
79
86
  return [];
80
87
  });
88
+ if (liveReload === true && manifest.language !== "go") {
89
+ warnings.push(`${displayPath}.dev.liveReload: only applies to Go services (language "go"); it is ignored here`);
90
+ }
81
91
  return { errors, warnings, manifest };
82
92
  }
83
93
  export function validateServiceManifestFile(filePath, displayPath) {
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.97",
4
+ "version": "1.3.99",
5
5
  "description": "TDK CLI — start services on your laptop.",
6
6
  "keywords": [
7
7
  "tdk",
@@ -62,7 +62,7 @@ RUN CGO_ENABLED=0 go build -o /out/app .
62
62
 
63
63
  FROM build AS test
64
64
  RUN go test ./...
65
-
65
+ {go_development}
66
66
  FROM debian:bookworm-slim AS production
67
67
 
68
68
  {curl}
@@ -102,14 +102,35 @@ EXPOSE {port}
102
102
  CMD ["app"]
103
103
  """
104
104
 
105
- def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND):
105
+ # Opt-in Go development target (`dev.liveReload`): the toolchain image with Air, which rebuilds and restarts the app when a `.go`
106
+ # file changes. Dependencies are downloaded in their own layer and the module and build caches live in BuildKit cache mounts, so an
107
+ # ordinary source edit recompiles incrementally. `go.mod`, `go.sum` and the Dockerfile still rebuild the image.
108
+ _GO_DEVELOPMENT = """
109
+ FROM golang:1.23-bookworm AS development
110
+ RUN go install github.com/air-verse/air@v1.61.7
111
+ WORKDIR /app/{res_path}
112
+ COPY {res_path}/go.* ./
113
+ RUN --mount=type=cache,target=/go/pkg/mod go mod download
114
+ COPY {res_path}/ ./
115
+ ENV GOFLAGS=-buildvcs=false
116
+ # Prime the build cache inside the image (a cache mount would not be kept), so the first edit recompiles only what changed.
117
+ RUN go build -o /tmp/app .
118
+ EXPOSE {port}
119
+ CMD ["sh", "-c", "exec air --build.cmd 'go build -o /tmp/app .' --build.bin /tmp/app --build.include_ext go"]
120
+ """
121
+
122
+ def generate_language_dockerfile(res_path, language, port = BASE_PORT_BACKEND, dev = False):
106
123
  """Dockerfile text for a provider-owned backend language, or fail for an unknown one."""
107
124
  template = {"python": _PYTHON, "go": _GO, "rust": _RUST}.get(language)
108
125
  if template == None:
109
126
  fail("No Dockerfile generator for backend language '{}'".format(language))
127
+ go_development = ""
128
+ if dev and language == "go":
129
+ go_development = _GO_DEVELOPMENT.format(res_path = res_path, port = port)
110
130
  return template.format(
111
131
  res_path = res_path,
112
132
  port = port,
133
+ go_development = go_development,
113
134
  curl = _CURL,
114
135
  healthcheck = _HEALTHCHECK.format(port = port),
115
136
  )
@@ -120,3 +141,13 @@ def is_provider_owned_language(manifest):
120
141
  if not manifest:
121
142
  return False
122
143
  return manifest.get("appType", "backend") == "backend" and manifest.get("language", "bun") in PROVIDER_OWNED_LANGUAGES
144
+
145
+
146
+ def go_live_reload_enabled(manifest):
147
+ """True for a Go service that opted in with `dev.liveReload: true` (see _GO_DEVELOPMENT); anything else stays off."""
148
+ if not manifest or manifest.get("language", "bun") != "go":
149
+ return False
150
+ dev = manifest.get("dev")
151
+ if type(dev) != "dict":
152
+ return False
153
+ return dev.get("liveReload") == True
@@ -8,6 +8,7 @@ load(
8
8
  "./dockerfile/language_dockerfile.star",
9
9
  "generate_language_dockerfile",
10
10
  "is_provider_owned_language",
11
+ "go_live_reload_enabled",
11
12
  "LANGUAGE_REQUIRED_FILES",
12
13
  )
13
14
  load("./config/dockerignore.star", "generate_dockerignore")
@@ -26,6 +27,7 @@ Docker = struct(
26
27
  migrator = generate_migrator_dockerfile,
27
28
  backend_language = generate_language_dockerfile,
28
29
  is_provider_owned_language = is_provider_owned_language,
30
+ go_live_reload = go_live_reload_enabled,
29
31
  language_required_files = LANGUAGE_REQUIRED_FILES,
30
32
  generate_dockerignore = generate_dockerignore,
31
33
  frontend_compose = generate_frontend_compose,
@@ -215,7 +215,7 @@ def _live_update_sync_source(project_root, full_res_path, sync_path):
215
215
  return relative_path
216
216
 
217
217
 
218
- def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', language='bun'):
218
+ def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', language='bun', go_live_reload=False):
219
219
  """Build live update sync and run rules for a resource.
220
220
 
221
221
  Python backends reload their own process after a sync (uvicorn --reload), so they have
@@ -225,9 +225,22 @@ def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', la
225
225
  # Go and Rust are compiled into the image and have no reload process, so syncing sources would leave the
226
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 in ['go', 'rust']:
228
+ if language == 'rust' or (language == 'go' and not go_live_reload):
229
229
  return []
230
230
 
231
+ if go_live_reload:
232
+ # Opted-in Go development loop: sync the sources into the development image, where Air rebuilds and restarts.
233
+ # Dependency and Dockerfile changes cannot be picked up by a restart, so they fall back to a full image rebuild.
234
+ source_dir = _live_update_sync_source(project_root, full_res_path, '')
235
+ return [
236
+ fall_back_on([
237
+ source_dir + 'go.mod',
238
+ source_dir + 'go.sum',
239
+ source_dir + OUTPUT.AUTOGENERATED_FOLDER,
240
+ ]),
241
+ sync(source_dir.rstrip('/'), '/app/' + full_res_path),
242
+ ]
243
+
231
244
  live_update_rules = []
232
245
  for sync_path in syncs:
233
246
  # Python images never read the npm/Bun registry config, and the engine does not
@@ -285,6 +298,7 @@ def _register_docker_build(config, live_update_rules):
285
298
  _resolve_dockerfile(config),
286
299
  live_update_rules,
287
300
  config['res_deps'],
301
+ target = 'development' if Docker.go_live_reload(config.get('manifest', {})) else 'production',
288
302
  )
289
303
 
290
304
 
@@ -408,6 +422,7 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
408
422
  config['syncs'],
409
423
  project_root=config.get('project_root', ''),
410
424
  language=config.get('manifest', {}).get('language', 'bun'),
425
+ go_live_reload=Docker.go_live_reload(config.get('manifest', {})),
411
426
  )
412
427
  _register_docker_build(config, live_update_rules)
413
428
  if defers_start:
@@ -105,7 +105,7 @@ def _get_resource_only_paths(resource_path):
105
105
 
106
106
  return only_paths
107
107
 
108
- def _build_typescript_service(name, context, dockerfile, live_update_rules, deps, env=None):
108
+ def _build_typescript_service(name, context, dockerfile, live_update_rules, deps, env=None, target='production'):
109
109
  """Build a TypeScript service with Docker.
110
110
 
111
111
  Args:
@@ -115,6 +115,7 @@ def _build_typescript_service(name, context, dockerfile, live_update_rules, deps
115
115
  live_update_rules: Live update configuration
116
116
  deps: Resource dependencies
117
117
  env: Dict of environment variables to pass to container (optional)
118
+ target: Dockerfile target to build (`development` for an opted-in Go service, otherwise `production`)
118
119
  """
119
120
  # Extract service path from dockerfile path (e.g., 'services/product/{domain}/{service-name}')
120
121
  # The dockerfile parameter looks like: 'services/product/{domain}/{service-name}/autogenerated/Dockerfile.autogenerated'
@@ -154,7 +155,7 @@ def _build_typescript_service(name, context, dockerfile, live_update_rules, deps
154
155
  name,
155
156
  context,
156
157
  dockerfile=dockerfile,
157
- target='production',
158
+ target=target,
158
159
  live_update=live_update_rules,
159
160
  only=_get_resource_only_paths(resource_path),
160
161
  ignore=ignore_patterns,
@@ -382,6 +382,7 @@ def _generate_all_configs_for_resource(
382
382
  res_path=resource_path,
383
383
  language=manifest.get('language'),
384
384
  port=manifest.get('port', 4000),
385
+ dev=Docker.go_live_reload(manifest),
385
386
  )
386
387
  else:
387
388
  dockerfile_content = Docker.backend(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdk-landscape/tdk-cli-core",
3
- "version": "1.3.97",
3
+ "version": "1.3.99",
4
4
  "description": "TDK CLI — start services on your laptop.",
5
5
  "homepage": "https://tdk-landscape.github.io/tdk-website",
6
6
  "repository": {