@tdk-landscape/tdk-cli-core 1.3.91 → 1.3.93
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/mcp.js +87 -0
- package/cli/dist/backend-frameworks/registry.js +16 -0
- package/cli/dist/commands/completion.js +3 -3
- package/cli/dist/commands/doctor.js +8 -2
- package/cli/dist/commands/networks.js +2 -1
- package/cli/dist/commands/project.js +15 -2
- package/cli/dist/commands/resource.js +37 -22
- package/cli/dist/commands/up.js +37 -1
- package/cli/dist/types/index.js +11 -0
- package/cli/dist/utils/constants.js +4 -0
- package/cli/dist/utils/doctor-runtime.js +3 -2
- package/cli/dist/utils/doctor-wiring.js +2 -1
- package/cli/dist/utils/env-validator.js +118 -30
- package/cli/dist/utils/resource-kind.js +8 -0
- package/cli/dist/utils/service-manifest.js +23 -3
- package/cli/dist/utils/service-urls.js +4 -3
- package/cli/dist/utils/services.js +2 -1
- package/cli/dist/utils/smoke.js +225 -0
- package/cli/package.json +2 -1
- package/engine/schemas/service-schema.json +145 -27
- package/engine/topologies/platform/docker/auth/auth.star +66 -28
- package/engine/topologies/platform/docker/compose/compose.star +8 -26
- package/engine/topologies/platform/docker/layers/l3_builder_layers.star +3 -0
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +3 -0
- package/engine/topologies/platform/docker/networking/api_path_constants.star +1 -57
- package/engine/topologies/platform/docker/secrets/env_secrets.star +72 -0
- package/engine/topologies/tilt/common/utils.star +0 -2
- package/engine/topologies/tilt/common/utils_env.star +72 -25
- package/engine/topologies/tilt/manifest/constants.star +2 -0
- package/engine/topologies/tilt/manifest/json_to_yaml.py +8 -3
- package/engine/topologies/tilt/manifest/loader.star +7 -0
- package/engine/topologies/tilt/resources/orchestrator/generators/env.star +43 -96
- package/package.json +1 -1
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"appType": {
|
|
10
10
|
"const": "backend"
|
|
11
11
|
}
|
|
12
|
-
}
|
|
12
|
+
},
|
|
13
|
+
"required": [
|
|
14
|
+
"appType"
|
|
15
|
+
]
|
|
13
16
|
},
|
|
14
17
|
"then": {
|
|
15
18
|
"properties": {
|
|
@@ -29,7 +32,10 @@
|
|
|
29
32
|
"appType": {
|
|
30
33
|
"const": "frontend"
|
|
31
34
|
}
|
|
32
|
-
}
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"appType"
|
|
38
|
+
]
|
|
33
39
|
},
|
|
34
40
|
"then": {
|
|
35
41
|
"properties": {
|
|
@@ -46,7 +52,10 @@
|
|
|
46
52
|
"appType": {
|
|
47
53
|
"const": "worker"
|
|
48
54
|
}
|
|
49
|
-
}
|
|
55
|
+
},
|
|
56
|
+
"required": [
|
|
57
|
+
"appType"
|
|
58
|
+
]
|
|
50
59
|
},
|
|
51
60
|
"then": {
|
|
52
61
|
"properties": {
|
|
@@ -67,7 +76,10 @@
|
|
|
67
76
|
"appType": {
|
|
68
77
|
"const": "bring-your-own"
|
|
69
78
|
}
|
|
70
|
-
}
|
|
79
|
+
},
|
|
80
|
+
"required": [
|
|
81
|
+
"appType"
|
|
82
|
+
]
|
|
71
83
|
},
|
|
72
84
|
"then": {
|
|
73
85
|
"properties": {
|
|
@@ -107,7 +119,7 @@
|
|
|
107
119
|
"type": "string"
|
|
108
120
|
},
|
|
109
121
|
"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, 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.",
|
|
122
|
+
"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• mcp - Model Context Protocol server (an HTTP service on Bun, run like a backend)\n• bring-your-own - Wrap an existing service. No app scaffold. Needs Dockerfile or --image.",
|
|
111
123
|
"enum": [
|
|
112
124
|
"backend",
|
|
113
125
|
"frontend",
|
|
@@ -115,6 +127,7 @@
|
|
|
115
127
|
"worker",
|
|
116
128
|
"migrator",
|
|
117
129
|
"library",
|
|
130
|
+
"mcp",
|
|
118
131
|
"bring-your-own"
|
|
119
132
|
],
|
|
120
133
|
"type": "string"
|
|
@@ -179,10 +192,10 @@
|
|
|
179
192
|
"type": "object"
|
|
180
193
|
},
|
|
181
194
|
"secrets": {
|
|
182
|
-
"description": "
|
|
195
|
+
"description": "Names of secrets the resource needs. By default each is read from the project .env (gitignored) and injected into the container; with TDK_SECRET_PROVIDER=infisical they are left to Infisical. NO secret values in params/envVars or anywhere in service.json.",
|
|
183
196
|
"properties": {
|
|
184
197
|
"required": {
|
|
185
|
-
"description": "Required secret names.
|
|
198
|
+
"description": "Required secret names. The resource does not start if any is missing from the project .env.",
|
|
186
199
|
"items": {
|
|
187
200
|
"type": "string",
|
|
188
201
|
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
@@ -191,7 +204,7 @@
|
|
|
191
204
|
"uniqueItems": true
|
|
192
205
|
},
|
|
193
206
|
"optional": {
|
|
194
|
-
"description": "Optional secret names.
|
|
207
|
+
"description": "Optional secret names. The resource starts with the variable empty when it is missing from the project .env.",
|
|
195
208
|
"items": {
|
|
196
209
|
"type": "string",
|
|
197
210
|
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
@@ -206,7 +219,7 @@
|
|
|
206
219
|
"type": "object"
|
|
207
220
|
},
|
|
208
221
|
"featuresEnabled": {
|
|
209
|
-
"description": "Platform features:\n• nats - NATS JetStream eventing\n• prisma - DB ORM & migrations\n• redis - Caching layer\n• infisical - Secrets management\n• vitest - Test framework\n• traefik - Reverse proxy routing\n• websocket - WS support\n• graphql - GraphQL server\n• grpc - gRPC protocol",
|
|
222
|
+
"description": "Platform features:\n• nats - NATS JetStream eventing\n• prisma - DB ORM & migrations\n• redis - Caching layer\n• infisical - Secrets management\n• vitest - Test framework\n• traefik - Reverse proxy routing\n• websocket - WS support\n• graphql - GraphQL server\n• grpc - gRPC protocol\n• vite, tsconfig, dockerfile, nginx, ... - generator features that `tdk resource` writes into new files",
|
|
210
223
|
"items": {
|
|
211
224
|
"enum": [
|
|
212
225
|
"nats",
|
|
@@ -217,7 +230,21 @@
|
|
|
217
230
|
"traefik",
|
|
218
231
|
"websocket",
|
|
219
232
|
"graphql",
|
|
220
|
-
"grpc"
|
|
233
|
+
"grpc",
|
|
234
|
+
"api-client",
|
|
235
|
+
"env-config",
|
|
236
|
+
"api-index",
|
|
237
|
+
"vite",
|
|
238
|
+
"tsconfig",
|
|
239
|
+
"dockerfile",
|
|
240
|
+
"npmrc",
|
|
241
|
+
"bunfig",
|
|
242
|
+
"nginx",
|
|
243
|
+
"env",
|
|
244
|
+
"playwright",
|
|
245
|
+
"agents-md",
|
|
246
|
+
"ddd",
|
|
247
|
+
"sablier"
|
|
221
248
|
],
|
|
222
249
|
"type": "string"
|
|
223
250
|
},
|
|
@@ -229,19 +256,7 @@
|
|
|
229
256
|
"examples": [
|
|
230
257
|
"react",
|
|
231
258
|
"vue",
|
|
232
|
-
"
|
|
233
|
-
"preact",
|
|
234
|
-
"lit",
|
|
235
|
-
"solid",
|
|
236
|
-
"qwik",
|
|
237
|
-
"vanilla",
|
|
238
|
-
"hono",
|
|
239
|
-
"express",
|
|
240
|
-
"elysia",
|
|
241
|
-
"fastify",
|
|
242
|
-
"nestjs",
|
|
243
|
-
"koa",
|
|
244
|
-
"h3"
|
|
259
|
+
"solid"
|
|
245
260
|
],
|
|
246
261
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
247
262
|
"type": "string"
|
|
@@ -251,8 +266,7 @@
|
|
|
251
266
|
"examples": [
|
|
252
267
|
"bun",
|
|
253
268
|
"python",
|
|
254
|
-
"go"
|
|
255
|
-
"rust"
|
|
269
|
+
"go"
|
|
256
270
|
],
|
|
257
271
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
258
272
|
"type": "string"
|
|
@@ -360,6 +374,77 @@
|
|
|
360
374
|
},
|
|
361
375
|
"type": "array"
|
|
362
376
|
},
|
|
377
|
+
"smoke": {
|
|
378
|
+
"description": "Post-start check run by `tdk up` through the public proxy URL (not inside the container): write a record, read it back. `tdk up` fails if a step misses. Absent means no smoke check. Workers and resources with exposeViaProxy false are skipped.",
|
|
379
|
+
"type": "object",
|
|
380
|
+
"required": [
|
|
381
|
+
"via",
|
|
382
|
+
"steps"
|
|
383
|
+
],
|
|
384
|
+
"additionalProperties": false,
|
|
385
|
+
"properties": {
|
|
386
|
+
"via": {
|
|
387
|
+
"const": "proxy",
|
|
388
|
+
"description": "Only `proxy` is supported: the request goes to the URL `tdk up` prints."
|
|
389
|
+
},
|
|
390
|
+
"timeoutSeconds": {
|
|
391
|
+
"type": "number",
|
|
392
|
+
"exclusiveMinimum": 0,
|
|
393
|
+
"default": 60,
|
|
394
|
+
"description": "How long to wait for the route to come up. Connection errors and 404/502/503/504 are retried until then; any other wrong answer fails at once."
|
|
395
|
+
},
|
|
396
|
+
"steps": {
|
|
397
|
+
"type": "array",
|
|
398
|
+
"minItems": 1,
|
|
399
|
+
"items": {
|
|
400
|
+
"type": "object",
|
|
401
|
+
"required": [
|
|
402
|
+
"path"
|
|
403
|
+
],
|
|
404
|
+
"additionalProperties": false,
|
|
405
|
+
"properties": {
|
|
406
|
+
"name": {
|
|
407
|
+
"type": "string",
|
|
408
|
+
"description": "Optional display name for this check."
|
|
409
|
+
},
|
|
410
|
+
"method": {
|
|
411
|
+
"type": "string",
|
|
412
|
+
"default": "GET",
|
|
413
|
+
"description": "HTTP method. Defaults to GET."
|
|
414
|
+
},
|
|
415
|
+
"path": {
|
|
416
|
+
"type": "string",
|
|
417
|
+
"pattern": "^/(?!/)",
|
|
418
|
+
"description": "Appended to the service's public base URL. `{{name}}` is replaced by a value saved by an earlier step."
|
|
419
|
+
},
|
|
420
|
+
"body": {
|
|
421
|
+
"description": "JSON request body."
|
|
422
|
+
},
|
|
423
|
+
"expect": {
|
|
424
|
+
"type": "integer",
|
|
425
|
+
"minimum": 100,
|
|
426
|
+
"maximum": 599,
|
|
427
|
+
"default": 200,
|
|
428
|
+
"description": "Expected HTTP status code, from 100 through 599."
|
|
429
|
+
},
|
|
430
|
+
"bodyContains": {
|
|
431
|
+
"type": "string",
|
|
432
|
+
"description": "Optional text that must occur in the response body."
|
|
433
|
+
},
|
|
434
|
+
"save": {
|
|
435
|
+
"type": "object",
|
|
436
|
+
"additionalProperties": {
|
|
437
|
+
"type": "string",
|
|
438
|
+
"pattern": "^\\$\\."
|
|
439
|
+
},
|
|
440
|
+
"description": "Saves response fields for later steps, for example {\"id\": \"$.id\"}."
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
"description": "Non-empty ordered list of HTTP checks to run after the resource starts."
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
},
|
|
363
448
|
"traefik": {
|
|
364
449
|
"description": "Custom Traefik routing. Auto-generated if not specified.",
|
|
365
450
|
"properties": {
|
|
@@ -377,14 +462,47 @@
|
|
|
377
462
|
}
|
|
378
463
|
},
|
|
379
464
|
"type": "object"
|
|
465
|
+
},
|
|
466
|
+
"type": {
|
|
467
|
+
"description": "Legacy alias for appType, still read from older service.json files. Use appType in new files.",
|
|
468
|
+
"enum": [
|
|
469
|
+
"backend",
|
|
470
|
+
"frontend",
|
|
471
|
+
"sdk",
|
|
472
|
+
"worker",
|
|
473
|
+
"migrator",
|
|
474
|
+
"library",
|
|
475
|
+
"mcp",
|
|
476
|
+
"bring-your-own"
|
|
477
|
+
],
|
|
478
|
+
"type": "string"
|
|
479
|
+
},
|
|
480
|
+
"enabled": {
|
|
481
|
+
"description": "Whether this resource is enabled in its project configuration.",
|
|
482
|
+
"type": "boolean"
|
|
483
|
+
},
|
|
484
|
+
"apiPath": {
|
|
485
|
+
"description": "API path exposed by this resource, when applicable.",
|
|
486
|
+
"type": "string"
|
|
380
487
|
}
|
|
381
488
|
},
|
|
382
489
|
"required": [
|
|
383
490
|
"appName",
|
|
384
|
-
"appType",
|
|
385
491
|
"stack",
|
|
386
492
|
"schemaVersion"
|
|
387
493
|
],
|
|
388
494
|
"title": "TDK Landscape Resource Configuration",
|
|
389
|
-
"type": "object"
|
|
495
|
+
"type": "object",
|
|
496
|
+
"anyOf": [
|
|
497
|
+
{
|
|
498
|
+
"required": [
|
|
499
|
+
"appType"
|
|
500
|
+
]
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"required": [
|
|
504
|
+
"type"
|
|
505
|
+
]
|
|
506
|
+
}
|
|
507
|
+
]
|
|
390
508
|
}
|
|
@@ -12,38 +12,80 @@
|
|
|
12
12
|
# them as the auth_config dict.
|
|
13
13
|
# =============================================================================
|
|
14
14
|
|
|
15
|
-
# Default values for auth configuration
|
|
15
|
+
# Default values for auth configuration.
|
|
16
|
+
#
|
|
17
|
+
# There is deliberately no default JWT secret: a secret that is the same in every project is not a secret. The JWT secret is
|
|
18
|
+
# generated once per project by the CLI into the project `.env` (JWT_SECRET) and Compose reads it from there.
|
|
16
19
|
DEFAULT_AUTH_MODE = 'local-jwt'
|
|
17
|
-
DEFAULT_JWT_SECRET = 'local-development-secret-min-32-chars-long'
|
|
18
20
|
DEFAULT_IDENTITY_RESOURCE_URL = 'http://identity-service:3000'
|
|
19
21
|
|
|
22
|
+
# Shown by Compose when the project `.env` has no JWT_SECRET.
|
|
23
|
+
JWT_SECRET_MISSING_MESSAGE = 'JWT_SECRET is not set. Run `tdk up` (it adds a generated one to the project .env) or add JWT_SECRET to .env'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _setting(auth_config, key, default):
|
|
27
|
+
"""A configured value, or the default when the key is missing OR present with a None value.
|
|
28
|
+
|
|
29
|
+
Callers build the config dict with every key present and None for anything unset, and `dict.get(key, default)` returns that
|
|
30
|
+
None instead of the default, which used to turn into the literal text `AUTH_MODE=None` in generated files.
|
|
31
|
+
"""
|
|
32
|
+
if not auth_config:
|
|
33
|
+
return default
|
|
34
|
+
value = auth_config.get(key)
|
|
35
|
+
if value == None or value == '':
|
|
36
|
+
return default
|
|
37
|
+
return value
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
|
|
40
|
+
def _first_set(*values):
|
|
41
|
+
"""The first value that is not None or an empty string, else None."""
|
|
42
|
+
for value in values:
|
|
43
|
+
if value != None and value != '':
|
|
44
|
+
return value
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def auth_config_from_manifest(manifest):
|
|
49
|
+
"""Build the auth configuration both the env file and the Compose entry use, from one place.
|
|
50
|
+
|
|
51
|
+
The two writers used to build this separately and drifted: the env file read `identityServiceUrl` while Compose read
|
|
52
|
+
`envVars.IDENTITY_RESOURCE_URL`, so setting one field changed only one output.
|
|
53
|
+
|
|
54
|
+
- authMode: the manifest's `authMode`, else `identity-service` when `dependsOn` contains `identity`, else unset (local-jwt).
|
|
55
|
+
- identityServiceUrl: the manifest's `identityServiceUrl`. The deprecated `params` / `envVars` entry named
|
|
56
|
+
`IDENTITY_RESOURCE_URL` is still honoured when the field is absent, so an existing manifest keeps working.
|
|
23
57
|
|
|
24
58
|
Args:
|
|
25
|
-
|
|
59
|
+
manifest: The service manifest dict (may be None)
|
|
26
60
|
|
|
27
61
|
Returns:
|
|
28
|
-
|
|
62
|
+
dict: {'authMode': str or None, 'identityServiceUrl': str or None}; None means "use the default"
|
|
29
63
|
"""
|
|
30
|
-
if not
|
|
31
|
-
return
|
|
32
|
-
|
|
64
|
+
if not manifest:
|
|
65
|
+
return {'authMode': None, 'identityServiceUrl': None}
|
|
66
|
+
|
|
67
|
+
auth_mode = _first_set(
|
|
68
|
+
manifest.get('authMode'),
|
|
69
|
+
'identity-service' if 'identity' in (manifest.get('dependsOn') or []) else None,
|
|
70
|
+
)
|
|
71
|
+
identity_url = _first_set(
|
|
72
|
+
manifest.get('identityServiceUrl'),
|
|
73
|
+
(manifest.get('params') or {}).get('IDENTITY_RESOURCE_URL'),
|
|
74
|
+
(manifest.get('envVars') or {}).get('IDENTITY_RESOURCE_URL'),
|
|
75
|
+
)
|
|
76
|
+
return {'authMode': auth_mode, 'identityServiceUrl': identity_url}
|
|
33
77
|
|
|
34
78
|
|
|
35
|
-
def
|
|
36
|
-
"""Get
|
|
79
|
+
def get_auth_mode(auth_config):
|
|
80
|
+
"""Get auth mode from auth config with default fallback.
|
|
37
81
|
|
|
38
82
|
Args:
|
|
39
83
|
auth_config: Auth configuration dictionary (may be None or empty)
|
|
40
84
|
|
|
41
85
|
Returns:
|
|
42
|
-
str:
|
|
86
|
+
str: Auth mode ('local-jwt' or 'identity-service')
|
|
43
87
|
"""
|
|
44
|
-
|
|
45
|
-
return DEFAULT_JWT_SECRET
|
|
46
|
-
return auth_config.get('jwtSecret', DEFAULT_JWT_SECRET)
|
|
88
|
+
return _setting(auth_config, 'authMode', DEFAULT_AUTH_MODE)
|
|
47
89
|
|
|
48
90
|
|
|
49
91
|
def get_identity_resource_url(auth_config):
|
|
@@ -55,9 +97,7 @@ def get_identity_resource_url(auth_config):
|
|
|
55
97
|
Returns:
|
|
56
98
|
str: URL for identity service
|
|
57
99
|
"""
|
|
58
|
-
|
|
59
|
-
return DEFAULT_IDENTITY_RESOURCE_URL
|
|
60
|
-
return auth_config.get('identityServiceUrl', DEFAULT_IDENTITY_RESOURCE_URL)
|
|
100
|
+
return _setting(auth_config, 'identityServiceUrl', DEFAULT_IDENTITY_RESOURCE_URL)
|
|
61
101
|
|
|
62
102
|
|
|
63
103
|
def get_auth_config(auth_config):
|
|
@@ -67,11 +107,10 @@ def get_auth_config(auth_config):
|
|
|
67
107
|
auth_config: Auth configuration dictionary (may be None or empty)
|
|
68
108
|
|
|
69
109
|
Returns:
|
|
70
|
-
struct: Auth configuration with mode
|
|
110
|
+
struct: Auth configuration with mode and identity_resource_url (the JWT secret lives in the project .env, not here)
|
|
71
111
|
"""
|
|
72
112
|
return struct(
|
|
73
113
|
mode = get_auth_mode(auth_config),
|
|
74
|
-
jwt_secret = get_jwt_secret(auth_config),
|
|
75
114
|
identity_resource_url = get_identity_resource_url(auth_config),
|
|
76
115
|
)
|
|
77
116
|
|
|
@@ -113,12 +152,12 @@ def generate_docker_compose_auth_env(auth_config):
|
|
|
113
152
|
str: Formatted auth environment variables for docker-compose
|
|
114
153
|
"""
|
|
115
154
|
auth_mode = get_auth_mode(auth_config)
|
|
116
|
-
jwt_secret = get_jwt_secret(auth_config)
|
|
117
155
|
identity_resource_url = get_identity_resource_url(auth_config)
|
|
118
156
|
|
|
119
157
|
if auth_mode == 'local-jwt':
|
|
158
|
+
# Required from the project .env (see JWT_SECRET_MISSING_MESSAGE); there is no shared default.
|
|
120
159
|
return """ - AUTH_MODE=local-jwt
|
|
121
|
-
- JWT_SECRET=${JWT_SECRET
|
|
160
|
+
- JWT_SECRET=${JWT_SECRET:?%(message)s}""" % {'message': JWT_SECRET_MISSING_MESSAGE}
|
|
122
161
|
else:
|
|
123
162
|
return """ - AUTH_MODE=identity-service
|
|
124
163
|
- IDENTITY_RESOURCE_URL=${IDENTITY_RESOURCE_URL:-%(identity_resource_url)s}""" % {'identity_resource_url': identity_resource_url}
|
|
@@ -128,7 +167,9 @@ def generate_env_file_auth_section(auth_config):
|
|
|
128
167
|
"""Generate .env file auth section content.
|
|
129
168
|
|
|
130
169
|
This function returns the formatted authentication configuration section
|
|
131
|
-
for .env.backend.autogenerated files.
|
|
170
|
+
for .env.backend.autogenerated files. These files are generated, checked by
|
|
171
|
+
`tdk config verify` and never carry secrets, so there is no JWT secret here:
|
|
172
|
+
in local-jwt mode Compose takes JWT_SECRET from the project .env.
|
|
132
173
|
|
|
133
174
|
Args:
|
|
134
175
|
auth_config: Auth configuration dictionary (may be None or empty)
|
|
@@ -137,7 +178,6 @@ def generate_env_file_auth_section(auth_config):
|
|
|
137
178
|
str: Formatted auth section for .env files
|
|
138
179
|
"""
|
|
139
180
|
auth_mode = get_auth_mode(auth_config)
|
|
140
|
-
jwt_secret = get_jwt_secret(auth_config)
|
|
141
181
|
identity_resource_url = get_identity_resource_url(auth_config)
|
|
142
182
|
|
|
143
183
|
content = """
|
|
@@ -146,9 +186,7 @@ def generate_env_file_auth_section(auth_config):
|
|
|
146
186
|
AUTH_MODE={auth_mode}
|
|
147
187
|
""".format(auth_mode=auth_mode)
|
|
148
188
|
|
|
149
|
-
if auth_mode
|
|
150
|
-
content += "JWT_SECRET={jwt_secret}\n".format(jwt_secret=jwt_secret)
|
|
151
|
-
else:
|
|
189
|
+
if auth_mode != 'local-jwt':
|
|
152
190
|
content += "IDENTITY_RESOURCE_URL={identity_resource_url}\n".format(identity_resource_url=identity_resource_url)
|
|
153
191
|
|
|
154
192
|
return content
|
|
@@ -156,8 +194,8 @@ AUTH_MODE={auth_mode}
|
|
|
156
194
|
|
|
157
195
|
# Export all functions as a struct for easy importing
|
|
158
196
|
AuthConfig = struct(
|
|
197
|
+
from_manifest = auth_config_from_manifest,
|
|
159
198
|
get_auth_mode = get_auth_mode,
|
|
160
|
-
get_jwt_secret = get_jwt_secret,
|
|
161
199
|
get_identity_resource_url = get_identity_resource_url,
|
|
162
200
|
get_auth_config = get_auth_config,
|
|
163
201
|
is_local_jwt_mode = is_local_jwt_mode,
|
|
@@ -20,6 +20,7 @@ load("./compose_helpers.star", "with_runtime_image_tag", "compose_build_config")
|
|
|
20
20
|
load("../networking/traefik.star", "get_frontend_traefik_labels", "get_backend_traefik_labels")
|
|
21
21
|
load("../networking/api_path_constants.star", "get_api_path_for_domain", "get_frontend_base_path")
|
|
22
22
|
load("../secrets/infisical.star", "get_infisical_environment_vars")
|
|
23
|
+
load("../secrets/env_secrets.star", "get_secret_environment_lines")
|
|
23
24
|
load("../auth/auth.star", "AuthConfig")
|
|
24
25
|
|
|
25
26
|
load("../constants.star", "PlatformDockerConstants")
|
|
@@ -189,32 +190,12 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
189
190
|
nats_queue = nats_cfg.get('queueGroup', stack + '_backend_svc')
|
|
190
191
|
nats_url = PlatformDockerConstants.NATS_URL
|
|
191
192
|
|
|
192
|
-
# Auth configuration
|
|
193
|
-
|
|
194
|
-
# Auto-detect identity-service mode if 'identity' is in dependsOn
|
|
195
|
-
internal_deps = manifest.get('dependsOn', []) if manifest else []
|
|
196
|
-
has_identity_dep = 'identity' in internal_deps
|
|
197
|
-
|
|
198
|
-
# If manifest has authMode, use it; otherwise auto-detect based on identity dependency
|
|
199
|
-
manifest_auth_mode = manifest.get('authMode') if manifest else None
|
|
200
|
-
if manifest_auth_mode:
|
|
201
|
-
auth_mode = manifest_auth_mode
|
|
202
|
-
elif has_identity_dep:
|
|
203
|
-
auth_mode = 'identity-service'
|
|
204
|
-
else:
|
|
205
|
-
auth_mode = None
|
|
206
|
-
|
|
207
|
-
auth_config = {
|
|
208
|
-
'authMode': auth_mode,
|
|
209
|
-
'jwtSecret': manifest.get('jwtSecret') if manifest else None,
|
|
210
|
-
'identityServiceUrl': manifest.get('envVars', {}).get('IDENTITY_RESOURCE_URL') if manifest else None,
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
# If we auto-detected identity-service mode but no URL is set, use default
|
|
214
|
-
if auth_mode == 'identity-service' and not auth_config['identityServiceUrl']:
|
|
215
|
-
auth_config['identityServiceUrl'] = AuthConfig.get_identity_resource_url(None)
|
|
193
|
+
# Auth configuration: one shared builder keeps this identical to the generated env file (see AuthConfig.from_manifest).
|
|
194
|
+
auth_config = AuthConfig.from_manifest(manifest)
|
|
216
195
|
|
|
217
196
|
auth_env = AuthConfig.generate_docker_compose_auth_env(auth_config)
|
|
197
|
+
# secrets.required / secrets.optional, read from the project .env by Compose (empty with TDK_SECRET_PROVIDER=infisical)
|
|
198
|
+
secrets_env = get_secret_environment_lines(manifest, res_name)
|
|
218
199
|
|
|
219
200
|
# Use .autogenerated/Dockerfile.app.autogenerated by default for build config (primary only)
|
|
220
201
|
if manifest and manifest.get('appType') == 'bring-your-own':
|
|
@@ -240,7 +221,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
240
221
|
|
|
241
222
|
# REMOVED: External port exposure - all services accessed via Traefik only
|
|
242
223
|
# Services are available at {project}.localhost/api/v1/{service}
|
|
243
|
-
#
|
|
224
|
+
# Inside the Docker network a service listens on its own port (manifest.port, passed to it as PORT), for service-to-service calls
|
|
244
225
|
ports_section = "" # No external ports - Traefik-only access
|
|
245
226
|
|
|
246
227
|
# Determine service name based on instance
|
|
@@ -325,7 +306,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
325
306
|
# to skip Prisma auto-migration at container boot (e.g. while diagnosing a
|
|
326
307
|
# migration/CLI issue) without touching the generator or any service code.
|
|
327
308
|
- AUTO_MIGRATE=${{AUTO_MIGRATE:-true}}
|
|
328
|
-
{auth_env}
|
|
309
|
+
{auth_env}{secrets_env}
|
|
329
310
|
labels:
|
|
330
311
|
{traefik_labels}
|
|
331
312
|
networks:
|
|
@@ -373,6 +354,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
373
354
|
healthcheck_section=healthcheck_section,
|
|
374
355
|
infisical_env=infisical_env,
|
|
375
356
|
auth_env=auth_env,
|
|
357
|
+
secrets_env=secrets_env,
|
|
376
358
|
network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
|
|
377
359
|
network_backend=PlatformDockerConstants.NETWORK_BACKEND,
|
|
378
360
|
network_database=PlatformDockerConstants.NETWORK_DATABASE,
|
|
@@ -28,6 +28,9 @@ load('./prisma/prisma_build.star',
|
|
|
28
28
|
'prisma_normalize_output',
|
|
29
29
|
'L3_generate_migration_engine',
|
|
30
30
|
)
|
|
31
|
+
# Infisical is opt-in: use_infisical defaults to False and nothing in the free engine passes True, so the generator behind this
|
|
32
|
+
# (tilt/generators/infisical/secrets_generator.star) is not on the default path. It is an older contract with fixed secret names and
|
|
33
|
+
# ports. Do not wire `secrets.required` through it; the default provider is platform/docker/secrets/env_secrets.star.
|
|
31
34
|
load('./infisical/infisical_docker.star', 'InfisicalDocker')
|
|
32
35
|
|
|
33
36
|
# Load project name for dynamic golden image naming
|
|
@@ -29,6 +29,9 @@ load('./prisma/prisma_runtime.star',
|
|
|
29
29
|
|
|
30
30
|
load('./bun_helpers.star', 'bun_hoisted_packages_symlink_fix')
|
|
31
31
|
load('../config/healthcheck.star', 'DOCKER_HEALTHCHECK', 'dockerfile_healthcheck_flags')
|
|
32
|
+
# Infisical is opt-in: use_infisical defaults to False and nothing in the free engine passes True, so the generator behind this
|
|
33
|
+
# (tilt/generators/infisical/secrets_generator.star) is not on the default path. It is an older contract with fixed secret names and
|
|
34
|
+
# ports. Do not wire `secrets.required` through it; the default provider is platform/docker/secrets/env_secrets.star.
|
|
32
35
|
load('./infisical/infisical_docker.star', 'InfisicalDocker')
|
|
33
36
|
|
|
34
37
|
|
|
@@ -33,21 +33,6 @@ def generate_api_path(stack, app_name):
|
|
|
33
33
|
# Examples: /api/user-management, /api/order-management
|
|
34
34
|
return "/api/" + stack + "-management"
|
|
35
35
|
|
|
36
|
-
# =============================================================================
|
|
37
|
-
# SERVICE STACK TO API PATH MAPPING (Dynamic)
|
|
38
|
-
# =============================================================================
|
|
39
|
-
# This is populated at runtime from discovered manifests
|
|
40
|
-
# No hardcoded resource names
|
|
41
|
-
|
|
42
|
-
RESOURCE_STACK_TO_API_PATH = {}
|
|
43
|
-
|
|
44
|
-
# =============================================================================
|
|
45
|
-
# API PATH TO SERVICE MAPPING (Dynamic)
|
|
46
|
-
# =============================================================================
|
|
47
|
-
|
|
48
|
-
API_PATH_TO_RESOURCE_STACK = {}
|
|
49
|
-
API_PATH_TO_RESOURCE_DOMAIN = API_PATH_TO_RESOURCE_STACK
|
|
50
|
-
|
|
51
36
|
# =============================================================================
|
|
52
37
|
# HELPER FUNCTIONS
|
|
53
38
|
# =============================================================================
|
|
@@ -110,7 +95,7 @@ def get_api_path_for_stack(stack, manifest=None):
|
|
|
110
95
|
return manifest.get("apiPath")
|
|
111
96
|
|
|
112
97
|
# NEW: Use /api/{stack}-management pattern instead of /api/v1/{pluralized}
|
|
113
|
-
return
|
|
98
|
+
return "/api/" + stack + "-management"
|
|
114
99
|
|
|
115
100
|
# Backwards compatibility alias
|
|
116
101
|
get_api_path_for_domain = get_api_path_for_stack
|
|
@@ -176,20 +161,6 @@ def get_api_path_for_resource(resource_name):
|
|
|
176
161
|
# Backwards compatibility alias
|
|
177
162
|
get_api_path_for_service = get_api_path_for_resource
|
|
178
163
|
|
|
179
|
-
def get_stack_for_api_path(api_path):
|
|
180
|
-
"""Returns the service stack for an API path (reverse lookup).
|
|
181
|
-
|
|
182
|
-
Args:
|
|
183
|
-
api_path: Full API path
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
Service stack string
|
|
187
|
-
"""
|
|
188
|
-
return API_PATH_TO_RESOURCE_STACK.get(api_path, "")
|
|
189
|
-
|
|
190
|
-
# Backwards compatibility alias
|
|
191
|
-
get_domain_for_api_path = get_stack_for_api_path
|
|
192
|
-
|
|
193
164
|
# Load project name for dynamic localhost domain
|
|
194
165
|
# Use TDK_PROJECT_ROOT env var set by Tilt, fallback to current directory
|
|
195
166
|
def _load_project_localhost():
|
|
@@ -231,30 +202,3 @@ def build_health_endpoint(api_path, health_path="/health"):
|
|
|
231
202
|
Full health endpoint path
|
|
232
203
|
"""
|
|
233
204
|
return api_path + health_path
|
|
234
|
-
|
|
235
|
-
def get_all_api_paths():
|
|
236
|
-
"""Returns a list of all defined API paths from discovered services.
|
|
237
|
-
|
|
238
|
-
Returns:
|
|
239
|
-
List of API path strings
|
|
240
|
-
"""
|
|
241
|
-
return RESOURCE_DOMAIN_TO_API_PATH.values()
|
|
242
|
-
|
|
243
|
-
def is_valid_api_path(api_path):
|
|
244
|
-
"""Checks if an API path is valid/defined.
|
|
245
|
-
|
|
246
|
-
Args:
|
|
247
|
-
api_path: API path to check
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
True if valid, False otherwise
|
|
251
|
-
"""
|
|
252
|
-
return api_path in API_PATH_TO_RESOURCE_DOMAIN
|
|
253
|
-
|
|
254
|
-
# =============================================================================
|
|
255
|
-
# LEGACY COMPATIBILITY (Deprecated - for migration only)
|
|
256
|
-
# =============================================================================
|
|
257
|
-
# All legacy paths removed - use dynamic path generation from manifests
|
|
258
|
-
|
|
259
|
-
LEGACY_DOMAIN_TO_API_PATH = {}
|
|
260
|
-
RESOURCE_DOMAIN_TO_API_PATH = LEGACY_DOMAIN_TO_API_PATH
|