@flisk-sh/flisk 1.4.1 → 1.4.5

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +421 -92
  3. package/cli/dist/backend-languages/bun.js +5 -0
  4. package/cli/dist/backend-languages/go.js +3 -1
  5. package/cli/dist/cli.js +9 -0
  6. package/cli/dist/commands/completion.js +8 -1
  7. package/cli/dist/commands/config.js +130 -55
  8. package/cli/dist/commands/doctor.js +16 -15
  9. package/cli/dist/commands/logs.js +1 -0
  10. package/cli/dist/commands/project.js +1 -0
  11. package/cli/dist/commands/resource.js +39 -11
  12. package/cli/dist/commands/status.js +18 -9
  13. package/cli/dist/commands/up.js +102 -17
  14. package/cli/dist/frontend-frameworks/next.js +197 -0
  15. package/cli/dist/frontend-frameworks/react.js +81 -12
  16. package/cli/dist/frontend-frameworks/registry.js +5 -4
  17. package/cli/dist/frontend-frameworks/vue.js +82 -12
  18. package/cli/dist/generator/resource-templates.js +2 -4
  19. package/cli/dist/generator/template-engine.js +5 -3
  20. package/cli/dist/utils/cold-preflight.js +3 -2
  21. package/cli/dist/utils/config-explain.js +197 -0
  22. package/cli/dist/utils/demo-reset.js +22 -0
  23. package/cli/dist/utils/discovery-paths.js +7 -1
  24. package/cli/dist/utils/host-port-plan.js +8 -3
  25. package/cli/dist/utils/port-conflict-fix.js +16 -14
  26. package/cli/dist/utils/port-occupant.js +35 -0
  27. package/cli/dist/utils/project-features.js +7 -16
  28. package/cli/dist/utils/project-templates.js +8 -8
  29. package/cli/dist/utils/resource-features.js +5 -5
  30. package/cli/dist/utils/service-manifest.js +1 -0
  31. package/cli/dist/utils/service-urls.js +11 -0
  32. package/cli/dist/utils/startup-report.js +4 -3
  33. package/cli/dist/utils/tar.js +33 -28
  34. package/cli/dist/utils/tilt-process.js +4 -3
  35. package/cli/dist/utils/windows-doctor.js +18 -15
  36. package/cli/dist-bundle/{chunk-UJXR6MZ2.js → chunk-4J2FLFUA.js} +14 -7
  37. package/cli/dist-bundle/{chunk-SRN64OPO.js → chunk-BMHPPX2Q.js} +10 -10
  38. package/cli/dist-bundle/chunk-BP2M4OJ4.js +2 -0
  39. package/cli/dist-bundle/{chunk-TZNXL3IO.js → chunk-CGTWXWBU.js} +1 -1
  40. package/cli/dist-bundle/chunk-GPUJ55MI.js +3 -0
  41. package/cli/dist-bundle/{chunk-KJOCVAMQ.js → chunk-LUUJ4JB7.js} +1 -1
  42. package/cli/dist-bundle/chunk-PEZQEZVV.js +17 -0
  43. package/cli/dist-bundle/{chunk-7HR5PVOD.js → chunk-SFOFTVYM.js} +5 -5
  44. package/cli/dist-bundle/{chunk-RX6O3PG4.js → chunk-TIYTGN5R.js} +1 -1
  45. package/cli/dist-bundle/{chunk-BB5BZ37Y.js → chunk-U42XZWXB.js} +1 -1
  46. package/cli/dist-bundle/chunk-ULMQVD7J.js +122 -0
  47. package/cli/dist-bundle/chunk-V36PT2HF.js +24 -0
  48. package/cli/dist-bundle/{chunk-7IXBBU27.js → chunk-VMVTECCK.js} +1 -1
  49. package/cli/dist-bundle/{chunk-M2R464RM.js → chunk-Z5QEE4SW.js} +1 -1
  50. package/cli/dist-bundle/cli.js +607 -375
  51. package/cli/dist-bundle/cold-preflight-3BNDOLGS.js +2 -0
  52. package/cli/dist-bundle/{demo-stage-HIHO4VV3.js → demo-stage-G4W23BXW.js} +1 -1
  53. package/cli/dist-bundle/{extension-fetch-VEFSCG4B.js → extension-fetch-KIPYDIRM.js} +1 -1
  54. package/cli/dist-bundle/{open-W3QX35R6.js → open-HQFT6WDC.js} +1 -1
  55. package/cli/dist-bundle/project-JZO2CPZZ.js +2 -0
  56. package/cli/dist-bundle/{project-templates-EYSQG2EO.js → project-templates-IMJ3OX5I.js} +1 -1
  57. package/cli/dist-bundle/{services-RGTQ2LXC.js → services-GLFKZBBZ.js} +1 -1
  58. package/cli/dist-bundle/{tilt-port-owner-6DO7OJRR.js → tilt-port-owner-PJN4MPYE.js} +1 -1
  59. package/cli/package.json +1 -1
  60. package/package.json +2 -1
  61. package/runtime/engine/topologies/platform/docker/compose/compose.star +40 -4
  62. package/runtime/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +6 -0
  63. package/runtime/engine/topologies/platform/docker/dockerfile/next_dockerfile.star +83 -0
  64. package/runtime/engine/topologies/platform/docker/index.star +4 -0
  65. package/runtime/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +7 -0
  66. package/runtime/engine/topologies/platform/docker/networking/traefik.star +85 -46
  67. package/runtime/engine/topologies/tilt/common/utils_output.star +1 -0
  68. package/runtime/engine/topologies/tilt/generators/validators.star +1 -1
  69. package/runtime/engine/topologies/tilt/generators/vite/frontend.star +2 -2
  70. package/runtime/engine/topologies/tilt/generators/vite/generators.star +3 -0
  71. package/runtime/engine/topologies/tilt/manifest/constants.star +3 -0
  72. package/runtime/engine/topologies/tilt/manifest/parser.star +9 -3
  73. package/runtime/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +8 -3
  74. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +50 -24
  75. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/env.star +11 -2
  76. package/runtime/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +23 -9
  77. package/cli/dist-bundle/chunk-DUNCELUK.js +0 -24
  78. package/cli/dist-bundle/chunk-K6SOCO6V.js +0 -3
  79. package/cli/dist-bundle/chunk-L6U555CJ.js +0 -61
  80. package/cli/dist-bundle/chunk-VRESY4NR.js +0 -17
  81. package/cli/dist-bundle/chunk-YL56J2WX.js +0 -2
  82. package/cli/dist-bundle/cold-preflight-BBDDHWRV.js +0 -2
  83. package/cli/dist-bundle/project-FW5LL2KM.js +0 -2
@@ -196,6 +196,9 @@ DEFAULT_SYNCS = {
196
196
  'bring-your-own': [],
197
197
  }
198
198
 
