@flisk-sh/flisk 0.0.0-stage โ†’ 1.3.189

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 (440) hide show
  1. package/CHANGELOG.md +492 -0
  2. package/LICENSE +21 -0
  3. package/README.md +479 -2
  4. package/Tiltfile +151 -0
  5. package/cli/bin/flisk.js +42 -0
  6. package/cli/dist/agent-runtimes/custom.js +335 -0
  7. package/cli/dist/agent-runtimes/registry.js +34 -0
  8. package/cli/dist/backend-frameworks/elysia.js +38 -0
  9. package/cli/dist/backend-frameworks/express.js +49 -0
  10. package/cli/dist/backend-frameworks/fastify.js +43 -0
  11. package/cli/dist/backend-frameworks/h3.js +37 -0
  12. package/cli/dist/backend-frameworks/koa.js +47 -0
  13. package/cli/dist/backend-frameworks/mcp.js +86 -0
  14. package/cli/dist/backend-frameworks/nestjs.js +55 -0
  15. package/cli/dist/backend-frameworks/registry.js +134 -0
  16. package/cli/dist/backend-frameworks/types.js +1 -0
  17. package/cli/dist/backend-languages/bun.js +76 -0
  18. package/cli/dist/backend-languages/go.js +125 -0
  19. package/cli/dist/backend-languages/node.js +122 -0
  20. package/cli/dist/backend-languages/python.js +94 -0
  21. package/cli/dist/backend-languages/registry.js +37 -0
  22. package/cli/dist/backend-languages/rust.js +82 -0
  23. package/cli/dist/backend-languages/types.js +1 -0
  24. package/cli/dist/cli.js +29 -0
  25. package/cli/dist/commands/__tests__/project-e2e-helpers.js +49 -0
  26. package/cli/dist/commands/__tests__/ui-loading-screen.test.js +16 -0
  27. package/cli/dist/commands/agent.js +182 -0
  28. package/cli/dist/commands/completion-candidates.js +116 -0
  29. package/cli/dist/commands/completion.js +843 -0
  30. package/cli/dist/commands/config.js +362 -0
  31. package/cli/dist/commands/doctor.js +1504 -0
  32. package/cli/dist/commands/down.js +114 -0
  33. package/cli/dist/commands/eject.js +108 -0
  34. package/cli/dist/commands/help.js +152 -0
  35. package/cli/dist/commands/import.js +164 -0
  36. package/cli/dist/commands/logs.js +119 -0
  37. package/cli/dist/commands/maintainers.js +102 -0
  38. package/cli/dist/commands/mcp.js +28 -0
  39. package/cli/dist/commands/networks.js +306 -0
  40. package/cli/dist/commands/project.js +447 -0
  41. package/cli/dist/commands/projects.js +103 -0
  42. package/cli/dist/commands/registry.js +53 -0
  43. package/cli/dist/commands/resource.js +992 -0
  44. package/cli/dist/commands/resources.js +127 -0
  45. package/cli/dist/commands/restart.js +89 -0
  46. package/cli/dist/commands/runtime.js +75 -0
  47. package/cli/dist/commands/stack.js +171 -0
  48. package/cli/dist/commands/stacks.js +71 -0
  49. package/cli/dist/commands/status.js +211 -0
  50. package/cli/dist/commands/ui.js +725 -0
  51. package/cli/dist/commands/up.js +609 -0
  52. package/cli/dist/commands/upgrade.js +840 -0
  53. package/cli/dist/commands/version.js +10 -0
  54. package/cli/dist/components/Accessible.js +11 -0
  55. package/cli/dist/components/BaseTooltip.js +30 -0
  56. package/cli/dist/components/DetailPanel.js +33 -0
  57. package/cli/dist/components/FileTree.js +51 -0
  58. package/cli/dist/components/ResourceSelectInput.js +42 -0
  59. package/cli/dist/components/ResourceTable.js +35 -0
  60. package/cli/dist/components/ServiceIssues.js +13 -0
  61. package/cli/dist/components/TUIHeader.js +27 -0
  62. package/cli/dist/components/TabBar.js +27 -0
  63. package/cli/dist/components/Tooltip.js +13 -0
  64. package/cli/dist/components/index.js +11 -0
  65. package/cli/dist/components/ui-theme.js +120 -0
  66. package/cli/dist/config/platform-standards.js +182 -0
  67. package/cli/dist/frontend-frameworks/lit.js +64 -0
  68. package/cli/dist/frontend-frameworks/preact.js +62 -0
  69. package/cli/dist/frontend-frameworks/qwik.js +61 -0
  70. package/cli/dist/frontend-frameworks/react.js +69 -0
  71. package/cli/dist/frontend-frameworks/registry.js +83 -0
  72. package/cli/dist/frontend-frameworks/solid.js +62 -0
  73. package/cli/dist/frontend-frameworks/svelte.js +67 -0
  74. package/cli/dist/frontend-frameworks/tanstack-router.js +101 -0
  75. package/cli/dist/frontend-frameworks/types.js +1 -0
  76. package/cli/dist/frontend-frameworks/vanilla.js +59 -0
  77. package/cli/dist/frontend-frameworks/vue.js +72 -0
  78. package/cli/dist/generator/__tests__/golden-helpers.js +43 -0
  79. package/cli/dist/generator/extension-fetch.js +291 -0
  80. package/cli/dist/generator/resource-templates.js +133 -0
  81. package/cli/dist/generator/template-engine.js +801 -0
  82. package/cli/dist/index.js +10 -0
  83. package/cli/dist/mcp/server.js +101 -0
  84. package/cli/dist/mcp/tools.js +351 -0
  85. package/cli/dist/program.js +37 -0
  86. package/cli/dist/types/index.js +38 -0
  87. package/cli/dist/utils/agent-host.js +36 -0
  88. package/cli/dist/utils/atomic-write.js +36 -0
  89. package/cli/dist/utils/bun-floor.js +9 -0
  90. package/cli/dist/utils/bun-requirement.js +10 -0
  91. package/cli/dist/utils/cache.js +63 -0
  92. package/cli/dist/utils/cold-preflight.js +257 -0
  93. package/cli/dist/utils/command-helpers.js +57 -0
  94. package/cli/dist/utils/constants.js +160 -0
  95. package/cli/dist/utils/discovery-context.js +71 -0
  96. package/cli/dist/utils/discovery-paths.js +93 -0
  97. package/cli/dist/utils/docker.js +34 -0
  98. package/cli/dist/utils/doctor-crashloop.js +161 -0
  99. package/cli/dist/utils/doctor-fix.js +391 -0
  100. package/cli/dist/utils/doctor-host.js +66 -0
  101. package/cli/dist/utils/doctor-report.js +78 -0
  102. package/cli/dist/utils/doctor-runtime.js +880 -0
  103. package/cli/dist/utils/doctor-starlark.js +132 -0
  104. package/cli/dist/utils/doctor-wiring.js +698 -0
  105. package/cli/dist/utils/down-cleanup.js +125 -0
  106. package/cli/dist/utils/env-validator.js +266 -0
  107. package/cli/dist/utils/errors.js +175 -0
  108. package/cli/dist/utils/exec-async.js +31 -0
  109. package/cli/dist/utils/file-helpers.js +104 -0
  110. package/cli/dist/utils/formatting.js +165 -0
  111. package/cli/dist/utils/generated-paths.js +51 -0
  112. package/cli/dist/utils/help-command.js +11 -0
  113. package/cli/dist/utils/host-port-config.js +140 -0
  114. package/cli/dist/utils/host-port-plan.js +100 -0
  115. package/cli/dist/utils/json-output.js +29 -0
  116. package/cli/dist/utils/machine-output.js +33 -0
  117. package/cli/dist/utils/paths.js +43 -0
  118. package/cli/dist/utils/platform.js +58 -0
  119. package/cli/dist/utils/port-assignment.js +75 -0
  120. package/cli/dist/utils/port-conflict-fix.js +25 -0
  121. package/cli/dist/utils/port-fallback-notice.js +17 -0
  122. package/cli/dist/utils/port-listener-probe.js +20 -0
  123. package/cli/dist/utils/project-config-defaults.js +17 -0
  124. package/cli/dist/utils/project-features.js +91 -0
  125. package/cli/dist/utils/project-templates.js +32 -0
  126. package/cli/dist/utils/prompt.js +97 -0
  127. package/cli/dist/utils/resource-features.js +184 -0
  128. package/cli/dist/utils/resource-kind.js +9 -0
  129. package/cli/dist/utils/schema-version-gate.js +45 -0
  130. package/cli/dist/utils/search-status.js +12 -0
  131. package/cli/dist/utils/secret-redaction.js +73 -0
  132. package/cli/dist/utils/service-config-checks.js +189 -0
  133. package/cli/dist/utils/service-manifest.js +254 -0
  134. package/cli/dist/utils/service-runtime-state.js +66 -0
  135. package/cli/dist/utils/service-urls.js +112 -0
  136. package/cli/dist/utils/services.js +360 -0
  137. package/cli/dist/utils/shared-infra.js +45 -0
  138. package/cli/dist/utils/shared-platform-postgres.js +232 -0
  139. package/cli/dist/utils/single-flight.js +21 -0
  140. package/cli/dist/utils/smoke.js +401 -0
  141. package/cli/dist/utils/spinner.js +40 -0
  142. package/cli/dist/utils/stack-features.js +15 -0
  143. package/cli/dist/utils/stack-status.js +14 -0
  144. package/cli/dist/utils/startup-report.js +78 -0
  145. package/cli/dist/utils/status-message.js +21 -0
  146. package/cli/dist/utils/status-ports.js +33 -0
  147. package/cli/dist/utils/suggestions.js +63 -0
  148. package/cli/dist/utils/tar.js +46 -0
  149. package/cli/dist/utils/tdk-version.js +51 -0
  150. package/cli/dist/utils/terminal-layout.js +19 -0
  151. package/cli/dist/utils/tilt-events.js +194 -0
  152. package/cli/dist/utils/tilt-logs.js +61 -0
  153. package/cli/dist/utils/tilt-port-owner.js +63 -0
  154. package/cli/dist/utils/tilt-process.js +54 -0
  155. package/cli/dist/utils/tilt-resource-state.js +32 -0
  156. package/cli/dist/utils/tilt-startup.js +49 -0
  157. package/cli/dist/utils/tilt-unreachable-message.js +9 -0
  158. package/cli/dist/utils/tilt.js +104 -0
  159. package/cli/dist/utils/ui-keymap.js +129 -0
  160. package/cli/dist/utils/ui-service-state.js +34 -0
  161. package/cli/dist/utils/up-only.js +28 -0
  162. package/cli/dist/utils/up-readiness.js +118 -0
  163. package/cli/dist/utils/url-actions.js +103 -0
  164. package/cli/dist/utils/validation.js +62 -0
  165. package/cli/dist/utils/which.js +56 -0
  166. package/cli/dist/utils/windows-doctor.js +176 -0
  167. package/cli/dist-bundle/chunk-6EA4DGCY.js +2 -0
  168. package/cli/dist-bundle/chunk-BZ4H7MM4.js +3 -0
  169. package/cli/dist-bundle/chunk-QWYXYAX6.js +510 -0
  170. package/cli/dist-bundle/cli.js +2017 -0
  171. package/cli/dist-bundle/cold-preflight-MM46GFQA.js +2 -0
  172. package/cli/dist-bundle/devtools-ZUHXW33W.js +5 -0
  173. package/cli/dist-bundle/extension-fetch-J4FFH7TJ.js +2 -0
  174. package/cli/package.json +83 -0
  175. package/cli/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
  176. package/cli/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
  177. package/cli/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
  178. package/cli/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
  179. package/cli/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
  180. package/cli/templates/.tiltignore.hbs +72 -0
  181. package/cli/templates/TILT_RESOURCE_DEFAULTS.star.hbs +220 -0
  182. package/cli/templates/TILT_TECH_STACK.star.hbs +149 -0
  183. package/cli/templates/Tiltfile.hbs +381 -0
  184. package/cli/templates/spec.master.hbs +192 -0
  185. package/discovery/AGENTS.md +362 -0
  186. package/discovery/TILT_DISCOVERY.star +429 -0
  187. package/discovery/Tiltfile +58 -0
  188. package/discovery/config.star +265 -0
  189. package/discovery/constants.star +167 -0
  190. package/discovery/deterministic.star +213 -0
  191. package/discovery/discovery_daemon.star +193 -0
  192. package/discovery/discovery_orchestrator.star +492 -0
  193. package/discovery/filtering.star +7 -0
  194. package/discovery/identity-management-backend/service.yaml +26 -0
  195. package/discovery/identity-management-frontend/service.yaml +29 -0
  196. package/discovery/identity-sdk/service.yaml +9 -0
  197. package/discovery/incremental_discovery.star +215 -0
  198. package/discovery/indexing.star +9 -0
  199. package/discovery/json_manifest_scanner.star +57 -0
  200. package/discovery/libraries.star +48 -0
  201. package/discovery/loading.star +25 -0
  202. package/discovery/manifest/constants.star +106 -0
  203. package/discovery/manifest/loading.star +418 -0
  204. package/discovery/manifest/normalize.star +289 -0
  205. package/discovery/manifest/validation.star +110 -0
  206. package/discovery/normalization.star +13 -0
  207. package/discovery/registry.star +562 -0
  208. package/discovery/resource_snapshot.py +384 -0
  209. package/discovery/resource_snapshot.star +145 -0
  210. package/discovery/schemas.star +3 -0
  211. package/discovery/services/platform/cdc/debezium/nats-connector/service.yaml +9 -0
  212. package/discovery/services/platform/cdc/nats-http-bridge/service.yaml +9 -0
  213. package/discovery/services/platform/orchestrator/orchestrator-glue-backend/service.yaml +26 -0
  214. package/discovery/services/platform/synthetic-monitor/service.yaml +9 -0
  215. package/discovery/test_resource_snapshot.py +253 -0
  216. package/discovery/validation.star +80 -0
  217. package/discovery/versioning.star +3 -0
  218. package/engine/AGENTS.md +706 -0
  219. package/engine/README.md +31 -0
  220. package/engine/REFACTORING_MANIFEST_SYSTEM.md +974 -0
  221. package/engine/Tiltfile +86 -0
  222. package/engine/assets/docker/golden-layers.Dockerfile +204 -0
  223. package/engine/assets/docker/wake-gateway/Dockerfile +28 -0
  224. package/engine/assets/docker/wake-gateway/decide.test.ts +177 -0
  225. package/engine/assets/docker/wake-gateway/decide.ts +135 -0
  226. package/engine/assets/docker/wake-gateway/server.ts +363 -0
  227. package/engine/assets/scripts/cleanup-completed-migrator-images.sh +39 -0
  228. package/engine/assets/scripts/cleanup-tilt-images.sh +122 -0
  229. package/engine/assets/scripts/mac-m1-resource-monitor.sh +123 -0
  230. package/engine/config.star +5 -0
  231. package/engine/entrypoint.star +101 -0
  232. package/engine/lifecycle/orchestrator.star +263 -0
  233. package/engine/paths.star +719 -0
  234. package/engine/schemas/project-schema.json +147 -0
  235. package/engine/schemas/service-schema.json +537 -0
  236. package/engine/scripts/README.md +89 -0
  237. package/engine/scripts/build-and-publish-lib.sh +207 -0
  238. package/engine/scripts/check-lib-publish.sh +87 -0
  239. package/engine/scripts/debug-container.sh +265 -0
  240. package/engine/scripts/generate-manifest.sh +200 -0
  241. package/engine/spec.master +702 -0
  242. package/engine/spec.master.example +341 -0
  243. package/engine/topologies/README.md +33 -0
  244. package/engine/topologies/platform/docker/AGENTS.md +59 -0
  245. package/engine/topologies/platform/docker/auth/auth.star +207 -0
  246. package/engine/topologies/platform/docker/build/frontend_build_plan.star +65 -0
  247. package/engine/topologies/platform/docker/build/golden_image.star +37 -0
  248. package/engine/topologies/platform/docker/build/golden_image_build.star +144 -0
  249. package/engine/topologies/platform/docker/build/golden_image_constants.star +100 -0
  250. package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +263 -0
  251. package/engine/topologies/platform/docker/compose/compose.star +637 -0
  252. package/engine/topologies/platform/docker/compose/compose_helpers.star +54 -0
  253. package/engine/topologies/platform/docker/compose/traefik_standalone.star +171 -0
  254. package/engine/topologies/platform/docker/config/bunignore.star +144 -0
  255. package/engine/topologies/platform/docker/config/dockerignore.star +152 -0
  256. package/engine/topologies/platform/docker/config/healthcheck.star +58 -0
  257. package/engine/topologies/platform/docker/constants.star +211 -0
  258. package/engine/topologies/platform/docker/dockerfile/dockerfile.star +56 -0
  259. package/engine/topologies/platform/docker/dockerfile/language_dockerfile.star +264 -0
  260. package/engine/topologies/platform/docker/generators/golden_docker_generator_v2.star +76 -0
  261. package/engine/topologies/platform/docker/index.star +40 -0
  262. package/engine/topologies/platform/docker/layers/bun_helpers.star +25 -0
  263. package/engine/topologies/platform/docker/layers/infisical/index.star +42 -0
  264. package/engine/topologies/platform/docker/layers/infisical/infisical_docker.star +264 -0
  265. package/engine/topologies/platform/docker/layers/l1_base_layers.star +89 -0
  266. package/engine/topologies/platform/docker/layers/l2_dependency_layers.star +147 -0
  267. package/engine/topologies/platform/docker/layers/l3_builder_layers.star +229 -0
  268. package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +282 -0
  269. package/engine/topologies/platform/docker/layers/prisma/constants.star +138 -0
  270. package/engine/topologies/platform/docker/layers/prisma/db_readiness.star +98 -0
  271. package/engine/topologies/platform/docker/layers/prisma/filesystem_setup.star +131 -0
  272. package/engine/topologies/platform/docker/layers/prisma/migrate_script.star +100 -0
  273. package/engine/topologies/platform/docker/layers/prisma/prisma_build.star +128 -0
  274. package/engine/topologies/platform/docker/layers/prisma/prisma_runtime.star +205 -0
  275. package/engine/topologies/platform/docker/layers/prisma/secret_resolution.star +121 -0
  276. package/engine/topologies/platform/docker/networking/api_path_constants.star +206 -0
  277. package/engine/topologies/platform/docker/networking/sablier_container_cycle.star +31 -0
  278. package/engine/topologies/platform/docker/networking/traefik.star +266 -0
  279. package/engine/topologies/platform/docker/networking/traefik_constants.star +148 -0
  280. package/engine/topologies/platform/docker/networking/traefik_helpers.star +162 -0
  281. package/engine/topologies/platform/docker/networking/traefik_static_routes.star +165 -0
  282. package/engine/topologies/platform/docker/secrets/env_secrets.star +74 -0
  283. package/engine/topologies/platform/docker/secrets/infisical.star +109 -0
  284. package/engine/topologies/platform/net/AGENTS.md +64 -0
  285. package/engine/topologies/platform/net/proxy.star +6 -0
  286. package/engine/topologies/platform/registries/local.star +6 -0
  287. package/engine/topologies/platform/registries/verdaccio_loader.star +20 -0
  288. package/engine/topologies/platform/security/AGENTS.md +49 -0
  289. package/engine/topologies/platform/security/secrets.star +241 -0
  290. package/engine/topologies/platform/state/AGENTS.md +64 -0
  291. package/engine/topologies/platform/state/relational.star +6 -0
  292. package/engine/topologies/tilt/common/AGENTS.md +59 -0
  293. package/engine/topologies/tilt/common/utils.star +409 -0
  294. package/engine/topologies/tilt/common/utils_debug.star +71 -0
  295. package/engine/topologies/tilt/common/utils_deterministic.star +170 -0
  296. package/engine/topologies/tilt/common/utils_docker_networks.star +54 -0
  297. package/engine/topologies/tilt/common/utils_env.star +92 -0
  298. package/engine/topologies/tilt/common/utils_output.star +99 -0
  299. package/engine/topologies/tilt/config/AGENTS.md +68 -0
  300. package/engine/topologies/tilt/config/defaults.star +5 -0
  301. package/engine/topologies/tilt/config/feature_flags.star +12 -0
  302. package/engine/topologies/tilt/config/global.star +57 -0
  303. package/engine/topologies/tilt/config/profiles.star +398 -0
  304. package/engine/topologies/tilt/generators/agents_md.star +64 -0
  305. package/engine/topologies/tilt/generators/api_gateway.star +147 -0
  306. package/engine/topologies/tilt/generators/index.star +126 -0
  307. package/engine/topologies/tilt/generators/infisical/README.md +424 -0
  308. package/engine/topologies/tilt/generators/infisical/examples.star +314 -0
  309. package/engine/topologies/tilt/generators/infisical/index.star +59 -0
  310. package/engine/topologies/tilt/generators/infisical/machine_identity.star +422 -0
  311. package/engine/topologies/tilt/generators/infisical/organization.star +536 -0
  312. package/engine/topologies/tilt/generators/infisical/path_manager.star +478 -0
  313. package/engine/topologies/tilt/generators/infisical/secrets_generator.star +422 -0
  314. package/engine/topologies/tilt/generators/nginx_conf.star +145 -0
  315. package/engine/topologies/tilt/generators/npmrc.star +132 -0
  316. package/engine/topologies/tilt/generators/tsconfig.star +25 -0
  317. package/engine/topologies/tilt/generators/typescript/backend_library_tsconfig.star +73 -0
  318. package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +137 -0
  319. package/engine/topologies/tilt/generators/typescript/common.star +9 -0
  320. package/engine/topologies/tilt/generators/typescript/frontend_library_tsconfig.star +78 -0
  321. package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +140 -0
  322. package/engine/topologies/tilt/generators/typescript/include.star +25 -0
  323. package/engine/topologies/tilt/generators/typescript/playwright_config.star +42 -0
  324. package/engine/topologies/tilt/generators/typescript/prisma_config.star +51 -0
  325. package/engine/topologies/tilt/generators/validators.star +868 -0
  326. package/engine/topologies/tilt/generators/vite/backend.star +80 -0
  327. package/engine/topologies/tilt/generators/vite/frameworks/lit.star +92 -0
  328. package/engine/topologies/tilt/generators/vite/frameworks/preact.star +94 -0
  329. package/engine/topologies/tilt/generators/vite/frameworks/qwik.star +94 -0
  330. package/engine/topologies/tilt/generators/vite/frameworks/react.star +128 -0
  331. package/engine/topologies/tilt/generators/vite/frameworks/solid.star +94 -0
  332. package/engine/topologies/tilt/generators/vite/frameworks/svelte.star +94 -0
  333. package/engine/topologies/tilt/generators/vite/frameworks/vanilla.star +92 -0
  334. package/engine/topologies/tilt/generators/vite/frameworks/vue.star +94 -0
  335. package/engine/topologies/tilt/generators/vite/frontend.star +153 -0
  336. package/engine/topologies/tilt/generators/vite/generators.star +45 -0
  337. package/engine/topologies/tilt/generators/vite/helpers.star +256 -0
  338. package/engine/topologies/tilt/generators/vite/library.star +67 -0
  339. package/engine/topologies/tilt/generators/vite/sdk.star +75 -0
  340. package/engine/topologies/tilt/generators/vite/templates.star +460 -0
  341. package/engine/topologies/tilt/generators/vite_config.star +24 -0
  342. package/engine/topologies/tilt/manifest/AGENTS.md +61 -0
  343. package/engine/topologies/tilt/manifest/README.md +679 -0
  344. package/engine/topologies/tilt/manifest/__init__.star +220 -0
  345. package/engine/topologies/tilt/manifest/constants.star +262 -0
  346. package/engine/topologies/tilt/manifest/errors.star +268 -0
  347. package/engine/topologies/tilt/manifest/integration.star +348 -0
  348. package/engine/topologies/tilt/manifest/json_to_yaml.py +61 -0
  349. package/engine/topologies/tilt/manifest/loader.star +239 -0
  350. package/engine/topologies/tilt/manifest/mapper.star +229 -0
  351. package/engine/topologies/tilt/manifest/parser.star +490 -0
  352. package/engine/topologies/tilt/manifest/schema.star +502 -0
  353. package/engine/topologies/tilt/manifest/tests/test_integration.star +278 -0
  354. package/engine/topologies/tilt/manifest/tests/test_loader_parser.star +475 -0
  355. package/engine/topologies/tilt/manifest/tests/test_manifest.star +319 -0
  356. package/engine/topologies/tilt/manifest/tests/test_validator.star +472 -0
  357. package/engine/topologies/tilt/manifest/validator.star +744 -0
  358. package/engine/topologies/tilt/manifest/yaml_to_json.py +20 -0
  359. package/engine/topologies/tilt/resources/AGENTS.md +71 -0
  360. package/engine/topologies/tilt/resources/build_concurrency.star +56 -0
  361. package/engine/topologies/tilt/resources/conditions.star +5 -0
  362. package/engine/topologies/tilt/resources/databases.star +246 -0
  363. package/engine/topologies/tilt/resources/declaration.star +5 -0
  364. package/engine/topologies/tilt/resources/deps.star +5 -0
  365. package/engine/topologies/tilt/resources/infra-loader.star +504 -0
  366. package/engine/topologies/tilt/resources/libs.star +32 -0
  367. package/engine/topologies/tilt/resources/libs_discovery.star +104 -0
  368. package/engine/topologies/tilt/resources/libs_paths.star +81 -0
  369. package/engine/topologies/tilt/resources/libs_resource_definition.star +153 -0
  370. package/engine/topologies/tilt/resources/libs_setup.star +166 -0
  371. package/engine/topologies/tilt/resources/libs_tsconfig.star +39 -0
  372. package/engine/topologies/tilt/resources/orchestrator/apply.star +79 -0
  373. package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +864 -0
  374. package/engine/topologies/tilt/resources/orchestrator/apply_manifest_orchestration.star +90 -0
  375. package/engine/topologies/tilt/resources/orchestrator/apply_migrator_orchestration.star +150 -0
  376. package/engine/topologies/tilt/resources/orchestrator/apply_resource_validation.star +38 -0
  377. package/engine/topologies/tilt/resources/orchestrator/apply_runtime_flags.star +46 -0
  378. package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +193 -0
  379. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/AGENTS.md +75 -0
  380. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/index.star +23 -0
  381. package/engine/topologies/tilt/resources/orchestrator/generators/dependencies/resolver.star +136 -0
  382. package/engine/topologies/tilt/resources/orchestrator/generators/env.star +219 -0
  383. package/engine/topologies/tilt/resources/orchestrator/generators/frontend.star +172 -0
  384. package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +577 -0
  385. package/engine/topologies/tilt/resources/orchestrator/helpers.star +35 -0
  386. package/engine/topologies/tilt/resources/orchestrator.star +56 -0
  387. package/engine/topologies/tilt/resources/ordering.star +5 -0
  388. package/engine/topologies/tilt/resources/shared-platform-postgres.star +30 -0
  389. package/engine/topologies/tilt/resources/triggers.star +5 -0
  390. package/engine/topologies/tilt/resources/watchers.star +304 -0
  391. package/engine/where-things-live.star +719 -0
  392. package/examples/tdk-example/LICENSE +21 -0
  393. package/examples/tdk-example/README.md +23 -0
  394. package/examples/tdk-example/TILT_SERVICE_DEFAULTS.star +42 -0
  395. package/examples/tdk-example/TILT_TECH_STACK.star +67 -0
  396. package/examples/tdk-example/Tiltfile +25 -0
  397. package/examples/tdk-example/services/platform/messaging/docker-compose.yml +26 -0
  398. package/examples/tdk-example/services/shop/orders-api/package.json +20 -0
  399. package/examples/tdk-example/services/shop/orders-api/service.json +14 -0
  400. package/examples/tdk-example/services/shop/orders-api/src/index.ts +81 -0
  401. package/examples/tdk-example/services/shop/orders-app/index.html +12 -0
  402. package/examples/tdk-example/services/shop/orders-app/package.json +23 -0
  403. package/examples/tdk-example/services/shop/orders-app/public/.gitkeep +0 -0
  404. package/examples/tdk-example/services/shop/orders-app/service.json +9 -0
  405. package/examples/tdk-example/services/shop/orders-app/src/main.tsx +9 -0
  406. package/examples/tdk-example/services/shop/orders-app/src/orders.tsx +67 -0
  407. package/examples/tdk-example/services/shop/orders-app/src/style.css +15 -0
  408. package/examples/tdk-example/services/shop/orders-worker/package.json +18 -0
  409. package/examples/tdk-example/services/shop/orders-worker/service.json +13 -0
  410. package/examples/tdk-example/services/shop/orders-worker/src/index.ts +33 -0
  411. package/ext/Tiltfile +66 -0
  412. package/ext/ide-components/README.md +221 -0
  413. package/ext/ide-components/code_viewer/server.py +208 -0
  414. package/ext/ide-components/config_inspector/server.py +1052 -0
  415. package/ext/ide-components/file_browser/server.py +287 -0
  416. package/ext/ide-components/shared/config_service.py +471 -0
  417. package/ext/ide-components/shared/file_utils.py +300 -0
  418. package/ext/ide-components/shared/http_utils.py +234 -0
  419. package/ext/ide-components/shared/static/styles.css +1334 -0
  420. package/ext/ide-components/shared/syntax_highlighter.py +142 -0
  421. package/ext/ide-components/shared/template_engine.py +125 -0
  422. package/ext/ide-components/shared/templates/base.html +121 -0
  423. package/ext/ide-components/shared/test_config_service.py +328 -0
  424. package/ext/ide-components/shared/tilt_integration.py +160 -0
  425. package/ext/ui-enhancements/cron-status-server.sh +51 -0
  426. package/ext/ui-enhancements/cron-status-tracker.sh +148 -0
  427. package/ext/ui-enhancements/extension.yaml +36 -0
  428. package/ext/ui-enhancements/tiltfile.py +101 -0
  429. package/ext/ui-enhancements/ui/cron-jobs-tab.css +396 -0
  430. package/ext/ui-enhancements/ui/help-panel.css +279 -0
  431. package/ext/ui-enhancements/ui/icons.css +125 -0
  432. package/ext/ui-enhancements/ui/tooltips.css +114 -0
  433. package/package.json +94 -4
  434. package/shared-platform-engineering/docker-templates/bun-hoisted-symlink-fix.sh +25 -0
  435. package/shared-platform-engineering/docker-templates/infisical-entrypoint.sh +466 -0
  436. package/shared-platform-engineering/docker-templates/install-deps.sh +154 -0
  437. package/shared-platform-engineering/docker-templates/prisma-bun-client-link-fix.sh +14 -0
  438. package/shared-platform-engineering/docker-templates/prisma-normalize-client.sh +58 -0
  439. package/specs/specs/TILT_SERVICE_DEFAULTS.star +207 -0
  440. package/specs/specs/TILT_TECH_STACK.star +161 -0