199
+ # Next.js owns its entry files (no index.html) and rebuilds from package.json or next.config.*, which are not synced.
200
+ NEXT_FRONTEND_SYNCS = ['src', 'public']
201
+
199
202
  RUNTIME_CONFIGS = {
200
203
  'bun': {
201
204
  'image_base': 'oven/bun:1.3.11-alpine',
@@ -20,6 +20,7 @@ load("./constants.star",
20
20
  "MANIFEST_FILENAME",
21
21
  "VALID_APP_TYPES",
22
22
  "DEFAULT_SYNCS",
23
+ "NEXT_FRONTEND_SYNCS",
23
24
  "AUTOGENERATED_CONFIG_PATHS",
24
25
  "TRAEFIK_DEFAULTS",
25
26
  )
@@ -90,6 +91,7 @@ _GENERATED_STRING_RULES = [
90
91
  (['databaseName'], _ALPHANUM + '_-', ''),
91
92
  (['stack'], _ALPHANUM + '_-', ''),
92
93
  (['image'], _ALPHANUM + '._/:@-', ''),
94
+ (['basePath'], _PATH_CHARS, '/'),
93
95
  ]
94
96
 
95
97
  def generated_string_error(manifest, resource_path=""):
@@ -200,7 +202,8 @@ def normalize(manifest, resource_path=""):
200
202
  if normalized.get('syncs') == None or normalized.get('syncs') == []:
201
203
  normalized['syncs'] = _compute_syncs(
202
204
  normalized['appType'],
203
- normalized.get('featuresEnabled', [])
205
+ normalized.get('featuresEnabled', []),
206
+ normalized.get('framework')
204
207
  )
205
208
 
206
209
  # Compute backendName for frontends if missing
@@ -339,13 +342,14 @@ def _determine_app_type(manifest, resource_path):
339
342
  # Default to backend
340
343
  return "backend"
341
344
 
342
- def _compute_syncs(app_type, features):
345
+ def _compute_syncs(app_type, features, framework=None):
343
346
  """
344
347
  Compute default sync paths based on app type and features.
345
348
 
346
349
  Args:
347
350
  app_type: App type string
348
351
  features: List of feature strings
352
+ framework: Frontend framework id (Next.js has no index.html, so it syncs less than a Vite SPA)
349
353
 
350
354
  Returns:
351
355
  List of sync paths
@@ -353,7 +357,9 @@ def _compute_syncs(app_type, features):
353
357
  syncs = []
354
358
 
355
359
  # Start with default syncs for app type
356
- if app_type in DEFAULT_SYNCS:
360
+ if app_type == 'frontend' and framework == 'next':
361
+ syncs.extend(NEXT_FRONTEND_SYNCS)
362
+ elif app_type in DEFAULT_SYNCS:
357
363
  syncs.extend(DEFAULT_SYNCS[app_type])
358
364
 
359
365
  # Add feature-specific syncs
@@ -321,7 +321,7 @@ def _live_update_sync_source(project_root, full_res_path, sync_path):
321
321
  return relative_path
322
322
 
323
323
 
324
- def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', language='bun', go_live_reload=False):
324
+ def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', language='bun', go_live_reload=False, node_frontend=False):
325
325
  """Build live update sync and run rules for a resource.
326
326
 
327
327
  Python backends reload their own process after a sync (uvicorn --reload), so they have
@@ -363,7 +363,7 @@ def _build_live_update_rules(res_path, full_res_path, syncs, project_root='', la
363
363
  dest = '/app/' + full_res_path + '/' + sync_path
364
364
  live_update_rules.append(sync(full_sync_path, dest))
365
365
 
366
- if language == 'python' or language == 'node':
366
+ if language == 'python' or language == 'node' or node_frontend:
367
367
  return live_update_rules
368
368
 
369
369
  package_json_path = '/app/' + full_res_path + '/package.json'
@@ -407,6 +407,9 @@ def _wake_compose_invocation(project_root, resource_path, res_name, resource_nam
407
407
 
408
408
  def _image_target(config):
409
409
  """The Dockerfile target Tilt builds and the deferStart prebuild must match: `development` for Go with `dev.liveReload`."""
410
+ # Next.js runs `next dev` in the development image, so a saved file hot-reloads through the synced sources.
411
+ if Docker.is_next_frontend(config.get('manifest', {})):
412
+ return 'development'
410
413
  return 'development' if Docker.go_live_reload(config.get('manifest', {})) else 'production'
411
414
 
412
415
 
@@ -548,6 +551,8 @@ def _register_single_resource(config, resource_path, compose_project_name, auto_
548
551
  config['syncs'],
549
552
  project_root=config.get('project_root', ''),
550
553
  language=config.get('manifest', {}).get('language', 'bun'),
554
+ # A Next.js image is Node with no Bun install step: syncing sources is enough, `next dev` reloads itself.
555
+ node_frontend=Docker.is_next_frontend(config.get('manifest', {})),
551
556
  go_live_reload=Docker.go_live_reload(config.get('manifest', {})),
552
557
  )
553
558
  _register_docker_build(config, live_update_rules)
@@ -610,7 +615,7 @@ def _register_frontend_typecheck(res, service_dir, typecheck_cmd):
610
615
  # Fixed script, variable parts passed as argv ($1 = service dir, $2 = typecheck command), never spliced into the shell text.
611
616
  cmd=[
612
617
  'sh', '-c',
613
- 'cd "$1" && { bunx tsc --version >/dev/null 2>&1 || { echo "typecheck needs host dependencies: run bun install in $1"; exit 1; }; } && eval "$2"',
618
+ 'cd "$1" && { bunx tsc --version >/dev/null 2>&1 && test -f "$1/node_modules/vite/package.json" || { echo "typecheck needs host dependencies: run bun install in $1"; exit 1; }; } && eval "$2"',
614
619
  'sh', service_dir, typecheck_cmd,
615
620
  ],
616
621
  deps=[service_dir + '/src', service_dir + '/tsconfig.json'],
@@ -7,37 +7,63 @@
7
7
  # Auto-inject configuration from internal dependencies
8
8
  # =============================================================================
9
9
 
10
- load("../../../../../../../discovery/registry.star", "get_resource_by_name")
10
+ load("../../../../../../../discovery/registry.star", "get_app_resources", "get_resource_by_name")
11
+ load("../../../../../platform/docker/networking/traefik_helpers.star", "cli_api_path")
12
+
13
+ def _find_backend_resource(resource_name):
14
+ """Find a discovered resource by its own name, e.g. "api".
15
+
16
+ Registry services are stacks (`app`) whose resources are the runnable pieces (`api`, `web`), so the lookup
17
+ must search resources rather than service names. Frontends are skipped.
18
+ """
19
+ for service in get_app_resources():
20
+ for resource in service.get("resources", []):
21
+ if resource.get("name") != resource_name:
22
+ continue
23
+ app_type = resource.get("_manifest", {}).get("appType") or resource.get("appType")
24
+ if app_type == "frontend":
25
+ continue
26
+ return resource
27
+ return None
28
+
29
+
30
+ def _find_frontend_backend(manifest):
31
+ """Find the backend a frontend talks to: `backendName` first, then the first API `dependsOn` entry.
32
+
33
+ Returns:
34
+ tuple: (backend resource name, backend manifest), or (None, None) when the frontend has no discovered backend
35
+ """
36
+ backend_name = manifest.get("backendName")
37
+ if backend_name:
38
+ backend = _find_backend_resource(backend_name)
39
+ if backend:
40
+ return backend_name, backend.get("_manifest", {})
41
+
42
+ for dep_name in manifest.get("dependsOn", []):
43
+ dep = _find_backend_resource(dep_name)
44
+ if dep and dep.get("_manifest", {}).get("appType") in ["backend", "mcp", "agent"]:
45
+ return dep_name, dep.get("_manifest", {})
46
+
47
+ return None, None
48
+
11
49
 
12
50
  def resolve_backend_api_path(manifest):
13
- """Resolve API path from backend manifest if backendName is specified.
14
-
15
- Frontend manifests often reference a backend via backendName.
16
- This function looks up the backend's apiPath for consistency.
17
-
51
+ """Resolve the URL path a frontend uses to reach its backend, e.g. "/api/api".
52
+
53
+ The path is the backend's route on api.{project}.localhost: its explicit `apiPath` when set, otherwise
54
+ `/api/{name}` with a trailing `-api` stripped, the same rule `flisk up` prints and Traefik serves.
55
+ Frontends that have no discovered backend get None, so callers keep their stack-management fallback.
56
+
18
57
  Args:
19
58
  manifest: Frontend service manifest
20
-
59
+
21
60
  Returns:
22
- str: API path from backend manifest, or None if not found
61
+ str: API path of the frontend's backend, or None if there is no backend
23
62
  """
24
- backend_name = manifest.get("backendName")
25
- if not backend_name:
26
- return None
27
-
28
- # Try to find the backend service
29
- backend_service = get_resource_by_name(backend_name)
30
- if not backend_service:
63
+ backend_name, backend_manifest = _find_frontend_backend(manifest)
64
+ if backend_name == None:
31
65
  return None
32
-
33
- # Get the backend manifest
34
- backend_resources = backend_service.get("resources", [])
35
- for resource in backend_resources:
36
- if resource.get("name") == backend_name:
37
- backend_manifest = resource.get("_manifest", {})
38
- return backend_manifest.get("apiPath")
39
-
40
- return None
66
+ return cli_api_path(backend_name, backend_manifest)
41
67
 
42
68
 
43
69
  def resolve_dependency_api_urls(manifest):
@@ -16,6 +16,7 @@ OUTPUT = struct(AUTOGENERATED_FOLDER = ".autogenerated")
16
16
 
17
17
  load("../../../../platform/docker/auth/auth.star", "AuthConfig")
18
18
  load("../../../../platform/docker/networking/traefik_helpers.star", "get_api_path")
19
+ load("./dependencies/resolver.star", "resolve_backend_api_path")
19
20
  load("../../../../platform/docker/networking/api_path_constants.star", "get_frontend_base_path")
20
21
  load("../../../../platform/docker/constants.star", "PlatformDockerConstants")
21
22
 
@@ -154,9 +155,11 @@ def _generate_params_env(manifest, backend_manifest=None, write_fn=None):
154
155
  backend_port = backend_manifest.get('port', BASE_PORT_BACKEND) if backend_manifest else BASE_PORT_BACKEND
155
156
  backend_name = manifest.get('backendName', stack + '-backend')
156
157
  target_filename = '/' + OUTPUT.AUTOGENERATED_FOLDER + '/.env.frontend.autogenerated'
158
+ # The browser calls the frontend's own backend; the stack-management route only serves flisk's own API.
159
+ frontend_api_url = 'http://' + PlatformDockerConstants.API_LOCAL_DOMAIN + (resolve_backend_api_path(manifest) or api_path)
157
160
 
158
161
  # Shared with the Traefik route and the Vite `base` -- see get_frontend_base_path.
159
- base_path = get_frontend_base_path(manifest)
162
+ base_path = get_frontend_base_path(manifest, manifest.get('appName'))
160
163
  public_url = 'http://' + PlatformDockerConstants.LOCAL_DOMAIN + base_path
161
164
 
162
165
  content = """# Generated by Tilt SDK - DO NOT EDIT
@@ -167,10 +170,16 @@ VITE_BACKEND_NAME={backend_name}
167
170
  PUBLIC_URL={public_url}
168
171
  """.format(
169
172
  backend_port=backend_port,
170
- api_url=api_url,
173
+ api_url=frontend_api_url,
171
174
  backend_name=backend_name,
172
175
  public_url=public_url,
173
176
  )
177
+ if manifest.get('framework') == 'next':
178
+ # Next.js reads NEXT_PUBLIC_* (inlined into the browser bundle) and next.config.mjs reads FLISK_BASE_PATH.
179
+ # The generated Dockerfile copies this file to .env, which `next dev` and `next build` both load.
180
+ content += """NEXT_PUBLIC_API_URL={api_url}
181
+ FLISK_BASE_PATH={base_path}
182
+ """.format(api_url=frontend_api_url, base_path=base_path)
174
183
  content += _build_dependency_env_lines(manifest)
175
184
  else:
176
185
  db_name = manifest.get('databaseName', PlatformDockerConstants.get_db_name(stack))
@@ -132,9 +132,12 @@ def _build_generated_file_list(resource_config, resource_path, manifest):
132
132
  if app_type == 'bring-your-own':
133
133
  return []
134
134
  is_frontend = resource_config.get('frontend', False) or app_type == 'frontend'
135
+ is_next = is_frontend and manifest.get('framework') == 'next'
135
136
 
136
137
  # Vite
137
- if is_frontend:
138
+ if is_next:
139
+ pass
140
+ elif is_frontend:
138
141
  files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['vite_frontend'])
139
142
  files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['vite_build'])
140
143
  elif app_type == 'backend':
@@ -143,7 +146,7 @@ def _build_generated_file_list(resource_config, resource_path, manifest):
143
146
  files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['vite_sdk'])
144
147
 
145
148
  # Nginx (frontends)
146
- if is_frontend:
149
+ if is_frontend and not is_next:
147
150
  files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['nginx'])
148
151
 
149
152
  # Env
@@ -159,9 +162,10 @@ def _build_generated_file_list(resource_config, resource_path, manifest):
159
162
  dockerfile_path = resource_path + "/" + dockerfile_rel_path
160
163
  files.append(dockerfile_path + '.dockerignore')
161
164
 
162
- # TS config(s)
163
- files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_docker'])
164
- files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_local'])
165
+ # TS config(s): Next.js uses the app's own tsconfig.json
166
+ if not is_next:
167
+ files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_docker'])
168
+ files.append(resource_path + '/' + OUTPUT.AUTOGENERATED_FOLDER + '/' + GENERATED_CONFIG_FILENAMES['tsconfig_local'])
165
169
 
166
170
  # Prisma config (non-frontends)
167
171
  if not is_frontend:
@@ -313,6 +317,8 @@ def _generate_all_configs_for_resource(
313
317
 
314
318
  app_type = manifest.get('appType', 'backend')
315
319
  is_frontend = resource_config.get('frontend', False) or app_type == 'frontend'
320
+ # Next.js is a server framework: it owns its build and dev server, so the SPA outputs (Vite, nginx, api-client, tsconfig) are skipped.
321
+ is_next = is_frontend and manifest.get('framework') == 'next'
316
322
  # Provider-owned backends (Python, Go) own their runtime files (package/pyproject, Dockerfile,
317
323
  # reload command). Bun-only configs below (Vite, tsconfig, Prisma, bunfig) are skipped.
318
324
  provider_owned = Docker.is_provider_owned_language(manifest)
@@ -342,7 +348,7 @@ def _generate_all_configs_for_resource(
342
348
  # ==========================================================================
343
349
  # 2. NGINX CONFIG (for frontends)
344
350
  # ==========================================================================
345
- if is_frontend:
351
+ if is_frontend and not is_next:
346
352
  # Generate referer map using ApiGateway and pass to Nginx
347
353
  referer_map_block = ApiGateway.build_referer_map_block(manifest)
348
354
  Nginx.frontend(manifest, write_file, referer_map_block)
@@ -355,7 +361,7 @@ def _generate_all_configs_for_resource(
355
361
  # ==========================================================================
356
362
  # 3.5 FRONTEND API CLIENT FILES (for frontends only)
357
363
  # ==========================================================================
358
- if is_frontend:
364
+ if is_frontend and not is_next:
359
365
  FrontendGenerators.generate_all(manifest, write_file)
360
366
 
361
367
  # ==========================================================================
@@ -368,7 +374,13 @@ def _generate_all_configs_for_resource(
368
374
  # ==========================================================================
369
375
  # 5. DOCKERFILE
370
376
  # ==========================================================================
371
- if is_frontend:
377
+ if is_next:
378
+ dockerfile_content = Docker.next_frontend(
379
+ res_path=resource_path,
380
+ port=resource_config.get('port', manifest.get('port', BASE_PORT_FRONTEND)),
381
+ use_golden=should_enable('golden-image'),
382
+ )
383
+ elif is_frontend:
372
384
  target_path = resource_config.get('target_path', '/usr/share/nginx/html')
373
385
  use_nginx = 'nginx' in target_path or target_path == '/usr/share/nginx/html'
374
386
  port = resource_config.get('port', manifest.get('port', BASE_PORT_FRONTEND))
@@ -425,7 +437,9 @@ def _generate_all_configs_for_resource(
425
437
  # ==========================================================================
426
438
  internal_deps_map = Libs.get_deps_mapping(ctx, resource_path)
427
439
 
428
- if is_frontend:
440
+ if is_next:
441
+ pass
442
+ elif is_frontend:
429
443
  # CRITICAL FIX (v2.0): For Docker builds, we MUST NOT pass internal_deps
430
444
  # This prevents "Module not found" errors when path aliases point to
431
445
  # source files outside the Docker context.
@@ -1,24 +0,0 @@
1
- import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
- import{E as x,a as h,ga as j,ha as D,t as _}from"./chunk-VRESY4NR.js";import{C as L,j as N,m as w,n as A,t as F,x as T}from"./chunk-BB5BZ37Y.js";import{execFileSync as B}from"node:child_process";import{existsSync as de,mkdirSync as le,readFileSync as K,realpathSync as fe,writeFileSync as pe}from"node:fs";import{join as b,resolve as he,sep as me}from"node:path";import{createServer as ie}from"node:net";import{connect as oe}from"node:net";function U(e,t=500){return new Promise(r=>{let s=oe({port:e,host:"127.0.0.1"}),n=o=>{s.destroy(),r(o)};s.setTimeout(t,()=>n(!1)),s.once("connect",()=>n(!0)),s.once("error",()=>n(!1))})}var ae={ingressHttp:{start:8080,end:8180},ingressHttps:{start:8443,end:8543},postgres:{start:15432,end:15532}},v={ingressHttp:"FLISK_HTTP_PORT",ingressHttps:"FLISK_HTTPS_PORT",postgres:"FLISK_POSTGRES_PORT"},H={ingressHttp:80,ingressHttps:443,postgres:5432},E={ingressHttp:H.ingressHttp,ingressHttps:H.ingressHttps};function ce(e,t){return e==="EACCES"&&t==="darwin"}async function O(e){return await U(e)?!1:new Promise(t=>{let r=ie();r.once("error",s=>{t(ce(s.code,process.platform))}),r.listen(e,"127.0.0.1",()=>r.close(()=>t(!0)))})}function ue(e,t){let r=Number(t);if(!Number.isInteger(r)||r<1||r>65535)throw new Error(`${e} must be an integer between 1 and 65535 (received ${JSON.stringify(t)})`);return r}async function R(e={}){let t=e.env??process.env,r=e.isAvailable??O,s={ingressHttp:!1,ingressHttps:!1,postgres:!1},n={ingressHttp:"",ingressHttps:"",postgres:""},o={explicit:s},a=new Set;for(let c of["ingressHttp","ingressHttps","postgres"]){let u=t[v[c]],i=u!==void 0&&u.trim()!=="",d=e.ranges?.[c]??ae[c],l=E[c],f=i?[ue(v[c],u)]:[...l===void 0?[]:[l],...Array.from({length:d.end-d.start+1},(g,k)=>d.start+k)],m;for(let g of f)if(!a.has(g)){if(await r(g)){m=g;break}if(i)throw new Error(`${v[c]}=${g} is already in use. Choose a free host port and retry.`)}if(m===void 0)throw new Error(`No available host port in ${d.start}-${d.end} for ${c}. Set ${v[c]} to a free port and retry.`);a.add(m),o[c]=m,s[c]=i,n[c]=i?"explicit override":m===l?"requested port is free":`selected from fallback range ${d.start}-${d.end}`}return{...o,requested:{...H},reason:n}}function Ve(e){return`Host ports: HTTP ${e.ingressHttp}, HTTPS ${e.ingressHttps}, Postgres ${e.postgres}`}var G=b(".flisk",".flisk-out","host-ports.json");function ge(e,t,r){return e.split(`
3
- `).some(s=>{let[n="",o=""]=s.split("|"),a=n.toLowerCase(),c=t.toLowerCase();return(a.startsWith(`${c}_`)||a.startsWith(`${c}-`))&&o.includes(`:${r}->`)})}function V(e){let t=b(e,G);if(!de(t))return null;try{let r=JSON.parse(K(t,"utf-8"));if(!r||typeof r!="object")return null;let s=r;return!Number.isInteger(s.ingressHttp)||!Number.isInteger(s.ingressHttps)||!Number.isInteger(s.postgres)||!s.requested||!s.explicit||!s.reason?null:s}catch{return null}}function Ye(e,t){let r=b(e,G);le(b(e,".flisk",".flisk-out"),{recursive:!0}),pe(r,`${JSON.stringify(t,null,2)}
4
- `,{encoding:"utf-8",mode:384})}function Qe(e){process.env.FLISK_HTTP_PORT=String(e.ingressHttp),process.env.FLISK_HTTPS_PORT=String(e.ingressHttps),process.env.FLISK_POSTGRES_PORT=String(e.postgres)}var ke="com.docker.compose.project.working_dir";function Pe(e,t,r){let s=new Set([he(t)]);try{s.add(fe(t))}catch{}return e.split(`
5
- `).some(n=>{let[,o="",a=""]=n.split("|");return!a||!o.includes(`:${r}->`)?!1:[...s].some(c=>a===c||a.startsWith(`${c}${me}`))})}function Xe(e,t,r=()=>B("docker",["ps","--format",`{{.Names}}|{{.Ports}}|{{.Label "${ke}"}}`],{encoding:"utf-8",timeout:5e3})){let s=V(e);if(!s||s.ingressHttp===t)return t;try{if(Pe(r(),e,s.ingressHttp))return s.ingressHttp}catch{}return t}async function et(e,t={}){let r=V(e),s=["FLISK_HTTP_PORT","FLISK_HTTPS_PORT","FLISK_POSTGRES_PORT"].some(i=>process.env[i]);if(t.inspectDocker===!1&&r&&!s)return r;let n="";try{let i=JSON.parse(K(b(e,".flisk",w),"utf-8"));n=String(i?.project?.name??"flisk-project").replace(/-/g,"_").toLowerCase()}catch{n="flisk_project"}let o="";if(t.inspectDocker!==!1)try{o=B("docker",["ps","--format","{{.Names}}|{{.Ports}}"],{encoding:"utf-8",timeout:5e3})}catch{}let a=i=>ge(o,n,i),c=t.isPortFree??O,u=async i=>await c(i)||a(i);if(r&&!s){for(let i of[r.ingressHttp,r.ingressHttps,r.postgres])if(!await u(i))return R({isAvailable:u});for(let[i,d]of Object.entries(E)){let l=r[i];if(d!==void 0&&l!==d&&await u(d))return R({isAvailable:u})}return r}return R({isAvailable:u})}import{execFileSync as Q,execSync as S}from"node:child_process";import{existsSync as I,readdirSync as Se,readFileSync as C}from"node:fs";import{createServer as Te}from"node:net";import{join as P}from"node:path";function W(e,t){return e==="pending"||e==="in_progress"||e==="none"||t==="pending"||t==="none"}var J="container is running, but its health check is failing";function M(e){let t=e?.runtimeStatus??"";if(t!=="ok")return t;let r=e?.composeResourceInfo?.healthStatus;return r==="unhealthy"?"error":r==="starting"?"pending":t}var y=1e4,z=3e3,be="http://localhost:4873";function X(e){return`'${e.replace(/'/g,"'\\''")}'`}function $(e,t,r,s,n){return process.platform==="win32"&&e===S?Q(L(t)??t,r,{...n,windowsHide:!0}):e(s,n)}var ye=[80,443];function $e(e){return e.replace(/-/g,"_")}function we(e,t){let r=new Set(t),s=[];for(let n of e.split(`
6
- `)){let o=n.trim();if(!o)continue;let a=o.indexOf(" "),c=a>=0?o.slice(0,a):o,u=a>=0?o.slice(a+1):"",i=[];for(let d of r)u.split(/[\s,]+/).some(f=>f.startsWith(`${d}->`)||f.includes(`:${d}->`))&&i.push(d);i.length>0&&s.push({name:c,ports:u,publishedPorts:i})}return s}function ee(e,t){let r=e.toLowerCase(),s=t.toLowerCase();return r===`${s}_traefik`||r.startsWith(`${s}_traefik`)}function ve(e,t){return e.filter(r=>!ee(r.name,t))}function Re(e){try{let t=e??D();return new Set(t.filter(r=>r.config?.sablier?.enable&&r.config?.sablier?.deferStart).map(r=>r.name))}catch{return new Set}}function _e(e,t=new Set){let s=JSON.parse(e).items??[],n=[],o=0,a=0,c=0;for(let u of s){let i=u.metadata?.name??"unknown",d=u.status?.updateStatus??"",l=M(u.status),f=l==="error"&&u.status?.runtimeStatus==="ok",g=(u.status?.buildHistory?.[0]?.error??"").trim()||(f?J:"");if(d==="error"||l==="error"){n.push({name:i,updateStatus:d,runtimeStatus:l,error:g.replace(/\s+/g," ").slice(0,800)});continue}if(d==="ok"&&(l==="ok"||l==="not_applicable")){c+=1;continue}i!=="(Tiltfile)"&&W(d,l)&&(t.has(i)?a+=1:o+=1)}return{failures:n,pendingCount:o,deferredCount:a,okCount:c,total:s.length}}var xe=/failed to resolve source metadata for docker\.io\/library\/([A-Za-z0-9._-]+-l[1-4](?:-[a-z]+)*)(?::[^\s:]+)?:/i;function te(e){let t=e.replace(/\s+/g," ").trim(),r=t.match(/Bind for [^ ]+:(\d+) failed: port is already allocated/i);if(r)return`host port ${r[1]} is already allocated`;if(/port is already allocated/i.test(t))return"a required host port is already allocated";let s=[...t.matchAll(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/gi)].map(c=>c[1]);if(s.length>0){let c=[...new Set(s)],u=c.slice(0,4).join(", "),i=c.length>4?` (+${c.length-4} more)`:"";return`private registry ConnectionRefused for ${u}${i} \u2014 Verdaccio (:4873) is down or unreachable from the build`}let n=t.match(xe);if(n)return`base image ${n[1]} was missing when this build started (the golden layers weren't built yet, or golden-layers-build failed) - Tilt does not retry a failed build by itself`;if(/install-deps\.sh/i.test(t)||/bun install(?: --production)?/i.test(t)||/npm (?:ci|install)/i.test(t))return"dependency install failed during ImageBuild \u2014 usually Verdaccio (:4873) unreachable or missing @scoped packages (ConnectionRefused downloading package manifest)";let o=t.match(/No such image:\s*([^\s]+)/i)?.[1]??t.match(/pull access denied for ([^,:\s]+)/i)?.[1]??void 0,a=t.match(/\bup\s+-d\s+--no-build\s+([A-Za-z0-9._-]+)/i)?.[1];if(o||a||/docker compose .*\bup\s+-d\s+--no-build\b/i.test(t)){let c=o??`${a??"service"}:dev`,u=a?` for ${a}`:"";return`compose run failed because image is missing (${c})${u} \u2014 wait for the ImageBuild resource to finish, then re-trigger the *-run-only resource`}return/bun run build/i.test(t)?"ImageBuild failed during `bun run build` (compile/typecheck) \u2014 open the resource logs in Tilt for the TypeScript/build error":(/ImageBuild:/i.test(t)||/^Command "/i.test(t),t.slice(0,220))}function q(e){return/ConnectionRefused downloading package manifest|Verdaccio|private registry|dependency install failed during ImageBuild|install-deps\.sh|bun install/i.test(e)}function He(e=T()??process.cwd()){let t=P(e,".flisk",w);if(!I(t))return!1;try{let r=JSON.parse(C(t,"utf-8"));return r.optional_infra?.verdaccio===!0||Object.values(r.phases??{}).some(s=>(s.enabledStacks??[]).includes("verdaccio"))}catch{return!1}}function re(e){for(let t of[P(e,".npmrc"),P(e,A)])if(I(t))try{let r=C(t,"utf-8");if(/:4873\b/.test(r)||/verdaccio/i.test(r))return!0}catch{}return!1}function Ee(e=T()??process.cwd()){return He(e)||re(e)}function mt(e=S,t=T()??process.cwd(),r=process.env.VERDACCIO_URL??process.env.NPM_REGISTRY_URL??be,s=!0){if(!s&&!re(t))return{name:"Private npm registry",didPass:!0,isSkipped:!0,message:"Verdaccio is enabled in the project config but needs a Premium license, so Flisk will not start it - skipped"};if(!Ee(t))return{name:"Private npm registry",didPass:!0,isSkipped:!0,message:"Private npm registry (Verdaccio) not enabled for this project - skipped"};let n=!1;try{let c=$(e,"docker",["ps","--format","{{.Names}}"],"docker ps --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:y});n=/verdaccio/i.test(c)}catch{}let o=!1,a="";try{let c=process.platform==="win32"?Q("curl.exe",["-fsS","-o","NUL","-w","%{http_code}","--max-time","2",r],{stdio:"pipe",encoding:"utf-8",timeout:z+1e3,windowsHide:!0}).trim():e(`curl -fsS -o /dev/null -w '%{http_code}' --max-time 2 ${X(r)}`,{stdio:"pipe",encoding:"utf-8",timeout:z+1e3}).trim();o=/^[23]\d\d$/.test(c),a=`HTTP ${c}`}catch(c){a=c instanceof Error?c.message.slice(0,120):"request failed"}return o?{name:"Private npm registry",didPass:!0,message:`Private npm registry reachable at ${r}${n?" (verdaccio container running)":""}`}:n?{name:"Private npm registry",didPass:!1,message:`Verdaccio container is running but ${r} is not responding (${a}). ImageBuilds that install @scoped packages will fail with ConnectionRefused downloading package manifest.`,fix:"Check `docker logs` for the verdaccio container, confirm port 4873 is published, then re-run `flisk doctor`."}:{name:"Private npm registry",didPass:!1,message:`Private npm registry (Verdaccio) is not reachable at ${r}. Docker ImageBuilds will fail with ConnectionRefused downloading package manifests for @flisk-sh/* and project-scoped packages.`,fix:"Start the registry with `bun run verdaccio:start` (or `docker compose -f docker-compose.verdaccio.yml up -d`), publish packages with `bun run publish:all` / `scripts/build/publish-to-verdaccio.sh`, then `tilt trigger` failed resources or re-run `flisk up`."}}function gt(e=S,t=x(),r=ye){let s=$e(t),n="";try{n=$(e,"docker",["ps","--format","{{.Names}}\\t{{.Ports}}"],"docker ps --format '{{.Names}}\\t{{.Ports}}'",{stdio:"pipe",encoding:"utf-8",timeout:y})}catch{return{name:"Ingress Ports",didPass:!0,isSkipped:!0,message:"Docker not available - skipped ingress port check"}}let o=we(n,r),a=ve(o,s);if(a.length===0){let u=o.filter(i=>ee(i.name,s));return u.length>0?{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${r.join("/")} held by this project's Traefik (${u.map(i=>i.name).join(", ")})`}:{name:"Ingress Ports",didPass:!0,message:`Ingress ports ${r.join("/")} are free for Traefik`}}return{name:"Ingress Ports",didPass:!1,message:`Traefik cannot bind ingress ports because another container already owns them:
7
- ${a.map(u=>{let i=u.publishedPorts.join(", ");return`${u.name} (host ports ${i})`}).join(`
8
- `)}`,fix:`Stop the foreign container(s), e.g. \`docker stop ${a[0]?.name}\`, or shut down the other Flisk/Tilt project using that Traefik. Then re-run \`flisk up\`.`}}function Z(e,t){return new Promise(r=>{let s=Te();s.once("error",n=>r(n.code==="EADDRINUSE"?"in-use":"unknown")),s.once("listening",()=>s.close(()=>r("free"))),s.listen(e,t)})}async function kt(e){for(let t=0;t<2;t++){let r=await Z(e,"0.0.0.0");if(r==="unknown")return r;if(r==="free"){let s=await Z(e,"127.0.0.1");if(s==="free"||s==="unknown")return s}t===0&&await new Promise(s=>setTimeout(s,50))}return"in-use"}function Oe(e){let{status:t}=e;return t===void 0?e.error??"no response":t===404?"HTTP 404: Traefik is answering but has no route for this URL (container not started yet, or not running)":t===502||t===503||t===504?`HTTP ${t}: Traefik found the route but the container isn't answering (still starting, or unhealthy)`:`HTTP ${t}`}function Pt(e){let t=e.filter(o=>o.status!==void 0),r=e.filter(o=>!o.ok);if(t.length===0)return{name:"Service Health",didPass:!0,isSkipped:!0,message:`No services responded - skipped ping of ${h(e.length,"service")} (stack may be down, or Traefik never bound :80)`,fix:"If `flisk up` is already running, check Traefik/port 80 in the Tilt UI. Otherwise start with: flisk up"};if(r.length===0)return{name:"Service Health",didPass:!0,message:`All ${h(e.length,"service")} responding on /health`};let s=r.map(o=>`${o.name} (${Oe(o)})
9
- ${o.url}`).join(`
10
- `),n=r.some(o=>o.status===404);return{name:"Service Health",didPass:!1,message:`${h(r.length,"service")} not healthy (${e.length-r.length}/${e.length} healthy):
11
- ${s}`,fix:n?"If `flisk up` just started, images may still be building: re-run `flisk doctor` in a minute. Otherwise check `docker ps` and the resource in the Tilt UI, and compare `flisk networks` with the URL above.":"Check container state and routing: docker ps, then flisk networks to compare the advertised URLs against Traefik's routers"}}function St(e){if(e.length===0)return{name:"Service Smoke",didPass:!0,isSkipped:!0,message:"No read-only smoke checks declared"};let t=e.filter(s=>!s.ok);if(t.length===0)return{name:"Service Smoke",didPass:!0,message:`All ${h(e.length,"service")} pass their smoke checks`};let r=t.map(s=>s.failure??`${s.name}: failed`).join(`
12
- `);return{name:"Service Smoke",didPass:!1,message:`${h(t.length,"service")} failing a smoke check (${e.length-t.length}/${e.length} passing):
13
- ${r}`,fix:'Add the route the smoke step expects, or correct its "path" in the service.json "smoke" block. Then re-run flisk doctor.'}}function Tt(e){let t=e.filter(n=>n.status!==void 0);if(t.length===0)return{name:"Advertised Endpoints",didPass:!0,isSkipped:!0,message:"No services responded - skipped advertised endpoint check"};let r=t.filter(n=>n.status===404);if(r.length===0)return{name:"Advertised Endpoints",didPass:!0,message:`${h(t.length,"advertised endpoint")} reachable`};let s=r.map(n=>`${n.name} (404)
14
- ${n.url}`).join(`
15
- `);return{name:"Advertised Endpoints",didPass:!0,isWarning:!0,message:`${h(r.length,"advertised link")} ${r.length===1?"returns":"return"} 404 (the service may be healthy, but nothing answers at this exact path):
16
- ${s}`,fix:'Nothing handles this exact path, so opening the link shows a 404 even when /health is fine. If this API has no root route, you can leave it. Otherwise add a GET route for it (for example one that returns the service name), or declare a read-only "smoke" step on a real route in service.json. The URL flisk networks and the Flisk App print stays the same until the CLI changes how it reports it.'}}function bt(e=S){let t="";try{t=$(e,"tilt",["get","uiresources","-o","json"],"tilt get uiresources -o json",{stdio:"pipe",encoding:"utf-8",timeout:y})}catch{return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"Tilt is not running - skipped resource status check",fix:"Start them with: flisk up"}}let r;try{r=_e(t,Re())}catch{return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"Could not parse Tilt resource status - skipped"}}if(r.total===0)return{name:"Tilt Resources",didPass:!0,isSkipped:!0,message:"No Tilt UIResources found"};if(r.failures.length===0){let p=r.deferredCount>0?`, ${r.deferredCount} deferred`:"";return{name:"Tilt Resources",didPass:!0,message:`Tilt resources healthy (${r.okCount} ok, ${r.pendingCount} pending${p})`}}let s=Ce(r.failures),o=s.slice(0,20),a=s.length-o.length,c=o.map(p=>({failure:p,why:Ae(p,e)})),u=c.map(({failure:p,why:ne})=>`${p.name}: ${ne}`).join(`
17
- `),i=a>0?`
18
- \u2026and ${h(a,"more failed resource")}`:"",d=r.pendingCount>0?`
19
- ${h(r.pendingCount,"resource")} still pending/not started \u2014 often blocked by the failures above.`:"",l=r.deferredCount>0?`
20
- ${h(r.deferredCount,"resource")} intentionally deferred (sablier.deferStart) \u2014 not started until first request, unrelated to the failures above.`:"",f=s.some(p=>/port is already allocated/i.test(p.error)),m=s.some(p=>q(p.error)||q(te(p.error))),g=c.some(({why:p})=>/Kafka broker unreachable|kafka:9092/i.test(p)),k="Open the Tilt UI URL printed by flisk up (default port "+F.tiltUi+" when TILT_PORT is unset), inspect the red resources, fix the listed errors, then `tilt trigger <resource>` or re-run `flisk up`.";return f?k="Free the conflicting host port (usually 80/443): `docker ps --filter publish=80` then `docker stop <other-traefik>`. Re-trigger Traefik in the Tilt UI or run `flisk up` again.":m?k="Start Verdaccio (`bun run verdaccio:start`), ensure packages are published (`bun run publish:all` or `bash scripts/build/publish-to-verdaccio.sh`), confirm `curl http://localhost:4873` works, then `tilt trigger` the failed resources.":g&&(k="Kafka is not running (optional infra). Start it, point the bridge at a real broker, or disable Kafka-dependent resources until Kafka is enabled."),{name:"Tilt Resources",didPass:!1,message:`Tilt reports ${h(r.failures.length,"failed resource")}:
21
- ${u}${i}${d}${l}`,fix:k}}var Ie=/^(traefik|postgres|nats|infisical|verdaccio|proxy)([.-]|$)/i;function Y(e){return Ie.test(e)}function Ce(e){return[...e].sort((t,r)=>{let s=Y(t.name)?0:1,n=Y(r.name)?0:1;return s!==n?s-n:t.name.localeCompare(r.name)})}function Ne(e,t=S,r=x()){try{let s=$(t,"docker",["ps","-a","--format","{{.Names}}"],"docker ps -a --format '{{.Names}}'",{stdio:"pipe",encoding:"utf-8",timeout:y}).split(`
22
- `).map(i=>i.trim()).filter(Boolean),n=`-autogenerated-${e}-1`,o=s.find(i=>i===e)??s.find(i=>i.startsWith(`${r}-`)&&i.endsWith(n))??s.find(i=>i.endsWith(n))??s.find(i=>i.includes(e));if(!o)return null;let a=$(t,"docker",["logs","--tail","120",o],`docker logs --tail 120 ${X(o)} 2>&1`,{stdio:"pipe",encoding:"utf-8",timeout:y});if(/getaddrinfo ENOTFOUND/i.test(a)&&/kafka:9092/i.test(a))return"runtime crash: Kafka broker unreachable (getaddrinfo ENOTFOUND kafka:9092)";if(/ECONNREFUSED/i.test(a)&&/kafka/i.test(a))return"runtime crash: Kafka connection refused";let c=a.match(/ConnectionRefused downloading package manifest (@[A-Za-z0-9._/-]+)/i);if(c?.[1])return`runtime/build log: private registry ConnectionRefused for ${c[1]}`;if(/ZodError/i.test(a)){let i=[...a.matchAll(/"path"\s*:\s*\[\s*"([^"]+)"\s*\]/g)].map(l=>l[1]),d=[...new Set(i)].slice(0,6);return d.length>0?`runtime crash: ZodError missing required config/env: ${d.join(", ")}`:"runtime crash: ZodError during config/env validation"}let u=a.match(/error:\s+Failed to [^:\n]+[^\n]{0,120}/i)?.[0]??a.match(/Failed to (?:start|initialize) [^\n]{0,120}/i)?.[0]??a.match(/^\s*error:\s+[^\n]{0,160}/im)?.[0];if(u)return`runtime crash: ${u.replace(/\s+/g," ").trim().slice(0,180)}`}catch{return null}return null}function Ae(e,t=S){return e.error?te(e.error):e.runtimeStatus==="error"?Ne(e.name,t)??"runtime error (container crashed or unhealthy \u2014 check Tilt/docker logs)":e.updateStatus||e.runtimeStatus||"unknown error"}var Fe=/\.(ts|tsx|mts|js|mjs|cjs)$/,Le=new Set(["node_modules","dist",N,"tests","__tests__"]);function se(e){let t=[];for(let r of Se(e,{withFileTypes:!0}))r.isDirectory()?Le.has(r.name)||t.push(...se(P(e,r.name))):Fe.test(r.name)&&!/\.(test|spec)\./.test(r.name)&&t.push(P(e,r.name));return t}function je(e){let t=e?.healthCheckPath||"/health";return{container:t,traefik:e?.traefik?.healthCheck||t}}function De(e){let t=[];for(let r of e){if(!_(r.config?.appType))continue;let s=P(r.path,"src");if(!I(s))continue;let n=se(s);if(n.length===0)continue;let o=n.map(i=>C(i,"utf-8")),{container:a,traefik:c}=je(r.config),u=a===c?[{path:a,usedBy:"container and Traefik healthchecks"}]:[{path:a,usedBy:"container healthcheck"},{path:c,usedBy:"Traefik healthcheck"}];for(let{path:i,usedBy:d}of u){let l=["'",'"',"`"].map(f=>`${f}${i}${f}`);o.some(f=>l.some(m=>f.includes(m)))||t.push({name:r.name,healthPath:i,usedBy:d})}}return t}function yt(e=T()??process.cwd()){let t=[];try{t=j(e)}catch{return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"Could not read services - skipped health route check"}}let r=t.filter(o=>_(o.config?.appType));if(r.length===0)return{name:"Health Routes",didPass:!0,isSkipped:!0,message:"No backend services to check"};let s=De(r);return s.length===0?{name:"Health Routes",didPass:!0,message:`All ${h(r.length,"backend")} define their health route`}:{name:"Health Routes",didPass:!1,message:`Backends without a health route (the container never turns healthy, so Traefik returns 404):
23
- ${s.map(o=>`${o.name}: no "${o.healthPath}" route in src/ (probed by the ${o.usedBy})`).join(`
24
- `)}`,fix:`Add the route, e.g. for Hono: app.get("${s[0]?.healthPath}", (c) => c.json({ status: "ok" })). To use a different path, set "healthCheckPath" (container healthcheck) or "traefik.healthCheck" (Traefik) in its service.json.`}}export{R as a,Ve as b,V as c,Ye as d,Qe as e,Xe as f,et as g,W as h,M as i,$e as j,we as k,Re as l,_e as m,te as n,He as o,mt as p,gt as q,kt as r,Pt as s,St as t,Tt as u,bt as v,yt as w};
@@ -1,3 +0,0 @@
1
- import{createRequire as __fliskRequire}from'node:module';const require=__fliskRequire(import.meta.url);
2
- import{randomUUID as F}from"node:crypto";import{existsSync as p,mkdirSync as g,readFileSync as y,statSync as M,writeFileSync as h}from"node:fs";import{homedir as P}from"node:os";import{join as a}from"node:path";import{spawnSync as _}from"node:child_process";import{existsSync as N,mkdirSync as w,mkdtempSync as C,readdirSync as $,renameSync as L,rmSync as v,statSync as j,writeFileSync as O}from"node:fs";import{tmpdir as D}from"node:os";import{join as c}from"node:path";function S(e,n){let r=C(c(D(),"flisk-ext-")),o=c(r,"bundle.tar.gz");O(o,e),w(n,{recursive:!0});let t=process.platform==="win32"?"tar.exe":"tar",i=_(t,["xzf",o,"-C",n,"--strip-components=1"],{stdio:"pipe",windowsHide:!0});if(i.status!==0){let s=c(r,"fallback");w(s,{recursive:!0});let f=_(t,["xzf",o,"-C",s],{stdio:"pipe",windowsHide:!0});if(f.status!==0)throw v(r,{recursive:!0,force:!0}),new Error(`tar extraction failed: ${f.stderr?.toString()||i.stderr?.toString()||"unknown error"}`);let l=$(s),[u]=l,m=l.length===1&&u!==void 0&&N(c(s,u))&&j(c(s,u)).isDirectory()?c(s,u):s;for(let d of $(m))L(c(m,d),c(n,d))}v(r,{recursive:!0,force:!0})}var U="https://tdk-extension-dist.oranguman.workers.dev/v1/premium/bundle.tar.gz",A=["playwright","c4-diagram","logging","agents-md","sablier","verdaccio"],z=720*60*1e3,k={"typescript/playwright_config.star":"engine/topologies/tilt/generators/typescript/playwright_config.star","generators/agents_md.star":"engine/topologies/tilt/generators/agents_md.star","c4_diagram.star":"engine/topologies/tilt/generators/c4_diagram.star","observability/logging.star":"engine/topologies/platform/observability/logging.star","generators/database_provisioner.star":"engine/topologies/tilt/generators/database_provisioner.star","generators/dependency_manifest.star":"engine/topologies/tilt/generators/dependency_manifest.star","generators/fixed_frontend_tsconfig.star":"engine/topologies/tilt/generators/typescript/fixed_frontend_tsconfig.star","synthetic_monitor.star":"engine/resources/synthetic_monitor.star","synthetic-monitor-topology.star":"engine/topologies/platform/services/synthetic-monitor.star","database-management-service.yaml":"platform/services/platform/database-management/service.yaml","networking/sablier_container_cycle.star":"engine/topologies/platform/docker/networking/sablier_container_cycle.star","registries/verdaccio_loader.star":"engine/topologies/platform/registries/verdaccio_loader.star"};function I(){return process.env.FLISK_LICENSE_KEY?.trim()||null}function R(){return process.env.FLISK_PREMIUM_ENDPOINT?.trim()||U}function x(e){let n=a(e,".flisk",".project-id");if(p(n))return y(n,"utf-8").trim();let r=F();return g(a(e,".flisk"),{recursive:!0}),h(n,`${r}
3
- `),r}function B(e){let n=e.replace(/[^a-zA-Z0-9_-]/g,"_");return a(P(),".flisk","cache",`premium-${n}`,"premium.tar.gz")}function T(e){return p(e)?Date.now()-M(e).mtimeMs<z:!1}async function E(e,n,r){let o=`${R()}?resource=${encodeURIComponent(n)}&projectId=${encodeURIComponent(r)}`,t=await fetch(o,{headers:{Authorization:`Bearer ${e}`}});return t.status!==401?t:(await t.arrayBuffer().catch(()=>{}),fetch(`${o}&key=${encodeURIComponent(e)}`))}async function H(e,n){let r=[];for(let o of A){let t;try{t=await E(e,o,n)}catch(i){return console.warn(`\u26A0\uFE0F Premium fetch failed (network error): ${i.message}`),null}if(t.ok){let i=Buffer.from(await t.arrayBuffer());return console.info(`\u{1F50E} Premium entitlement probe: granted resource=${o} (HTTP ${t.status}); prior denials=${r.join(", ")||"none"}`),console.info(`\u{1F50E} Premium bundle download: ${i.byteLength} bytes`),i}if(t.status===401||t.status===403){r.push(`${o}=${t.status}`);continue}return console.warn(`\u26A0\uFE0F Premium fetch failed: ${o} returned HTTP ${t.status}; prior entitlement probes: ${r.join(", ")||"none"}`),null}return console.warn(`\u26A0\uFE0F Premium entitlement probes denied: ${r.join(", ")}`),null}async function re(e,n){let r=I();if(!r)return!1;let o=B(r);if(T(o))console.info("\u{1F50E} Premium bundle cache: hit (cached bundle reused)");else{console.info("\u{1F50E} Premium bundle cache: miss; requesting bundle from distribution service");let t=x(e),i=await H(r,t);if(!i)return console.warn("\u26A0\uFE0F No premium resources unlocked for this key - using free tier only."),!1;g(a(o,".."),{recursive:!0}),h(o,i)}try{let t=a(o,"..","extracted");g(t,{recursive:!0}),S(y(o),t);let i=0,s=[];for(let[l,u]of Object.entries(k)){let m=a(t,l);if(!p(m)){s.push(l);continue}let d=a(n,u);g(a(d,".."),{recursive:!0}),h(d,y(m)),i++}let f=a(t,"networking/sablier_container_cycle.star");if(console.info(`\u{1F50E} Premium bundle contents: mapped=${i}/${Object.keys(k).length}; sablier_overlay=${p(f)?"present":"MISSING"}`),s.length>0&&console.info(`\u{1F50E} Premium bundle missing mapped files: ${s.join(", ")}`),!p(f)&&process.env.FLISK_PREMIUM_DIAGNOSTICS==="1"){let l=await G(e);console.info(`\u{1F50E} Sablier entitlement check: ${l?"granted":"not confirmed"}; compare with sablier_overlay above to distinguish license access from bundle contents`)}return i>0?(console.log(`\u2713 Premium resources unlocked (${i} file${i===1?"":"s"})`),!0):!1}catch(t){return console.warn(`\u26A0\uFE0F Failed to apply premium overlay: ${t.message}`),!1}}function K(e,n){let r=e.replace(/[^a-zA-Z0-9_-]/g,"_");return a(P(),".flisk","cache",`premium-${r}`,`${n}-access.json`)}async function b(e,n){let r=I();if(!r)return!1;let o=K(r,n);if(T(o))try{let s=JSON.parse(y(o,"utf-8"));return console.info(`\u{1F50E} ${n} entitlement probe: cache=${s.granted===!0?"granted":"denied"}`),s.granted===!0}catch{}let t=x(e),i=!1;try{let s=await E(r,n,t);i=s.ok,console.info(`\u{1F50E} ${n} entitlement probe: HTTP ${s.status} (${i?"granted":"denied"})`),s.body&&await s.arrayBuffer().catch(()=>{})}catch(s){return console.warn(`\u26A0\uFE0F ${n} license check failed (network error): ${s.message}`),!1}try{g(a(o,".."),{recursive:!0}),h(o,JSON.stringify({granted:i,checked:new Date().toISOString()}))}catch{}return i}async function ne(e){return b(e,"verdaccio")}async function oe(e){return b(e,"ddd")}async function G(e){return b(e,"sablier")}export{S as a,re as b,ne as c,oe as d,G as e};