@@ -0,0 +1,992 @@
1
+ // Copyright (c) 2026 Flisk Landscape contributors
2
+ // SPDX-License-Identifier: MIT
3
+ import { existsSync, mkdirSync } from "node:fs";
4
+ import { isAbsolute, relative, resolve } from "node:path";
5
+ import chalk from "chalk";
6
+ import { Command } from "commander";
7
+ import { DEFAULT_AGENT_RUNTIME, resolveAgentDurability, resolveAgentRuntime, } from "../agent-runtimes/registry.js";
8
+ import { DEFAULT_BACKEND_FRAMEWORK, listBackendFrameworks, resolveBackendFramework, } from "../backend-frameworks/registry.js";
9
+ import { getBackendIndexTemplate, getDrizzleBackendIndexTemplate, } from "../backend-languages/bun.js";
10
+ import { resolveBackendLanguage } from "../backend-languages/registry.js";
11
+ import { DEFAULT_FRONTEND_FRAMEWORK, listFrontendFrameworks, resolveFrontendFramework, } from "../frontend-frameworks/registry.js";
12
+ import { hasDddLicense } from "../generator/extension-fetch.js";
13
+ import { getDockerfileTemplate, getTestTemplate, getWorkerIndexTemplate, } from "../generator/resource-templates.js";
14
+ import { RESOURCE_TYPE_VALUES } from "../types/index.js";
15
+ import { addYesOption, assertValid, confirmOrCancel } from "../utils/command-helpers.js";
16
+ import { AUTOGENERATED_DIR, BRING_YOUR_OWN_TYPE, PACKAGE_JSON, PORT_RANGES, SERVICE_JSON, } from "../utils/constants.js";
17
+ import { clearDiscoveryCache } from "../utils/discovery-context.js";
18
+ import { chooseResourcePath, isPathDiscovered, readDiscoveryPaths, } from "../utils/discovery-paths.js";
19
+ import { errorFactories, requireProjectRoot, runCommand, TdkError } from "../utils/errors.js";
20
+ import { writeFilesWithProgress } from "../utils/file-helpers.js";
21
+ import { showCommandHeader } from "../utils/formatting.js";
22
+ import { assignPort } from "../utils/port-assignment.js";
23
+ import { promptSelect, promptText } from "../utils/prompt.js";
24
+ import { getDefaultFeaturesForResourceType } from "../utils/resource-features.js";
25
+ import { SERVICE_MANIFEST_SCHEMA_URL, SERVICE_MANIFEST_SCHEMA_VERSION, } from "../utils/service-manifest.js";
26
+ import { discoverResources } from "../utils/services.js";
27
+ import { createKebabCaseValidator, isPathSafe, validateResourceName, validateStackName, } from "../utils/validation.js";
28
+ export { getDockerfileTemplate, getShutdownHandlerTemplate, getTestTemplate, getWorkerIndexTemplate, } from "../generator/resource-templates.js";
29
+ export const BASE_TEMPLATE = {
30
+ port: 0, // Will be assigned
31
+ // `dependsOn` is the current name; `dependencies` is deprecated and `flisk doctor` warns about it, so a fresh scaffold must not write it.
32
+ dependsOn: [],
33
+ build: {
34
+ dockerfile: "Dockerfile",
35
+ context: ".",
36
+ },
37
+ dev: {
38
+ command: "bun run dev",
39
+ watch: ["src/**/*"],
40
+ },
41
+ };
42
+ export const TYPE_SPECIFIC = {
43
+ backend: {
44
+ healthCheckPath: "/health",
45
+ },
46
+ frontend: {
47
+ dev: {
48
+ command: "bun run dev",
49
+ watch: ["src/**/*", "public/**/*"],
50
+ },
51
+ },
52
+ worker: {
53
+ dev: {
54
+ command: "bun run worker",
55
+ watch: ["src/**/*"],
56
+ },
57
+ },
58
+ mcp: {
59
+ healthCheckPath: "/health",
60
+ },
61
+ // /ready is the readiness route (checkpoint store reachable); /health is liveness only, so an idle agent stays healthy.
62
+ agent: {
63
+ healthCheckPath: "/health",
64
+ },
65
+ [BRING_YOUR_OWN_TYPE]: {
66
+ healthCheckPath: "/health",
67
+ },
68
+ };
69
+ const BYO_RESTART_POLICIES = ["no", "on-failure", "unless-stopped", "always"];
70
+ export function resolveByoPort(value, assignedPort, resources) {
71
+ return resolveResourcePort(BRING_YOUR_OWN_TYPE, value, assignedPort, resources);
72
+ }
73
+ // `--port` is an override for every port-assigned type, validated against that type's range.
74
+ // Without it the next free port in the type's range is used.
75
+ export function resolveResourcePort(resourceType, value, assignedPort, resources) {
76
+ if (!value)
77
+ return assignedPort;
78
+ const { min, max } = PORT_RANGES[resourceType];
79
+ const flag = resourceType === BRING_YOUR_OWN_TYPE ? "BYO --port" : "--port";
80
+ const subject = resourceType === BRING_YOUR_OWN_TYPE ? "BYO port" : "Port";
81
+ if (!/^\d+$/.test(value)) {
82
+ throw new TdkError(`${flag} must be an integer from ${min} through ${max}.`);
83
+ }
84
+ const port = Number(value);
85
+ if (!Number.isInteger(port) || port < min || port > max) {
86
+ throw new TdkError(`${flag} must be an integer from ${min} through ${max}.`);
87
+ }
88
+ if (resources.some((resource) => resource.config?.port === port)) {
89
+ throw new TdkError(`${subject} ${port} is already assigned to another resource.`);
90
+ }
91
+ return port;
92
+ }
93
+ export function createServiceJson(name, type, stack, port, extraFeatures = [], frameworkId, languageId, agent) {
94
+ const framework = resolveFrontendFramework(type, frameworkId);
95
+ // Only an explicit selection is persisted: a missing `language` has always meant Bun.
96
+ const language = languageId === undefined ? undefined : resolveBackendLanguage(type, languageId);
97
+ // Likewise a missing backend `framework` has always meant Hono.
98
+ const backendFramework = resolveBackendFramework(type, frameworkId, language);
99
+ const typeSpecific = TYPE_SPECIFIC[type];
100
+ const base = JSON.parse(JSON.stringify(BASE_TEMPLATE));
101
+ for (const [key, value] of Object.entries(typeSpecific)) {
102
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
103
+ base[key] = { ...base[key], ...value };
104
+ }
105
+ else {
106
+ base[key] = value;
107
+ }
108
+ }
109
+ if (language?.devCommand) {
110
+ base.dev = { command: language.devCommand, watch: language.watch ?? base.dev.watch };
111
+ // The engine generates the image (.autogenerated/Dockerfile.app.autogenerated); a build
112
+ // block would point at a Dockerfile that does not exist.
113
+ delete base.build;
114
+ }
115
+ return {
116
+ ...base,
117
+ $schema: SERVICE_MANIFEST_SCHEMA_URL,
118
+ schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
119
+ appName: name,
120
+ appType: type,
121
+ featuresEnabled: [
122
+ ...getDefaultFeaturesForResourceType(type).filter(
123
+ // Prisma wiring is Bun-only; the Python provider owns their data layer.
124
+ (feature) => !(language?.createFiles && feature === "prisma")),
125
+ ...extraFeatures,
126
+ ],
127
+ name,
128
+ type,
129
+ stack,
130
+ port,
131
+ ...(framework ? { framework: framework.id } : {}),
132
+ ...(backendFramework && type !== "mcp" && type !== "agent"
133
+ ? { framework: backendFramework.id }
134
+ : {}),
135
+ ...(language ? { language: language.id } : {}),
136
+ ...(type === "agent" && agent ? { agent } : {}),
137
+ };
138
+ }
139
+ export function createByoServiceJson(name, stack, port, options) {
140
+ return {
141
+ $schema: SERVICE_MANIFEST_SCHEMA_URL,
142
+ schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
143
+ appName: name,
144
+ appType: BRING_YOUR_OWN_TYPE,
145
+ stack,
146
+ port,
147
+ healthCheckPath: options.healthCheckPath,
148
+ ...(options.image ? { image: options.image } : { dockerfile: options.dockerfile }),
149
+ ...(options.exposeViaProxy === false ? { exposeViaProxy: false } : {}),
150
+ ...(options.restart ? { restart: options.restart } : {}),
151
+ };
152
+ }
153
+ /**
154
+ * The Vite configs a frontend's `dev`/`build` scripts point at are written by the engine
155
+ * (Tilt loads the Starlark generator in `flisk up`), not by `flisk resource`. Without them
156
+ * `vite` stops with an esbuild "Could not resolve" stack trace, so the pre-scripts say what
157
+ * is missing instead. Inside the engine's image build the file exists and the check passes.
158
+ */
159
+ function requireGeneratedViteConfig(configPath) {
160
+ // The text sits inside single quotes in a shell command: no quotes, backticks or parentheses.
161
+ const message = `${configPath} does not exist yet. Flisk engine writes it when flisk up loads this resource ` +
162
+ "(needs Docker); flisk resource and flisk config regenerate do not create it. " +
163
+ "Run flisk up <stack> once, or point this script at your own Vite config to build on the host.";
164
+ return `bun -e 'if(!require("fs").existsSync(${JSON.stringify(configPath)})){console.error(${JSON.stringify(message)});process.exit(1)}'`;
165
+ }
166
+ export function createPackageJson(name, type, frameworkId, prismaEnabled = false, agentConfig, drizzleEnabled = false) {
167
+ const isFrontend = type === "frontend";
168
+ const framework = resolveFrontendFramework(type, frameworkId);
169
+ const backendFramework = resolveBackendFramework(type, frameworkId);
170
+ const isPostgresAgent = type === "agent" && agentConfig?.durability === "postgres";
171
+ return {
172
+ name: `@project/${name}`,
173
+ version: "0.0.1",
174
+ type: "module",
175
+ scripts: {
176
+ ...(isFrontend
177
+ ? {
178
+ predev: requireGeneratedViteConfig(`${AUTOGENERATED_DIR}/vite.config.frontend.autogenerated.ts`),
179
+ prebuild: requireGeneratedViteConfig(`${AUTOGENERATED_DIR}/vite.config.build.autogenerated.ts`),
180
+ }
181
+ : {}),
182
+ dev: isFrontend
183
+ ? `vite --config ${AUTOGENERATED_DIR}/vite.config.frontend.autogenerated.ts`
184
+ : "bun run --watch src/index.ts",
185
+ build: isFrontend
186
+ ? `tsc && vite build --config ${AUTOGENERATED_DIR}/vite.config.build.autogenerated.ts`
187
+ : "tsc",
188
+ // Backends and workers run `start` in their production image; doctor checks for it.
189
+ ...(isFrontend ? {} : { start: "bun run dist/index.js" }),
190
+ test: "vitest",
191
+ lint: "biome check .",
192
+ "lint:fix": "biome check . --write",
193
+ },
194
+ dependencies: {
195
+ ...(isFrontend ? {} : (backendFramework?.dependencies ?? { hono: "^4.0.0" })),
196
+ ...(framework?.dependencies ?? {}),
197
+ ...(prismaEnabled || isPostgresAgent
198
+ ? {
199
+ ...(prismaEnabled
200
+ ? {
201
+ prisma: "^7.5.0",
202
+ "@prisma/client": "^7.5.0",
203
+ "@prisma/adapter-pg": "^7.5.0",
204
+ }
205
+ : {}),
206
+ pg: "^8.13.0",
207
+ }
208
+ : {}),
209
+ ...(drizzleEnabled ? { "drizzle-orm": DRIZZLE_ORM_RANGE, pg: PG_RANGE } : {}),
210
+ },
211
+ devDependencies: {
212
+ "@types/bun": "^1.4.2",
213
+ // The generated Docker tsconfig sets "types": ["node"]; without this the
214
+ // image's `bun run build` fails with TS2688. ^20 matches the services that build.
215
+ "@types/node": "^20.0.0",
216
+ typescript: "^7.0.2",
217
+ vitest: "^5.0.0",
218
+ "@biomejs/biome": "^2.5.13",
219
+ // vitest 5 peers on vite ^6.4.0 || ^7 || ^8; vite 5 makes `vitest run` fail at startup with
220
+ // ERR_PACKAGE_PATH_NOT_EXPORTED ('./module-runner'), in every resource once it is hoisted.
221
+ ...(isFrontend ? { vite: "^6.4.1" } : {}),
222
+ ...(drizzleEnabled ? { "drizzle-kit": DRIZZLE_KIT_RANGE, "@types/pg": "^8.11.0" } : {}),
223
+ ...(framework?.devDependencies ?? {}),
224
+ ...(backendFramework?.devDependencies ?? {}),
225
+ },
226
+ };
227
+ }
228
+ export const PRISMA_SCHEMA_TEMPLATE = `generator client {
229
+ provider = "prisma-client"
230
+ output = "../generated/prisma"
231
+ }
232
+
233
+ datasource db {
234
+ provider = "postgresql"
235
+ }
236
+ `;
237
+ export const PRISMA_CONFIG_TEMPLATE = `export default {
238
+ schema: "prisma/schema.prisma",
239
+ datasource: {
240
+ url: process.env.DATABASE_URL,
241
+ },
242
+ };
243
+ `;
244
+ /** Version ranges for `--feature drizzle`; `drizzle-kit` must stay on the same minor as `drizzle-orm`. */
245
+ export const DRIZZLE_ORM_RANGE = "^0.44.7";
246
+ export const DRIZZLE_KIT_RANGE = "^0.31.8";
247
+ const PG_RANGE = "^8.13.0";
248
+ export const DRIZZLE_DB_TEMPLATE = `import { drizzle } from 'drizzle-orm/node-postgres';
249
+ import { Pool } from 'pg';
250
+
251
+ // DATABASE_URL is injected by Flisk. The pool connects on the first query, so a missing or
252
+ // unreachable database fails /health/db instead of stopping the service at startup.
253
+ export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
254
+ export const db = drizzle(pool);
255
+ `;
256
+ export const DRIZZLE_SCHEMA_TEMPLATE = `// Define tables here, for example with pgTable from 'drizzle-orm/pg-core'.
257
+ export {};
258
+ `;
259
+ export const DRIZZLE_CONFIG_TEMPLATE = `import { defineConfig } from 'drizzle-kit';
260
+
261
+ export default defineConfig({
262
+ dialect: 'postgresql',
263
+ schema: './src/schema.ts',
264
+ out: './drizzle',
265
+ dbCredentials: { url: process.env.DATABASE_URL ?? '' },
266
+ });
267
+ `;
268
+ export function createResourceTsconfig(resourceType, frameworkId) {
269
+ const framework = resolveFrontendFramework(resourceType, frameworkId);
270
+ const backendFramework = resolveBackendFramework(resourceType, frameworkId);
271
+ return {
272
+ ...TSCONFIG_TEMPLATE,
273
+ compilerOptions: {
274
+ ...TSCONFIG_TEMPLATE.compilerOptions,
275
+ // TypeScript 7 does not auto-include @types/*: without this, `tsc` fails with TS2591 on
276
+ // `process`/`Buffer`. "node" matches the generated Docker tsconfig. Frontends target the
277
+ // DOM and keep the default.
278
+ ...(resourceType === "frontend" ? {} : { types: ["node"] }),
279
+ ...(framework?.compilerOptions ?? {}),
280
+ ...(backendFramework?.compilerOptions ?? {}),
281
+ },
282
+ };
283
+ }
284
+ export const TSCONFIG_TEMPLATE = {
285
+ compilerOptions: {
286
+ target: "ES2022",
287
+ module: "ESNext",
288
+ moduleResolution: "bundler",
289
+ strict: true,
290
+ esModuleInterop: true,
291
+ skipLibCheck: true,
292
+ forceConsistentCasingInFileNames: true,
293
+ outDir: "./dist",
294
+ rootDir: "./src",
295
+ declaration: true,
296
+ declarationMap: true,
297
+ sourceMap: true,
298
+ },
299
+ include: ["src/**/*"],
300
+ exclude: ["node_modules", "dist"],
301
+ };
302
+ export { getBackendIndexTemplate, getDrizzleBackendIndexTemplate };
303
+ /** Resource types `flisk resource --type` accepts; `byo` is an alias of `bring-your-own`. */
304
+ export function parseResourceType(type) {
305
+ const normalized = type === "byo" ? BRING_YOUR_OWN_TYPE : type;
306
+ const validTypes = [...RESOURCE_TYPE_VALUES];
307
+ if (!validTypes.includes(normalized)) {
308
+ throw new TdkError(`Unknown resource type "${type}". Supported types: ${validTypes.join(", ")}.`, [`Use one of: ${validTypes.join(", ")}`, "Omit --type to create a backend"]);
309
+ }
310
+ return normalized;
311
+ }
312
+ /**
313
+ * Parse an existing `service.json` manifest.
314
+ *
315
+ * Wraps JSON syntax errors in a `TdkError` that names the offending file, so a malformed manifest
316
+ * is reported as a CLI error instead of an unhandled `SyntaxError`. Only the JSON syntax is
317
+ * checked here: the manifest fields themselves are not validated, and a syntactically valid file
318
+ * is returned as-is, including unknown or missing fields.
319
+ *
320
+ * @param content - The raw contents of the `service.json` file.
321
+ * @param configPath - Path to the manifest, used in the error message.
322
+ * @returns The parsed manifest.
323
+ * @throws TdkError When the content is not valid JSON.
324
+ */
325
+ export function parseExistingServiceJson(content, configPath) {
326
+ try {
327
+ return JSON.parse(content);
328
+ }
329
+ catch (error) {
330
+ const reason = error instanceof Error ? error.message : String(error);
331
+ throw new TdkError(`Could not parse service.json at ${configPath}: ${reason}`);
332
+ }
333
+ }
334
+ /**
335
+ * Prints the framework inventory for the requested resource type.
336
+ *
337
+ * Without an explicit `--type` the listing stays the historical frontend one, byte for byte, so
338
+ * `flisk resource --frameworks` keeps working exactly as before: `--language` is a backend option and
339
+ * never applies to it. `--type frontend` and `--type backend` list that type's frameworks, and the
340
+ * backend listing carries the language (and runtime) each framework is scaffolded with.
341
+ */
342
+ function printFrameworkInventory(options, isTypeExplicit) {
343
+ // No explicit `--type` means the historical frontend listing, so `--language` is not applied to it:
344
+ // it is a backend option, and the frontend listing has never carried one.
345
+ if (!isTypeExplicit) {
346
+ printFrontendFrameworks();
347
+ return;
348
+ }
349
+ const resourceType = parseResourceType(options.type ?? "backend");
350
+ if (resourceType !== "frontend" && resourceType !== "backend") {
351
+ throw new TdkError(`No frameworks are registered for --type ${resourceType}.`, [
352
+ "Use --type frontend or --type backend to list frameworks",
353
+ "Omit --type to list the frontend frameworks",
354
+ ]);
355
+ }
356
+ // `--language` belongs to backends only, so the resource command rejects it for every other type.
357
+ const language = resolveBackendLanguage(resourceType, options.language);
358
+ if (resourceType === "frontend") {
359
+ printFrontendFrameworks();
360
+ return;
361
+ }
362
+ for (const f of listBackendFrameworks(options.language)) {
363
+ // The compatibility cell names the language it is judged against, so it is never a bare
364
+ // "compatible" that could be read against a different language.
365
+ const compatibility = f.compatible
366
+ ? `compatible with ${language?.id}`
367
+ : `incompatible with --language ${language?.id}`;
368
+ console.log(`${f.id}\t${f.label}\t${f.languages.join(", ")}\t${compatibility}\t${f.command}`);
369
+ }
370
+ }
371
+ /** The historical frontend listing: id, display name, kind, verification, command. */
372
+ function printFrontendFrameworks() {
373
+ for (const f of listFrontendFrameworks()) {
374
+ console.log(`${f.id}\t${f.label}\t${f.kind}\t${f.verified ? "verified" : "unverified"}\t${f.command}`);
375
+ }
376
+ }
377
+ export const resourceCommand = addYesOption(new Command("resource"))
378
+ .description("Create a new resource (service) from scratch, or register an existing one")
379
+ .argument("[name]", "Resource name (kebab-case)")
380
+ .option("-t, --type <type>", "Resource type: backend, frontend, worker, mcp, agent, bring-your-own, sdk", "backend")
381
+ .option("--framework <id>", "Framework: frontend react (default), vue, svelte, preact, lit, solid, qwik, vanilla, tanstack-router; backend hono (default), express, elysia, fastify, nestjs, koa, h3 (list them with --frameworks)")
382
+ .option("--frameworks", "List registered frameworks for the resource type and exit (--type frontend, --type backend)")
383
+ .option("--language <id>", "Backend language: bun (default), python, go, rust, node")
384
+ .option("-s, --stack <stack>", "Stack to assign resource to", "default")
385
+ .option("-p, --path <path>", "Custom path for resource directory")
386
+ .option("--resource-path <path>", "Alias for --path (for backward compatibility)")
387
+ .option("--register-existing", "Register an existing resource without creating templates")
388
+ .option("--ddd", "Scaffold DDD (domain-driven design) folders + path aliases (Premium - requires TDK_LICENSE_KEY)")
389
+ .option("--dockerfile <path>", "Path to Dockerfile relative to resource dir (for bring-your-own)")
390
+ .option("--health-path <path>", "HTTP health check path (for bring-your-own)", "/health")
391
+ .option("--no-proxy", "Disable the Traefik route (bring-your-own only)")
392
+ .option("--restart <policy>", "Compose restart policy: no, on-failure, unless-stopped, always (bring-your-own only; use no for one-shot jobs)")
393
+ .option("--image <name>", "Docker image name instead of building from Dockerfile (for bring-your-own)")
394
+ .option("--port <port>", "Port for the resource, within its type's range (default: next free port in that range)")
395
+ .option("--feature <feature...>", "Enable a resource feature (for example: prisma, drizzle)")
396
+ .option("--runtime <id>", "Agent runtime (for agent only): custom (default)")
397
+ .option("--durability <kind>", "Where agent sessions are checkpointed (for agent only): files (default, named volume) or postgres")
398
+ .action(async (name, options, command) => {
399
+ if (options.frameworks) {
400
+ await runCommand(async () => {
401
+ // The frontend listing is the historical default; `--type` selects the listing when it is given.
402
+ printFrameworkInventory(options, command.getOptionValueSource("type") === "cli");
403
+ });
404
+ return;
405
+ }
406
+ await runCommand(async () => {
407
+ const projectRoot = requireProjectRoot();
408
+ // Support --resource-path as alias for --path
409
+ const resourcePath = options.resourcePath || options.path;
410
+ showCommandHeader("Resource Creation");
411
+ const allResources = discoverResources();
412
+ let resourceName = name;
413
+ if (!resourceName && options.yes) {
414
+ throw new TdkError("A resource name is required when using --yes.", [
415
+ "Pass the name explicitly, for example: flisk resource api --yes.",
416
+ ]);
417
+ }
418
+ if (!resourceName) {
419
+ const inputName = await promptText({
420
+ message: "Resource name (kebab-case):",
421
+ validate: (input) => {
422
+ const validation = createKebabCaseValidator("resource")(input);
423
+ return validation === true || validation;
424
+ },
425
+ });
426
+ resourceName = inputName;
427
+ }
428
+ else {
429
+ assertValid(validateResourceName(resourceName));
430
+ }
431
+ const resourceType = parseResourceType(options.type);
432
+ if (resourceType !== "agent" &&
433
+ (options.runtime !== undefined || options.durability !== undefined)) {
434
+ throw new TdkError("--runtime and --durability can only be used with --type agent.", [
435
+ "Drop --runtime and --durability, or pass --type agent",
436
+ ]);
437
+ }
438
+ // Validate the agent options before anything is written, so a bad flag leaves no half-scaffolded directory.
439
+ let agentConfig;
440
+ if (resourceType === "agent") {
441
+ resolveAgentRuntime(options.runtime);
442
+ agentConfig = {
443
+ runtime: options.runtime ?? DEFAULT_AGENT_RUNTIME,
444
+ durability: resolveAgentDurability(options.durability),
445
+ };
446
+ }
447
+ let frameworkOption = options.framework;
448
+ if (resourceType === "frontend" &&
449
+ frameworkOption === undefined &&
450
+ !options.yes &&
451
+ process.stdin.isTTY) {
452
+ frameworkOption = await promptSelect({
453
+ message: "Frontend framework:",
454
+ choices: listFrontendFrameworks()
455
+ .map((f) => ({ title: f.label, value: f.id }))
456
+ .sort((a, b) => a.value === DEFAULT_FRONTEND_FRAMEWORK
457
+ ? -1
458
+ : b.value === DEFAULT_FRONTEND_FRAMEWORK
459
+ ? 1
460
+ : 0),
461
+ });
462
+ }
463
+ const frontendFramework = resolveFrontendFramework(resourceType, frameworkOption, resourceName);
464
+ const backendLanguage = resolveBackendLanguage(resourceType, options.language);
465
+ // A backend on a language that owns its runtime (python, go, rust, node) has no framework to
466
+ // pick: `resolveBackendFramework` rejects that combination, so the prompt only offers the
467
+ // frameworks the resolver accepts.
468
+ const backendFrameworkChoices = resourceType === "backend"
469
+ ? listBackendFrameworks(options.language).filter((framework) => framework.compatible)
470
+ : [];
471
+ if (resourceType === "backend" &&
472
+ frameworkOption === undefined &&
473
+ !options.yes &&
474
+ process.stdin.isTTY &&
475
+ backendFrameworkChoices.length > 0) {
476
+ frameworkOption = await promptSelect({
477
+ message: "Backend framework:",
478
+ choices: backendFrameworkChoices
479
+ .map((f) => ({ title: f.label, value: f.id }))
480
+ .sort((a, b) => a.value === DEFAULT_BACKEND_FRAMEWORK
481
+ ? -1
482
+ : b.value === DEFAULT_BACKEND_FRAMEWORK
483
+ ? 1
484
+ : 0),
485
+ });
486
+ }
487
+ const backendFramework = resolveBackendFramework(resourceType, frameworkOption, backendLanguage);
488
+ // The id passed on to the file generators. An mcp resource has one fixed scaffold and no `framework` setting, so it passes none
489
+ // (its provider is selected by the resource type itself).
490
+ const scaffoldFrameworkId = resourceType === "mcp" || resourceType === "agent"
491
+ ? undefined
492
+ : (frontendFramework?.id ?? backendFramework?.id);
493
+ let dddEnabled = false;
494
+ if (options.ddd) {
495
+ if (resourceType !== "backend" && resourceType !== "worker") {
496
+ console.log(chalk.yellow(`\nโš ๏ธ --ddd only applies to backend/worker resources, ignoring for type "${resourceType}"`));
497
+ }
498
+ else {
499
+ const granted = await hasDddLicense(projectRoot);
500
+ if (!granted) {
501
+ throw new Error("DDD scaffolding is a Premium feature and requires a license key that grants it. " +
502
+ "Set export TDK_LICENSE_KEY=<key> (request a Premium license at https://tdk-landscape.github.io/tdk-website/#waitlist) and try again.");
503
+ }
504
+ dddEnabled = true;
505
+ }
506
+ }
507
+ assertValid(validateStackName(options.stack));
508
+ let stackName = options.stack;
509
+ if (stackName === "default") {
510
+ const existingResources = allResources;
511
+ const stackSet = new Set();
512
+ for (const r of existingResources) {
513
+ if (r.stack)
514
+ stackSet.add(r.stack);
515
+ }
516
+ const existingStacks = Array.from(stackSet);
517
+ if (existingStacks.length > 0) {
518
+ if (options.yes) {
519
+ if (existingStacks.length > 1) {
520
+ throw new TdkError("Multiple stacks exist; specify --stack when using --yes.", [
521
+ `Available stacks: ${existingStacks.join(", ")}`,
522
+ ]);
523
+ }
524
+ stackName = existingStacks[0] ?? "main";
525
+ }
526
+ else {
527
+ const selectedStack = await promptSelect({
528
+ message: "Assign to stack:",
529
+ choices: [
530
+ ...existingStacks.map((s) => ({ title: s, value: s })),
531
+ { title: "Create new stack", value: "__new__" },
532
+ ],
533
+ });
534
+ if (selectedStack === "__new__") {
535
+ const newStack = await promptText({
536
+ message: "New stack name:",
537
+ validate: (input) => {
538
+ const validation = createKebabCaseValidator("stack")(input);
539
+ return validation === true || validation;
540
+ },
541
+ });
542
+ stackName = newStack;
543
+ }
544
+ else {
545
+ stackName = selectedStack;
546
+ }
547
+ }
548
+ }
549
+ else if (options.yes) {
550
+ stackName = "main";
551
+ }
552
+ else {
553
+ const newStack = await promptText({
554
+ message: "Stack name (first resource):",
555
+ initial: "main",
556
+ validate: (input) => {
557
+ const validation = createKebabCaseValidator("stack")(input);
558
+ return validation === true || validation;
559
+ },
560
+ });
561
+ stackName = newStack;
562
+ }
563
+ }
564
+ let finalResourcePath = resourcePath;
565
+ let pathAdjustedFrom;
566
+ if (!finalResourcePath) {
567
+ const defaultPaths = {
568
+ backend: `services/${stackName}/${resourceName}`,
569
+ frontend: `apps/${resourceName}`,
570
+ worker: `workers/${resourceName}`,
571
+ mcp: `services/${stackName}/${resourceName}`,
572
+ agent: `services/${stackName}/${resourceName}`,
573
+ [BRING_YOUR_OWN_TYPE]: `services/${stackName}/${resourceName}`,
574
+ sdk: `packages/${resourceName}`,
575
+ };
576
+ finalResourcePath = defaultPaths[resourceType];
577
+ // An SDK is registered where it already lives, so its folder is never moved.
578
+ if (resourceType !== "sdk") {
579
+ const chosen = chooseResourcePath(projectRoot, finalResourcePath, stackName, resourceName);
580
+ finalResourcePath = chosen.path;
581
+ pathAdjustedFrom = chosen.adjustedFrom;
582
+ }
583
+ }
584
+ const fullPath = resolve(projectRoot, finalResourcePath);
585
+ // Prevent path traversal attacks
586
+ const relativePathResult = relative(projectRoot, fullPath);
587
+ if (relativePathResult.startsWith("..") || isAbsolute(relativePathResult)) {
588
+ errorFactories.invalidPath(fullPath).exit();
589
+ }
590
+ if (!isPathSafe(finalResourcePath)) {
591
+ errorFactories.invalidPath(finalResourcePath).exit();
592
+ }
593
+ // Check if resource already exists
594
+ const isExistingResource = existsSync(fullPath);
595
+ const hasServiceJson = existsSync(resolve(fullPath, SERVICE_JSON));
596
+ if (options.port !== undefined && resourceType === "sdk") {
597
+ throw new TdkError("--port does not apply to --type sdk.", [
598
+ "Drop --port, or use a service type",
599
+ ]);
600
+ }
601
+ if (resourceType === "sdk" && !hasServiceJson) {
602
+ throw new TdkError(`No service.json in ${finalResourcePath}`, [
603
+ 'Add one with "appType": "sdk", then run this again to register it',
604
+ ]);
605
+ }
606
+ const shouldRegisterExisting = options.registerExisting ||
607
+ resourceType === "sdk" ||
608
+ (isExistingResource && hasServiceJson);
609
+ if (isExistingResource && !shouldRegisterExisting && resourceType !== BRING_YOUR_OWN_TYPE) {
610
+ errorFactories.directoryExists(fullPath).exit();
611
+ }
612
+ const nextPort = resourceType === "sdk"
613
+ ? 0
614
+ : assignPort(resourceType, allResources);
615
+ if (options.restart !== undefined) {
616
+ if (resourceType !== BRING_YOUR_OWN_TYPE) {
617
+ throw new TdkError("--restart can only be used with --type bring-your-own.", [
618
+ "Add --type bring-your-own, or drop --restart",
619
+ ]);
620
+ }
621
+ if (!BYO_RESTART_POLICIES.includes(options.restart)) {
622
+ throw new TdkError(`Unknown --restart policy "${options.restart}".`, [
623
+ `Use one of: ${BYO_RESTART_POLICIES.join(", ")}`,
624
+ ]);
625
+ }
626
+ }
627
+ // Checked before anything is written: the Drizzle files are Bun/TypeScript files for a backend.
628
+ const requestedFeatures = Array.isArray(options.feature) ? options.feature : [];
629
+ if (requestedFeatures.includes("drizzle") &&
630
+ (resourceType !== "backend" || backendLanguage?.createFiles)) {
631
+ throw new TdkError("--feature drizzle is only supported for Bun backends.", [
632
+ "Use --type backend without --language, or drop --feature drizzle",
633
+ ]);
634
+ }
635
+ const assignedPort = resourceType === "sdk"
636
+ ? 0
637
+ : resolveResourcePort(resourceType, options.port, nextPort, allResources);
638
+ console.log(chalk.gray("\nResource details:"));
639
+ console.log(chalk.gray(` Name: ${resourceName}`));
640
+ console.log(chalk.gray(` Type: ${resourceType}`));
641
+ console.log(chalk.gray(` Stack: ${stackName}`));
642
+ console.log(chalk.gray(` Port: ${assignedPort || "N/A (SDK)"}`));
643
+ console.log(chalk.gray(` Path: ${finalResourcePath}`));
644
+ if (shouldRegisterExisting && hasServiceJson) {
645
+ console.log(chalk.yellow("\nโš ๏ธ Existing resource detected - will update service.json only"));
646
+ }
647
+ const confirmed = options.yes ||
648
+ (await confirmOrCancel(shouldRegisterExisting && hasServiceJson
649
+ ? "\nRegister existing resource?"
650
+ : "\nCreate resource?"));
651
+ if (!confirmed)
652
+ return;
653
+ // Handle existing resource registration
654
+ if (shouldRegisterExisting && hasServiceJson) {
655
+ // Read existing service.json
656
+ const { readFileSync } = await import("node:fs");
657
+ const existingServiceJsonPath = resolve(fullPath, SERVICE_JSON);
658
+ const existingContent = readFileSync(existingServiceJsonPath, "utf-8");
659
+ const existingServiceJson = parseExistingServiceJson(existingContent, existingServiceJsonPath);
660
+ // Update with new values while preserving existing fields
661
+ const updatedServiceJson = {
662
+ ...existingServiceJson,
663
+ $schema: existingServiceJson.$schema ?? SERVICE_MANIFEST_SCHEMA_URL,
664
+ schemaVersion: existingServiceJson.schemaVersion ?? SERVICE_MANIFEST_SCHEMA_VERSION,
665
+ appName: resourceName,
666
+ appType: resourceType === "sdk" ? "sdk" : existingServiceJson.appType || resourceType,
667
+ stack: stackName,
668
+ ...(assignedPort > 0 && { port: assignedPort }),
669
+ };
670
+ // Write updated service.json
671
+ const { writeFileSync } = await import("node:fs");
672
+ writeFileSync(existingServiceJsonPath, JSON.stringify(updatedServiceJson, null, 2));
673
+ clearDiscoveryCache();
674
+ console.log(chalk.green("\nโœ… Existing resource registered successfully!"));
675
+ console.log(chalk.gray(`\nLocation: ${fullPath}`));
676
+ console.log(chalk.gray(`\nNext steps:`));
677
+ console.log(chalk.gray(` flisk up ${stackName}`));
678
+ return;
679
+ }
680
+ // Handle bring-your-own type
681
+ if (resourceType === BRING_YOUR_OWN_TYPE) {
682
+ console.log(chalk.blue("\n๐Ÿ“ Creating bring-your-own resource..."));
683
+ // Parse port option
684
+ const port = assignedPort;
685
+ const dockerfile = options.dockerfile || "./Dockerfile";
686
+ const dockerfilePath = resolve(fullPath, dockerfile);
687
+ const dockerfileRelativePath = relative(fullPath, dockerfilePath);
688
+ if (isAbsolute(dockerfileRelativePath) ||
689
+ dockerfileRelativePath === ".." ||
690
+ dockerfileRelativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
691
+ errorFactories.invalidPath(dockerfile).exit();
692
+ }
693
+ mkdirSync(fullPath, { recursive: true });
694
+ // Create service.json
695
+ const byoServiceJson = createByoServiceJson(resourceName, stackName, port, {
696
+ healthCheckPath: options.healthPath || "/health",
697
+ dockerfile,
698
+ ...(options.image ? { image: options.image } : {}),
699
+ ...(options.proxy === false ? { exposeViaProxy: false } : {}),
700
+ ...(options.restart ? { restart: options.restart } : {}),
701
+ });
702
+ const { writeFileSync } = await import("node:fs");
703
+ writeFileSync(resolve(fullPath, SERVICE_JSON), JSON.stringify(byoServiceJson, null, 2));
704
+ // Create Dockerfile stub if no image provided and dockerfile doesn't exist
705
+ if (!options.image && !existsSync(dockerfilePath)) {
706
+ mkdirSync(resolve(dockerfilePath, ".."), { recursive: true });
707
+ const healthConfigPath = resolve(dockerfilePath, "..", "health.conf");
708
+ const healthConfigRelativePath = relative(fullPath, healthConfigPath).replace(/\\/g, "/");
709
+ const dockerfileContent = `FROM nginx:1.27-alpine
710
+ RUN apk add --no-cache curl
711
+ COPY ${healthConfigRelativePath} /etc/nginx/conf.d/default.conf
712
+ EXPOSE ${port}
713
+ `;
714
+ writeFileSync(dockerfilePath, dockerfileContent);
715
+ const healthConfContent = `server {
716
+ listen ${port};
717
+ location /health {
718
+ default_type text/plain;
719
+ return 200 'ok';
720
+ }
721
+ location / {
722
+ default_type text/plain;
723
+ return 200 'byo';
724
+ }
725
+ }
726
+ `;
727
+ writeFileSync(healthConfigPath, healthConfContent);
728
+ }
729
+ // Create AGENTS.md
730
+ const agentsMdContent = `# AGENTS.md
731
+
732
+ DO NOT DELETE service.json
733
+
734
+ This file contains the resource configuration for Flisk.
735
+ `;
736
+ const agentsPath = resolve(fullPath, "AGENTS.md");
737
+ if (!existsSync(agentsPath))
738
+ writeFileSync(agentsPath, agentsMdContent);
739
+ clearDiscoveryCache();
740
+ console.log(chalk.green("\nโœ… Bring-your-own resource created successfully!"));
741
+ console.log(chalk.gray(`\nLocation: ${fullPath}`));
742
+ console.log(chalk.gray(`\nNext steps:`));
743
+ console.log(chalk.gray(` flisk up ${stackName}`));
744
+ return;
745
+ }
746
+ // Create directory structure for new resources
747
+ console.log(chalk.blue("\n๐Ÿ“ Creating directory structure..."));
748
+ mkdirSync(fullPath, { recursive: true });
749
+ // A provider that owns its runtime files only gets the folders it writes into (Go keeps its files at the root).
750
+ const ownedTopLevel = new Set((backendLanguage?.createFiles?.(resourceName) ?? []).map((file) => file.filename.split("/")[0]));
751
+ for (const folder of ["src", "tests"]) {
752
+ if (!backendLanguage?.createFiles || ownedTopLevel.has(folder)) {
753
+ mkdirSync(resolve(fullPath, folder), { recursive: true });
754
+ }
755
+ }
756
+ if (resourceType === "frontend") {
757
+ mkdirSync(resolve(fullPath, "public"), { recursive: true });
758
+ }
759
+ if (dddEnabled) {
760
+ for (const layer of ["domain", "application", "infrastructure", "presentation"]) {
761
+ mkdirSync(resolve(fullPath, "src", layer), { recursive: true });
762
+ }
763
+ }
764
+ // Prepare file generation tasks
765
+ const prismaEnabled = requestedFeatures.includes("prisma");
766
+ const drizzleEnabled = requestedFeatures.includes("drizzle");
767
+ const serviceJson = createServiceJson(resourceName, resourceType, stackName, assignedPort, [...(dddEnabled ? ["ddd"] : []), ...requestedFeatures], scaffoldFrameworkId, options.language === undefined ? undefined : backendLanguage?.id, agentConfig);
768
+ const packageJson = createPackageJson(resourceName, resourceType, scaffoldFrameworkId, prismaEnabled, agentConfig, drizzleEnabled);
769
+ if (prismaEnabled && resourceType === "backend") {
770
+ serviceJson.dependsOn = [`${resourceName}-migrator`];
771
+ }
772
+ const tasks = [
773
+ {
774
+ type: "json",
775
+ filename: SERVICE_JSON,
776
+ content: serviceJson,
777
+ description: "Generating service.json",
778
+ emoji: "๐Ÿ“",
779
+ },
780
+ {
781
+ type: "json",
782
+ filename: PACKAGE_JSON,
783
+ content: packageJson,
784
+ description: "Generating package.json",
785
+ emoji: "๐Ÿ“ฆ",
786
+ },
787
+ {
788
+ type: "json",
789
+ filename: "tsconfig.json",
790
+ content: createResourceTsconfig(resourceType, scaffoldFrameworkId),
791
+ description: "Generating tsconfig.json",
792
+ emoji: "โš™๏ธ",
793
+ },
794
+ {
795
+ type: "text",
796
+ filename: "Dockerfile",
797
+ content: getDockerfileTemplate(serviceJson.port, typeof serviceJson.healthCheckPath === "string"
798
+ ? serviceJson.healthCheckPath
799
+ : undefined),
800
+ description: "Generating Dockerfile",
801
+ emoji: "๐Ÿณ",
802
+ },
803
+ ];
804
+ // Add source files based on resource type
805
+ if (resourceType === "backend" || resourceType === "mcp" || resourceType === "agent") {
806
+ // The /health/db route is only written into the Bun + Hono template (the default framework).
807
+ const drizzleRouteTemplate = drizzleEnabled &&
808
+ resourceType === "backend" &&
809
+ (backendFramework?.id ?? "hono") === "hono"
810
+ ? getDrizzleBackendIndexTemplate
811
+ : undefined;
812
+ const indexContent = resourceType === "agent"
813
+ ? (backendFramework?.createIndex ?? getBackendIndexTemplate)(resourceName, agentConfig?.durability)
814
+ : drizzleRouteTemplate
815
+ ? drizzleRouteTemplate(resourceName)
816
+ : (backendFramework?.createIndex ?? getBackendIndexTemplate)(resourceName);
817
+ tasks.push({
818
+ type: "text",
819
+ filename: "src/index.ts",
820
+ content: indexContent,
821
+ description: resourceType === "mcp"
822
+ ? "Generating MCP server source"
823
+ : resourceType === "agent"
824
+ ? "Generating agent source"
825
+ : "Generating backend source",
826
+ emoji: "๐Ÿ’ป",
827
+ });
828
+ }
829
+ else if (resourceType === "frontend") {
830
+ tasks.push(...(frontendFramework?.createFiles(resourceName).map((file) => ({
831
+ ...file,
832
+ type: "text",
833
+ })) ?? []));
834
+ // The shared frontend Docker builder unconditionally runs `COPY <resource>/public`
835
+ // (the Vite static-assets folder), and an empty folder can't be committed, so
836
+ // without a placeholder the image build fails for every framework.
837
+ tasks.push({
838
+ type: "text",
839
+ filename: "public/.gitkeep",
840
+ content: "",
841
+ description: "Generating public/ assets folder",
842
+ emoji: "๐Ÿ“",
843
+ });
844
+ }
845
+ else if (resourceType === "worker") {
846
+ tasks.push({
847
+ type: "text",
848
+ filename: "src/index.ts",
849
+ content: getWorkerIndexTemplate(resourceName),
850
+ description: "Generating worker source",
851
+ emoji: "๐Ÿ’ป",
852
+ });
853
+ }
854
+ // Add test file
855
+ tasks.push({
856
+ type: "text",
857
+ filename: `tests/${resourceName}.test.ts`,
858
+ content: getTestTemplate(resourceName),
859
+ description: "Generating test file",
860
+ emoji: "๐Ÿงช",
861
+ });
862
+ if (drizzleEnabled) {
863
+ tasks.push({
864
+ type: "text",
865
+ filename: "src/db.ts",
866
+ content: DRIZZLE_DB_TEMPLATE,
867
+ description: "Generating Drizzle client",
868
+ emoji: "๐Ÿ—„๏ธ",
869
+ }, {
870
+ type: "text",
871
+ filename: "src/schema.ts",
872
+ content: DRIZZLE_SCHEMA_TEMPLATE,
873
+ description: "Generating Drizzle schema",
874
+ emoji: "๐Ÿงฌ",
875
+ }, {
876
+ type: "text",
877
+ filename: "drizzle.config.ts",
878
+ content: DRIZZLE_CONFIG_TEMPLATE,
879
+ description: "Generating Drizzle config",
880
+ emoji: "โš™๏ธ",
881
+ });
882
+ }
883
+ // A language provider that owns its runtime files replaces the shared Bun/TypeScript
884
+ // scaffold (package.json, tsconfig, source, test, and the Dockerfile, which the engine generates); service.json stays shared.
885
+ const languageFiles = backendLanguage?.createFiles?.(resourceName);
886
+ if (languageFiles) {
887
+ const owned = new Set(languageFiles.map((file) => file.filename));
888
+ const sharedTasks = tasks.filter((task) => task.filename === SERVICE_JSON ||
889
+ !(owned.has(task.filename) ||
890
+ task.filename === "Dockerfile" ||
891
+ task.filename === "src/index.ts" ||
892
+ task.filename === PACKAGE_JSON ||
893
+ task.filename === "tsconfig.json" ||
894
+ task.filename.endsWith(".test.ts")));
895
+ tasks.length = 0;
896
+ tasks.push(...sharedTasks, ...languageFiles.map((file) => ({ ...file, type: "text" })));
897
+ }
898
+ // Execute all file writes with progress
899
+ console.log(chalk.blue("๐Ÿ’ป Generating source files..."));
900
+ writeFilesWithProgress(fullPath, tasks);
901
+ if (prismaEnabled && resourceType === "backend") {
902
+ mkdirSync(resolve(fullPath, "prisma"), { recursive: true });
903
+ const migratorName = `${resourceName}-migrator`;
904
+ const migratorPath = resolve(projectRoot, `services/${stackName}/${migratorName}`);
905
+ mkdirSync(resolve(migratorPath, "prisma"), { recursive: true });
906
+ const migratorConfig = {
907
+ ...JSON.parse(JSON.stringify(BASE_TEMPLATE)),
908
+ $schema: SERVICE_MANIFEST_SCHEMA_URL,
909
+ schemaVersion: SERVICE_MANIFEST_SCHEMA_VERSION,
910
+ appName: migratorName,
911
+ appType: "migrator",
912
+ name: migratorName,
913
+ type: "migrator",
914
+ stack: stackName,
915
+ featuresEnabled: ["prisma"],
916
+ };
917
+ migratorConfig.dependsOn = ["postgres"];
918
+ // A migrator serves no route, so it declares no port. The base template's placeholder 0 would fail the service port check in flisk up.
919
+ delete migratorConfig.port;
920
+ const migratorPackage = createPackageJson(migratorName, "migrator", undefined, true);
921
+ migratorPackage.scripts = {
922
+ ...migratorPackage.scripts,
923
+ start: "prisma migrate deploy",
924
+ };
925
+ writeFilesWithProgress(migratorPath, [
926
+ {
927
+ type: "json",
928
+ filename: SERVICE_JSON,
929
+ content: migratorConfig,
930
+ description: "Generating Prisma migrator manifest",
931
+ emoji: "๐Ÿ—ƒ๏ธ",
932
+ },
933
+ {
934
+ type: "json",
935
+ filename: PACKAGE_JSON,
936
+ content: migratorPackage,
937
+ description: "Generating Prisma migrator package",
938
+ emoji: "๐Ÿ“ฆ",
939
+ },
940
+ {
941
+ type: "text",
942
+ filename: "prisma/schema.prisma",
943
+ content: PRISMA_SCHEMA_TEMPLATE,
944
+ description: "Generating Prisma schema",
945
+ emoji: "๐Ÿงฌ",
946
+ },
947
+ {
948
+ type: "text",
949
+ filename: "prisma.config.ts",
950
+ content: PRISMA_CONFIG_TEMPLATE,
951
+ description: "Generating Prisma config",
952
+ emoji: "โš™๏ธ",
953
+ },
954
+ ]);
955
+ writeFilesWithProgress(fullPath, [
956
+ {
957
+ type: "text",
958
+ filename: "prisma/schema.prisma",
959
+ content: PRISMA_SCHEMA_TEMPLATE,
960
+ description: "Generating Prisma schema",
961
+ emoji: "๐Ÿงฌ",
962
+ },
963
+ {
964
+ type: "text",
965
+ filename: "prisma.config.ts",
966
+ content: PRISMA_CONFIG_TEMPLATE,
967
+ description: "Generating Prisma config",
968
+ emoji: "โš™๏ธ",
969
+ },
970
+ ]);
971
+ }
972
+ clearDiscoveryCache();
973
+ console.log(chalk.green("\nโœ… Resource created successfully!"));
974
+ console.log(chalk.gray(`\nLocation: ${fullPath}`));
975
+ const discoveryPaths = readDiscoveryPaths(projectRoot);
976
+ if (pathAdjustedFrom) {
977
+ console.log(chalk.yellow(`\n๐Ÿ“ Placed in ${finalResourcePath} instead of the usual ${pathAdjustedFrom}: ` +
978
+ `discovery.paths in .tdk/project.json is ${JSON.stringify(discoveryPaths)}, ` +
979
+ `so ${pathAdjustedFrom} would never be started by \`flisk up\`.`));
980
+ console.log(chalk.gray(" Use --path to choose another folder."));
981
+ }
982
+ else if (!isPathDiscovered(projectRoot, finalResourcePath, discoveryPaths)) {
983
+ console.log(chalk.yellow(`\nโš ๏ธ ${finalResourcePath} is outside discovery.paths ${JSON.stringify(discoveryPaths)}, ` +
984
+ "so `flisk up` will not start this resource."));
985
+ console.log(chalk.gray(" Add a matching pattern to discovery.paths in .tdk/project.json and run `flisk config regenerate`."));
986
+ }
987
+ console.log(chalk.gray(`\nNext steps:`));
988
+ console.log(chalk.gray(` cd ${finalResourcePath}`));
989
+ console.log(chalk.gray(` ${backendLanguage?.installHint ?? "bun install"}`));
990
+ console.log(chalk.gray(` flisk up ${stackName}`));
991
+ });
992
+ });