@korajs/cli 0.0.0-canary-20261008174129

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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/bin.cjs +9465 -0
  4. package/dist/bin.cjs.map +1 -0
  5. package/dist/bin.js +3150 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/chunk-5NI2FEQL.js +92 -0
  8. package/dist/chunk-5NI2FEQL.js.map +1 -0
  9. package/dist/chunk-6C4BHSRA.js +82 -0
  10. package/dist/chunk-6C4BHSRA.js.map +1 -0
  11. package/dist/chunk-7Y4J2GXP.js +796 -0
  12. package/dist/chunk-7Y4J2GXP.js.map +1 -0
  13. package/dist/chunk-BURFQVMV.js +719 -0
  14. package/dist/chunk-BURFQVMV.js.map +1 -0
  15. package/dist/chunk-TVIRMDER.js +2569 -0
  16. package/dist/chunk-TVIRMDER.js.map +1 -0
  17. package/dist/create.cjs +1368 -0
  18. package/dist/create.cjs.map +1 -0
  19. package/dist/create.js +11 -0
  20. package/dist/create.js.map +1 -0
  21. package/dist/index.cjs +3489 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +772 -0
  24. package/dist/index.d.ts +772 -0
  25. package/dist/index.js +129 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lab-manager-KUDII6BT.js +280 -0
  28. package/dist/lab-manager-KUDII6BT.js.map +1 -0
  29. package/dist/schema-loader-GGHECMTM.js +9 -0
  30. package/dist/schema-loader-GGHECMTM.js.map +1 -0
  31. package/dist/spectator-manager-FTH2NFSE.js +145 -0
  32. package/dist/spectator-manager-FTH2NFSE.js.map +1 -0
  33. package/dist/studio-server-XLGZNGG5.js +1747 -0
  34. package/dist/studio-server-XLGZNGG5.js.map +1 -0
  35. package/dist/vite.cjs +351 -0
  36. package/dist/vite.cjs.map +1 -0
  37. package/dist/vite.d.cts +161 -0
  38. package/dist/vite.d.ts +161 -0
  39. package/dist/vite.js +321 -0
  40. package/dist/vite.js.map +1 -0
  41. package/package.json +79 -0
  42. package/templates/react-basic/.env.example +3 -0
  43. package/templates/react-basic/AGENTS.md +87 -0
  44. package/templates/react-basic/README.md.hbs +62 -0
  45. package/templates/react-basic/index.html.hbs +12 -0
  46. package/templates/react-basic/kora.config.ts +13 -0
  47. package/templates/react-basic/package.json.hbs +35 -0
  48. package/templates/react-basic/src/App.tsx +132 -0
  49. package/templates/react-basic/src/index.css +287 -0
  50. package/templates/react-basic/src/kora-worker.ts +9 -0
  51. package/templates/react-basic/src/main.tsx +24 -0
  52. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  53. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  54. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  55. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  56. package/templates/react-basic/src/schema.ts +9 -0
  57. package/templates/react-basic/src/vite-env.d.ts +18 -0
  58. package/templates/react-basic/tsconfig.json.hbs +14 -0
  59. package/templates/react-basic/vite.config.ts +88 -0
  60. package/templates/react-sync/.env.example +25 -0
  61. package/templates/react-sync/AGENTS.md +87 -0
  62. package/templates/react-sync/README.md.hbs +106 -0
  63. package/templates/react-sync/index.html.hbs +12 -0
  64. package/templates/react-sync/kora.config.ts +17 -0
  65. package/templates/react-sync/package.json.hbs +44 -0
  66. package/templates/react-sync/server.ts +141 -0
  67. package/templates/react-sync/src/App.tsx +160 -0
  68. package/templates/react-sync/src/auth.ts +25 -0
  69. package/templates/react-sync/src/index.css +350 -0
  70. package/templates/react-sync/src/kora-worker.ts +9 -0
  71. package/templates/react-sync/src/main.tsx +52 -0
  72. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-sync/src/schema.ts +9 -0
  77. package/templates/react-sync/src/vite-env.d.ts +19 -0
  78. package/templates/react-sync/tsconfig.json.hbs +14 -0
  79. package/templates/react-sync/vite.config.ts +105 -0
  80. package/templates/react-tailwind/.env.example +3 -0
  81. package/templates/react-tailwind/AGENTS.md +87 -0
  82. package/templates/react-tailwind/README.md.hbs +62 -0
  83. package/templates/react-tailwind/index.html.hbs +12 -0
  84. package/templates/react-tailwind/kora.config.ts +13 -0
  85. package/templates/react-tailwind/package.json.hbs +38 -0
  86. package/templates/react-tailwind/src/App.tsx +201 -0
  87. package/templates/react-tailwind/src/index.css +7 -0
  88. package/templates/react-tailwind/src/kora-worker.ts +9 -0
  89. package/templates/react-tailwind/src/main.tsx +31 -0
  90. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  94. package/templates/react-tailwind/src/schema.ts +9 -0
  95. package/templates/react-tailwind/src/vite-env.d.ts +18 -0
  96. package/templates/react-tailwind/tsconfig.json.hbs +14 -0
  97. package/templates/react-tailwind/vite.config.ts +95 -0
  98. package/templates/react-tailwind-sync/.env.example +25 -0
  99. package/templates/react-tailwind-sync/AGENTS.md +89 -0
  100. package/templates/react-tailwind-sync/README.md.hbs +106 -0
  101. package/templates/react-tailwind-sync/index.html.hbs +12 -0
  102. package/templates/react-tailwind-sync/kora.config.ts +17 -0
  103. package/templates/react-tailwind-sync/package.json.hbs +47 -0
  104. package/templates/react-tailwind-sync/server.ts +141 -0
  105. package/templates/react-tailwind-sync/src/App.tsx +293 -0
  106. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  107. package/templates/react-tailwind-sync/src/index.css +7 -0
  108. package/templates/react-tailwind-sync/src/kora-worker.ts +9 -0
  109. package/templates/react-tailwind-sync/src/kora.ts +32 -0
  110. package/templates/react-tailwind-sync/src/main.tsx +44 -0
  111. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +21 -0
  112. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  113. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  114. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  115. package/templates/react-tailwind-sync/src/schema.ts +9 -0
  116. package/templates/react-tailwind-sync/src/vite-env.d.ts +19 -0
  117. package/templates/react-tailwind-sync/tsconfig.json.hbs +14 -0
  118. package/templates/react-tailwind-sync/vite.config.ts +112 -0
  119. package/templates/svelte-basic/.env.example +3 -0
  120. package/templates/svelte-basic/AGENTS.md +76 -0
  121. package/templates/svelte-basic/README.md.hbs +62 -0
  122. package/templates/svelte-basic/index.html.hbs +12 -0
  123. package/templates/svelte-basic/kora.config.ts +12 -0
  124. package/templates/svelte-basic/package.json.hbs +33 -0
  125. package/templates/svelte-basic/src/App.svelte +128 -0
  126. package/templates/svelte-basic/src/Root.svelte +14 -0
  127. package/templates/svelte-basic/src/index.css +287 -0
  128. package/templates/svelte-basic/src/kora-worker.ts +9 -0
  129. package/templates/svelte-basic/src/main.ts +16 -0
  130. package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
  131. package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
  132. package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
  133. package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
  134. package/templates/svelte-basic/src/schema.ts +9 -0
  135. package/templates/svelte-basic/src/vite-env.d.ts +12 -0
  136. package/templates/svelte-basic/svelte.config.js +5 -0
  137. package/templates/svelte-basic/tsconfig.json.hbs +14 -0
  138. package/templates/svelte-basic/vite.config.ts +57 -0
  139. package/templates/svelte-sync/.env.example +25 -0
  140. package/templates/svelte-sync/AGENTS.md +76 -0
  141. package/templates/svelte-sync/README.md.hbs +106 -0
  142. package/templates/svelte-sync/index.html.hbs +12 -0
  143. package/templates/svelte-sync/kora.config.ts +17 -0
  144. package/templates/svelte-sync/package.json.hbs +43 -0
  145. package/templates/svelte-sync/server.ts +141 -0
  146. package/templates/svelte-sync/src/App.svelte +151 -0
  147. package/templates/svelte-sync/src/Root.svelte +22 -0
  148. package/templates/svelte-sync/src/auth.ts +25 -0
  149. package/templates/svelte-sync/src/index.css +339 -0
  150. package/templates/svelte-sync/src/kora-worker.ts +9 -0
  151. package/templates/svelte-sync/src/main.ts +32 -0
  152. package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
  153. package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
  154. package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
  155. package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
  156. package/templates/svelte-sync/src/schema.ts +9 -0
  157. package/templates/svelte-sync/src/vite-env.d.ts +22 -0
  158. package/templates/svelte-sync/svelte.config.js +5 -0
  159. package/templates/svelte-sync/tsconfig.json.hbs +14 -0
  160. package/templates/svelte-sync/vite.config.ts +91 -0
  161. package/templates/svelte-tailwind/AGENTS.md +76 -0
  162. package/templates/svelte-tailwind/README.md.hbs +62 -0
  163. package/templates/svelte-tailwind/index.html.hbs +12 -0
  164. package/templates/svelte-tailwind/kora.config.ts +12 -0
  165. package/templates/svelte-tailwind/package.json.hbs +36 -0
  166. package/templates/svelte-tailwind/src/App.svelte +76 -0
  167. package/templates/svelte-tailwind/src/Root.svelte +14 -0
  168. package/templates/svelte-tailwind/src/index.css +7 -0
  169. package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
  170. package/templates/svelte-tailwind/src/main.ts +16 -0
  171. package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  172. package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
  173. package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
  174. package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
  175. package/templates/svelte-tailwind/src/schema.ts +9 -0
  176. package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
  177. package/templates/svelte-tailwind/svelte.config.js +5 -0
  178. package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
  179. package/templates/svelte-tailwind/vite.config.ts +64 -0
  180. package/templates/svelte-tailwind-sync/.env.example +25 -0
  181. package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
  182. package/templates/svelte-tailwind-sync/README.md.hbs +106 -0
  183. package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
  184. package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
  185. package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
  186. package/templates/svelte-tailwind-sync/server.ts +141 -0
  187. package/templates/svelte-tailwind-sync/src/App.svelte +193 -0
  188. package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
  189. package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
  190. package/templates/svelte-tailwind-sync/src/index.css +7 -0
  191. package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
  192. package/templates/svelte-tailwind-sync/src/main.ts +32 -0
  193. package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  194. package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  195. package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  196. package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
  197. package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
  198. package/templates/svelte-tailwind-sync/src/vite-env.d.ts +22 -0
  199. package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
  200. package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
  201. package/templates/svelte-tailwind-sync/vite.config.ts +98 -0
  202. package/templates/tauri-react/.env.example +27 -0
  203. package/templates/tauri-react/.github/workflows/release-desktop.yml +107 -0
  204. package/templates/tauri-react/AGENTS.md +91 -0
  205. package/templates/tauri-react/README.md.hbs +183 -0
  206. package/templates/tauri-react/dev.ts +37 -0
  207. package/templates/tauri-react/index.html.hbs +16 -0
  208. package/templates/tauri-react/kora.config.ts +17 -0
  209. package/templates/tauri-react/package.json.hbs +46 -0
  210. package/templates/tauri-react/server.ts +148 -0
  211. package/templates/tauri-react/src/App.tsx +803 -0
  212. package/templates/tauri-react/src/AppShell.tsx +158 -0
  213. package/templates/tauri-react/src/SetupScreen.tsx +288 -0
  214. package/templates/tauri-react/src/auth.ts +45 -0
  215. package/templates/tauri-react/src/main.tsx +9 -0
  216. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  217. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  218. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  219. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  220. package/templates/tauri-react/src/schema.ts +9 -0
  221. package/templates/tauri-react/src/sync-config.ts +103 -0
  222. package/templates/tauri-react/src/updater.ts +38 -0
  223. package/templates/tauri-react/src/vite-env.d.ts +19 -0
  224. package/templates/tauri-react/src-tauri/Cargo.toml +19 -0
  225. package/templates/tauri-react/src-tauri/build.rs +3 -0
  226. package/templates/tauri-react/src-tauri/capabilities/default.json +12 -0
  227. package/templates/tauri-react/src-tauri/icons/128x128.png +0 -0
  228. package/templates/tauri-react/src-tauri/icons/128x128@2x.png +0 -0
  229. package/templates/tauri-react/src-tauri/icons/32x32.png +0 -0
  230. package/templates/tauri-react/src-tauri/icons/icon.icns +0 -0
  231. package/templates/tauri-react/src-tauri/icons/icon.ico +0 -0
  232. package/templates/tauri-react/src-tauri/src/lib.rs +8 -0
  233. package/templates/tauri-react/src-tauri/src/main.rs +6 -0
  234. package/templates/tauri-react/src-tauri/tauri.conf.json +44 -0
  235. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  236. package/templates/tauri-react/vite.config.ts +15 -0
  237. package/templates/vue-basic/AGENTS.md +84 -0
  238. package/templates/vue-basic/README.md.hbs +62 -0
  239. package/templates/vue-basic/index.html.hbs +12 -0
  240. package/templates/vue-basic/kora.config.ts +12 -0
  241. package/templates/vue-basic/package.json.hbs +32 -0
  242. package/templates/vue-basic/src/App.vue +117 -0
  243. package/templates/vue-basic/src/index.css +287 -0
  244. package/templates/vue-basic/src/kora-worker.ts +9 -0
  245. package/templates/vue-basic/src/main.ts +20 -0
  246. package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
  247. package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
  248. package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
  249. package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
  250. package/templates/vue-basic/src/schema.ts +9 -0
  251. package/templates/vue-basic/src/vite-env.d.ts +17 -0
  252. package/templates/vue-basic/tsconfig.json.hbs +15 -0
  253. package/templates/vue-basic/vite.config.ts +74 -0
  254. package/templates/vue-sync/.env.example +25 -0
  255. package/templates/vue-sync/AGENTS.md +84 -0
  256. package/templates/vue-sync/README.md.hbs +106 -0
  257. package/templates/vue-sync/index.html.hbs +12 -0
  258. package/templates/vue-sync/kora.config.ts +17 -0
  259. package/templates/vue-sync/package.json.hbs +42 -0
  260. package/templates/vue-sync/server.ts +141 -0
  261. package/templates/vue-sync/src/App.vue +135 -0
  262. package/templates/vue-sync/src/auth.ts +25 -0
  263. package/templates/vue-sync/src/index.css +339 -0
  264. package/templates/vue-sync/src/kora-worker.ts +9 -0
  265. package/templates/vue-sync/src/main.ts +47 -0
  266. package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
  267. package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
  268. package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
  269. package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
  270. package/templates/vue-sync/src/schema.ts +9 -0
  271. package/templates/vue-sync/src/vite-env.d.ts +27 -0
  272. package/templates/vue-sync/tsconfig.json.hbs +15 -0
  273. package/templates/vue-sync/vite.config.ts +91 -0
  274. package/templates/vue-tailwind/AGENTS.md +84 -0
  275. package/templates/vue-tailwind/README.md.hbs +62 -0
  276. package/templates/vue-tailwind/index.html.hbs +12 -0
  277. package/templates/vue-tailwind/kora.config.ts +12 -0
  278. package/templates/vue-tailwind/package.json.hbs +35 -0
  279. package/templates/vue-tailwind/src/App.vue +135 -0
  280. package/templates/vue-tailwind/src/index.css +7 -0
  281. package/templates/vue-tailwind/src/kora-worker.ts +9 -0
  282. package/templates/vue-tailwind/src/main.ts +31 -0
  283. package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  284. package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
  285. package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
  286. package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
  287. package/templates/vue-tailwind/src/schema.ts +9 -0
  288. package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
  289. package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
  290. package/templates/vue-tailwind/vite.config.ts +75 -0
  291. package/templates/vue-tailwind-sync/.env.example +25 -0
  292. package/templates/vue-tailwind-sync/AGENTS.md +84 -0
  293. package/templates/vue-tailwind-sync/README.md.hbs +106 -0
  294. package/templates/vue-tailwind-sync/index.html.hbs +12 -0
  295. package/templates/vue-tailwind-sync/kora.config.ts +17 -0
  296. package/templates/vue-tailwind-sync/package.json.hbs +45 -0
  297. package/templates/vue-tailwind-sync/server.ts +141 -0
  298. package/templates/vue-tailwind-sync/src/App.vue +245 -0
  299. package/templates/vue-tailwind-sync/src/auth.ts +25 -0
  300. package/templates/vue-tailwind-sync/src/index.css +7 -0
  301. package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
  302. package/templates/vue-tailwind-sync/src/main.ts +56 -0
  303. package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  304. package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  305. package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  306. package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
  307. package/templates/vue-tailwind-sync/src/schema.ts +9 -0
  308. package/templates/vue-tailwind-sync/src/vite-env.d.ts +27 -0
  309. package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
  310. package/templates/vue-tailwind-sync/vite.config.ts +92 -0
@@ -0,0 +1,2569 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DeployPlatformUnavailableError,
4
+ InvalidProjectError,
5
+ createLogger,
6
+ createPromptClient
7
+ } from "./chunk-7Y4J2GXP.js";
8
+ import {
9
+ findProjectRoot,
10
+ resolveProjectBinaryEntryPoint
11
+ } from "./chunk-5NI2FEQL.js";
12
+
13
+ // src/commands/agents/agents-md-content.ts
14
+ var AGENTS_FRAMEWORKS = ["react", "vue", "svelte"];
15
+ function isAgentsFramework(value) {
16
+ return AGENTS_FRAMEWORKS.includes(value);
17
+ }
18
+ function detectFrameworkFromManifest(manifest) {
19
+ if (typeof manifest !== "object" || manifest === null) return null;
20
+ const record = manifest;
21
+ const deps = collectDependencyNames(record.dependencies, record.devDependencies);
22
+ if (deps.has("@korajs/react")) return "react";
23
+ if (deps.has("@korajs/vue")) return "vue";
24
+ if (deps.has("@korajs/svelte")) return "svelte";
25
+ return null;
26
+ }
27
+ function collectDependencyNames(...groups) {
28
+ const names = /* @__PURE__ */ new Set();
29
+ for (const group of groups) {
30
+ if (typeof group !== "object" || group === null) continue;
31
+ for (const name of Object.keys(group)) {
32
+ names.add(name);
33
+ }
34
+ }
35
+ return names;
36
+ }
37
+ var FRAMEWORK_BINDINGS = {
38
+ react: `## React bindings
39
+
40
+ \`\`\`tsx
41
+ import { KoraProvider, useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/react'
42
+
43
+ // Wrap the tree once near your app entry: <KoraProvider app={app}><App /></KoraProvider>
44
+
45
+ const todos = useCollection('todos')
46
+ const rows = useQuery(todos.where({ completed: false }).orderBy('createdAt'))
47
+ const addTodo = useMutation((data) => todos.insert(data))
48
+ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
49
+ await addTodo.mutateAsync({ title: 'x' }) // resolves with the result; throws on failure
50
+ const status = useSyncStatus() // status.status, status.pendingOperations
51
+ \`\`\`
52
+
53
+ Rules: always render \`mutation.error\` somewhere. Keep \`<StrictMode>\` in place; the hooks are StrictMode-safe. \`useQuery\` reads the local store: no network, no spinner. Its very first render returns \`[]\` before the local query has run; use \`useQueryState\` when you must tell "loading" from "no rows".`,
54
+ vue: `## Vue bindings
55
+
56
+ \`\`\`ts
57
+ import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/vue'
58
+
59
+ const todos = useCollection('todos')
60
+ const rows = useQuery(todos.where({ completed: false }).orderBy('createdAt')) // readonly reactive ref
61
+ const addTodo = useMutation((data) => todos.insert(data))
62
+ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
63
+ const status = useSyncStatus() // status.status, status.pendingOperations
64
+ \`\`\`
65
+
66
+ Rules: surface the mutation's error state in the UI. \`useQuery\` reads the local store (no network, no spinner); the ref holds \`[]\` until the first local result. The Kora context comes from \`KoraProvider\`, wired in your app entry (for example \`src/main.ts\`).`,
67
+ svelte: `## Svelte bindings
68
+
69
+ Kora's Svelte bindings from \`@korajs/svelte\` expose \`useCollection\`, \`useQuery\`, \`useMutation\`, and \`useSyncStatus\`, wired through the provider set up in your app entry.
70
+
71
+ \`\`\`ts
72
+ import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/svelte'
73
+
74
+ const todos = useCollection('todos')
75
+ const rows = useQuery(todos.where({ completed: false }).orderBy('createdAt')) // reactive store
76
+ const addTodo = useMutation((data) => todos.insert(data))
77
+ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
78
+ const status = useSyncStatus() // readable store; read $status.status in markup
79
+ \`\`\`
80
+
81
+ Rules: queries are reactive stores over the local database (no network, no spinner; \`[]\` until the first local result). Mutations expose an \`error\` state that must be surfaced in the UI.`
82
+ };
83
+ function generateAgentsMd(framework) {
84
+ return `# AGENTS.md
85
+
86
+ Guidance for AI coding agents working in this project. Humans: this is useful for you too.
87
+
88
+ ## What this project is
89
+
90
+ This project uses **Kora.js**, an offline-first application framework. All application data lives in a local SQLite database (WASM + OPFS in the browser, native SQLite on desktop and server) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs live at https://korajs.dev, with a machine-readable index at https://korajs.dev/llms.txt and the complete docs in one file at https://korajs.dev/llms-full.txt. Any documentation page is available as raw markdown by appending \`.md\` to its URL.
91
+
92
+ ## Golden rules
93
+
94
+ 1. **The schema is the source of truth.** Collections are defined with \`defineSchema\` and the \`t.*\` field builders (by convention in \`src/schema.ts\`). To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
95
+ 2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
96
+ 3. **Await readiness before direct collection access.** Outside the UI bindings, \`await app.ready\` before calling \`app.<collection>\` methods. The framework bindings handle this for you inside components.
97
+ 4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a read or write on connectivity. If you are checking \`navigator.onLine\` before a data operation, you are doing it wrong.
98
+ 5. **Surface mutation errors.** Fire-and-forget \`mutate\` calls put errors into the mutation state. Always render the mutation's \`error\`, or handle the promise from the \`mutateAsync\` variant. Silent failure is the worst failure.
99
+ 6. **Do not add a state library for server or app data.** Kora's reactive queries are the store. Do not reach for react-query, SWR, Redux, Zustand, or similar for data that lives in a collection. Local UI state (form inputs, toggles) can use your framework's normal state tools.
100
+ 7. **Do not add network-style loading states for local reads.** \`useQuery\` reads the local database, which answers within a frame; its very first render returns an empty result. Use \`useQueryState\` only where "loading" and "no rows" must look different.
101
+
102
+ ## Data API cheat sheet
103
+
104
+ \`\`\`ts
105
+ await app.ready
106
+ const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
107
+ await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
108
+ await app.todos.delete(rec.id)
109
+ const one = await app.todos.findById(rec.id)
110
+ const unsubscribe = app.todos
111
+ .where({ completed: false })
112
+ .orderBy('createdAt', 'desc')
113
+ .subscribe((rows) => {/* fires immediately, then on every change */})
114
+ \`\`\`
115
+
116
+ Schema example (\`src/schema.ts\`):
117
+
118
+ \`\`\`ts
119
+ import { defineSchema, t } from 'korajs'
120
+
121
+ export default defineSchema({
122
+ version: 1,
123
+ collections: {
124
+ todos: {
125
+ fields: {
126
+ title: t.string(),
127
+ completed: t.boolean().default(false),
128
+ tags: t.array(t.string()).default([]),
129
+ priority: t.enum(['low', 'medium', 'high']).default('medium'),
130
+ createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
131
+ },
132
+ indexes: ['completed', 'createdAt'],
133
+ },
134
+ },
135
+ })
136
+ \`\`\`
137
+
138
+ If you change a collection's shape, increment \`version\` and add a \`migrations\` entry for it (\`migrate().addField(...)\`): each device migrates its local database on its next open. \`npx kora migrate\` generates the matching migration for server databases and a transform stub for clients still on the older version.
139
+
140
+ ## Conflict handling
141
+
142
+ Concurrent edits merge automatically: last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key, and character-level CRDT for \`t.richtext()\` fields. When a field needs domain-specific merging (counters, quantities), add a \`resolve\` function in the schema rather than writing sync logic by hand. Never write your own conflict or merge code.
143
+
144
+ ## Sync and auth
145
+
146
+ Enable sync with one option on \`createApp\`: \`createApp({ schema, sync: { url, autoConnect: true } })\` (without \`autoConnect\`, call \`await app.sync?.connect()\` after \`app.ready\`). When the app is offline, writes queue locally and sync when connectivity returns. If the project uses \`@korajs/auth\`, pass its binding as \`createApp({ schema, sync: { url, authClient: createKoraAuthSync({ authClient, schema }) } })\`; the server grants what each user may sync. Local writes work without sign-in; sync requires the server to accept the connection.
147
+
148
+ ${FRAMEWORK_BINDINGS[framework]}
149
+
150
+ ## Commands
151
+
152
+ - \`npx kora dev\` runs the dev environment (app server, local sync server when configured, schema watcher).
153
+ - \`npx kora doctor\` diagnoses a broken setup.
154
+ - \`npx kora migrate\` generates a migration from a schema change (\`--apply\` runs it against a SQLite or Postgres database).
155
+ - In a running app, the DevTools overlay (Ctrl+Shift+K, Cmd+Shift+K on macOS) inspects operations, merges, and sync status.
156
+ `;
157
+ }
158
+
159
+ // src/commands/agents/agents-command.ts
160
+ import { access, readFile, writeFile } from "fs/promises";
161
+ import { join } from "path";
162
+ import { defineCommand } from "citty";
163
+ async function fileExists(path) {
164
+ try {
165
+ await access(path);
166
+ return true;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+ async function readManifest(root) {
172
+ try {
173
+ return JSON.parse(await readFile(join(root, "package.json"), "utf-8"));
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+ function resolveFramework(override, manifest) {
179
+ if (override !== void 0 && isAgentsFramework(override)) return override;
180
+ return detectFrameworkFromManifest(manifest) ?? "react";
181
+ }
182
+ var agentsCommand = defineCommand({
183
+ meta: {
184
+ name: "agents-md",
185
+ description: "Write an AGENTS.md with Kora rules for AI coding agents into this project"
186
+ },
187
+ args: {
188
+ force: {
189
+ type: "boolean",
190
+ description: "Overwrite an existing AGENTS.md",
191
+ default: false
192
+ },
193
+ framework: {
194
+ type: "string",
195
+ description: "Override framework detection (react, vue, or svelte)"
196
+ }
197
+ },
198
+ async run({ args }) {
199
+ const logger = createLogger();
200
+ const projectRoot = await findProjectRoot() ?? process.cwd();
201
+ const manifest = await readManifest(projectRoot);
202
+ const override = typeof args.framework === "string" ? args.framework : void 0;
203
+ if (override !== void 0 && !isAgentsFramework(override)) {
204
+ logger.error(`Unknown framework "${override}". Use one of: react, vue, svelte.`);
205
+ process.exitCode = 1;
206
+ return;
207
+ }
208
+ const framework = resolveFramework(override, manifest);
209
+ const target = join(projectRoot, "AGENTS.md");
210
+ const force = args.force === true;
211
+ if (await fileExists(target) && !force) {
212
+ logger.warn("AGENTS.md already exists. Re-run with --force to overwrite it.");
213
+ process.exitCode = 1;
214
+ return;
215
+ }
216
+ await writeFile(target, generateAgentsMd(framework), "utf-8");
217
+ logger.banner();
218
+ logger.success(`Wrote AGENTS.md (${framework} bindings) to ${target}`);
219
+ logger.step("Commit it so coding agents pick up Kora conventions automatically.");
220
+ logger.blank();
221
+ }
222
+ });
223
+
224
+ // src/commands/generate/type-generator.ts
225
+ function generateTypes(schema) {
226
+ const lines = [
227
+ "// Auto-generated by @korajs/cli \u2014 do not edit manually",
228
+ `// Generated from schema version ${String(schema.version)}`,
229
+ ""
230
+ ];
231
+ const collectionNames = Object.keys(schema.collections);
232
+ if (collectionNames.length === 0) {
233
+ return lines.join("\n");
234
+ }
235
+ for (const [name, collection] of Object.entries(schema.collections)) {
236
+ const pascal = toPascalCase(name);
237
+ const fields = collection.fields;
238
+ lines.push(`export interface ${pascal}Record {`);
239
+ lines.push(" readonly id: string");
240
+ for (const [fieldName, descriptor] of Object.entries(fields)) {
241
+ const tsType = fieldKindToTypeScript(descriptor);
242
+ const optional = !descriptor.required && !descriptor.auto ? "?" : "";
243
+ lines.push(` readonly ${fieldName}${optional}: ${tsType}`);
244
+ }
245
+ lines.push("}");
246
+ lines.push("");
247
+ lines.push(`export interface ${pascal}InsertInput {`);
248
+ for (const [fieldName, descriptor] of Object.entries(fields)) {
249
+ if (descriptor.auto) continue;
250
+ const tsType = fieldKindToTypeScript(descriptor);
251
+ const optional = !descriptor.required || descriptor.defaultValue !== void 0 ? "?" : "";
252
+ lines.push(` ${fieldName}${optional}: ${tsType}`);
253
+ }
254
+ lines.push("}");
255
+ lines.push("");
256
+ lines.push(`export interface ${pascal}UpdateInput {`);
257
+ for (const [fieldName, descriptor] of Object.entries(fields)) {
258
+ if (descriptor.auto) continue;
259
+ const tsType = fieldKindToTypeScript(descriptor);
260
+ lines.push(` ${fieldName}?: ${tsType}`);
261
+ }
262
+ lines.push("}");
263
+ lines.push("");
264
+ }
265
+ return lines.join("\n");
266
+ }
267
+ function fieldKindToTypeScript(descriptor) {
268
+ switch (descriptor.kind) {
269
+ case "string":
270
+ return "string";
271
+ case "number":
272
+ return "number";
273
+ case "boolean":
274
+ return "boolean";
275
+ case "timestamp":
276
+ return "number";
277
+ case "richtext":
278
+ return "string";
279
+ case "enum": {
280
+ if (descriptor.enumValues && descriptor.enumValues.length > 0) {
281
+ return descriptor.enumValues.map((v) => `'${v}'`).join(" | ");
282
+ }
283
+ return "string";
284
+ }
285
+ case "array": {
286
+ const itemType = itemKindToTypeScript(descriptor.itemKind);
287
+ return `Array<${itemType}>`;
288
+ }
289
+ default:
290
+ return "unknown";
291
+ }
292
+ }
293
+ function itemKindToTypeScript(kind) {
294
+ switch (kind) {
295
+ case "string":
296
+ return "string";
297
+ case "number":
298
+ return "number";
299
+ case "boolean":
300
+ return "boolean";
301
+ case "timestamp":
302
+ return "number";
303
+ case "richtext":
304
+ return "string";
305
+ case "enum":
306
+ return "string";
307
+ case "array":
308
+ return "unknown[]";
309
+ default:
310
+ return "unknown";
311
+ }
312
+ }
313
+ function toPascalCase(name) {
314
+ return name.split(/[_-]/).map((part) => {
315
+ if (part.length === 0) return "";
316
+ const first = part[0];
317
+ return first ? first.toUpperCase() + part.slice(1) : "";
318
+ }).join("");
319
+ }
320
+
321
+ // src/commands/deploy/adapters/adapter.ts
322
+ var DEPLOY_PLATFORMS = [
323
+ "fly",
324
+ "railway",
325
+ "aws-ecs",
326
+ "aws-lightsail",
327
+ "render",
328
+ "docker",
329
+ "kora-cloud"
330
+ ];
331
+ var IMPLEMENTED_DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail"];
332
+ function isImplementedDeployPlatform(platform) {
333
+ return IMPLEMENTED_DEPLOY_PLATFORMS.includes(platform);
334
+ }
335
+ function isDeployPlatform(value) {
336
+ return DEPLOY_PLATFORMS.includes(value);
337
+ }
338
+
339
+ // src/commands/deploy/builder/client-builder.ts
340
+ import { spawn } from "child_process";
341
+ import { existsSync } from "fs";
342
+ import { copyFile, readdir } from "fs/promises";
343
+ import { join as join2, resolve } from "path";
344
+ async function buildClient(options) {
345
+ const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
346
+ if (!viteEntryPoint) {
347
+ throw new Error(
348
+ `Could not find local Vite binary in ${options.projectRoot}. Install dependencies before deploying.`
349
+ );
350
+ }
351
+ const args = [
352
+ viteEntryPoint,
353
+ "build",
354
+ "--outDir",
355
+ options.outDir,
356
+ "--mode",
357
+ options.mode ?? "production"
358
+ ];
359
+ await runProcess(process.execPath, args, options.projectRoot);
360
+ await patchSqliteWasmAssets(options.projectRoot, options.outDir);
361
+ return { outDir: options.outDir };
362
+ }
363
+ async function patchSqliteWasmAssets(projectRoot, outDir) {
364
+ const assetsDir = join2(outDir, "assets");
365
+ if (!existsSync(assetsDir)) return;
366
+ const files = await readdir(assetsDir);
367
+ const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
368
+ if (hashedWasm && !files.includes("sqlite3.wasm")) {
369
+ await copyFile(join2(assetsDir, hashedWasm), join2(assetsDir, "sqlite3.wasm"));
370
+ }
371
+ if (!files.includes("sqlite3-opfs-async-proxy.js")) {
372
+ const proxyFile = resolve(
373
+ projectRoot,
374
+ "node_modules",
375
+ "@sqlite.org",
376
+ "sqlite-wasm",
377
+ "sqlite-wasm",
378
+ "jswasm",
379
+ "sqlite3-opfs-async-proxy.js"
380
+ );
381
+ if (existsSync(proxyFile)) {
382
+ await copyFile(proxyFile, join2(assetsDir, "sqlite3-opfs-async-proxy.js"));
383
+ }
384
+ }
385
+ }
386
+ async function runProcess(command, args, cwd) {
387
+ await new Promise((resolve2, reject) => {
388
+ const child = spawn(command, args, {
389
+ cwd,
390
+ stdio: "inherit",
391
+ env: process.env
392
+ });
393
+ child.on("error", (error) => {
394
+ reject(error);
395
+ });
396
+ child.on("exit", (code) => {
397
+ if (code === 0) {
398
+ resolve2();
399
+ return;
400
+ }
401
+ reject(new Error(`Client build failed with exit code ${String(code ?? "unknown")}.`));
402
+ });
403
+ });
404
+ }
405
+
406
+ // src/commands/deploy/builder/server-bundler.ts
407
+ import { mkdir } from "fs/promises";
408
+ import { access as access2 } from "fs/promises";
409
+ import { join as join3 } from "path";
410
+ import { build } from "esbuild";
411
+ var DEFAULT_ENTRY_CANDIDATES = [
412
+ "server.ts",
413
+ "server.js",
414
+ "src/server.ts",
415
+ "src/server.js"
416
+ ];
417
+ async function bundleServer(options) {
418
+ const candidates = options.entryFileCandidates ?? DEFAULT_ENTRY_CANDIDATES;
419
+ const entryFilePath = await resolveServerEntry(options.projectRoot, candidates);
420
+ if (!entryFilePath) {
421
+ throw new Error(
422
+ `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
423
+ );
424
+ }
425
+ await mkdir(options.deployDirectory, { recursive: true });
426
+ const outputFilePath = join3(options.deployDirectory, "server-bundled.js");
427
+ await build({
428
+ entryPoints: [entryFilePath],
429
+ outfile: outputFilePath,
430
+ bundle: true,
431
+ platform: "node",
432
+ format: "esm",
433
+ target: ["node20"],
434
+ sourcemap: false,
435
+ logLevel: "silent",
436
+ external: ["better-sqlite3"],
437
+ banner: {
438
+ js: "import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);"
439
+ }
440
+ });
441
+ return {
442
+ entryFilePath,
443
+ outputFilePath
444
+ };
445
+ }
446
+ async function resolveServerEntry(projectRoot, candidates) {
447
+ for (const candidate of candidates) {
448
+ const fullPath = join3(projectRoot, candidate);
449
+ try {
450
+ await access2(fullPath);
451
+ return fullPath;
452
+ } catch {
453
+ }
454
+ }
455
+ return null;
456
+ }
457
+
458
+ // src/commands/deploy/artifacts/fly-toml-generator.ts
459
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
460
+ import { join as join4 } from "path";
461
+ var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
462
+ function generateFlyToml(options) {
463
+ const internalPort = options.internalPort ?? 3e3;
464
+ return [
465
+ GENERATED_HEADER,
466
+ "",
467
+ `app = "${options.appName}"`,
468
+ `primary_region = "${options.region}"`,
469
+ "",
470
+ "[build]",
471
+ ` dockerfile = "Dockerfile"`,
472
+ "",
473
+ "[http_service]",
474
+ ` internal_port = ${String(internalPort)}`,
475
+ " force_https = true",
476
+ ' auto_stop_machines = "stop"',
477
+ " auto_start_machines = true",
478
+ " min_machines_running = 0",
479
+ "",
480
+ " [[http_service.checks]]",
481
+ ' grace_period = "10s"',
482
+ ' interval = "30s"',
483
+ ' method = "GET"',
484
+ ' timeout = "5s"',
485
+ ' path = "/health"',
486
+ ""
487
+ ].join("\n");
488
+ }
489
+ async function writeFlyTomlArtifact(deployDirectory, options) {
490
+ await mkdir2(deployDirectory, { recursive: true });
491
+ const filePath = join4(deployDirectory, "fly.toml");
492
+ await writeFile2(filePath, generateFlyToml(options), "utf-8");
493
+ return filePath;
494
+ }
495
+
496
+ // src/commands/deploy/adapters/fly-adapter.ts
497
+ import { spawn as spawn2 } from "child_process";
498
+ import { join as join5 } from "path";
499
+ var FlyAdapter = class {
500
+ name = "fly";
501
+ logger = createLogger();
502
+ runner;
503
+ commandCandidates;
504
+ flyCommand = null;
505
+ currentContext;
506
+ lastDeploymentId = null;
507
+ constructor(options = {}) {
508
+ this.runner = options.runner ?? new NodeFlyCommandRunner();
509
+ this.commandCandidates = options.commandCandidates ?? DEFAULT_FLY_COMMAND_CANDIDATES;
510
+ this.currentContext = options.context ?? null;
511
+ }
512
+ /**
513
+ * Seeds runtime context for non-provisioning commands.
514
+ */
515
+ setContext(context) {
516
+ this.currentContext = context;
517
+ }
518
+ /**
519
+ * Checks whether a Fly CLI executable can be resolved.
520
+ */
521
+ async detect() {
522
+ const command = await this.tryResolveFlyCommand(process.cwd());
523
+ return command !== null;
524
+ }
525
+ /**
526
+ * Ensures Fly CLI is available before deployment.
527
+ */
528
+ async install() {
529
+ const available = await this.detect();
530
+ if (!available) {
531
+ throw new Error(
532
+ "Fly CLI is required but not installed. Install from https://fly.io/docs/hands-on/install-flyctl/."
533
+ );
534
+ }
535
+ }
536
+ /**
537
+ * Performs Fly authentication check/login.
538
+ */
539
+ async authenticate() {
540
+ const projectRoot = this.currentContext?.projectRoot ?? process.cwd();
541
+ const status = await this.runFlyCommand(["auth", "whoami", "--json"], projectRoot, false);
542
+ if (status.exitCode === 0) return;
543
+ const login = await this.runFlyCommand(["auth", "login"], projectRoot, true);
544
+ if (login.exitCode !== 0) {
545
+ throw new Error(`Fly authentication failed: ${normalizeError(login.stderr, login.stdout)}`);
546
+ }
547
+ }
548
+ /**
549
+ * Provisions app and optionally region-bound resources in Fly.
550
+ */
551
+ async provision(config) {
552
+ this.currentContext = {
553
+ projectRoot: config.projectRoot,
554
+ appName: config.appName,
555
+ region: config.region ?? "iad"
556
+ };
557
+ const appCreateArgs = ["apps", "create", config.appName];
558
+ const createApp = await this.runFlyCommand(appCreateArgs, config.projectRoot, false);
559
+ if (createApp.exitCode !== 0 && !isAlreadyExistsResponse(createApp.stderr, createApp.stdout)) {
560
+ throw new Error(
561
+ `Fly app provisioning failed for "${config.appName}": ${normalizeError(createApp.stderr, createApp.stdout)}`
562
+ );
563
+ }
564
+ const secrets = [];
565
+ const portSecret = await this.runFlyCommand(
566
+ ["secrets", "set", "PORT=3000", "--app", config.appName],
567
+ config.projectRoot,
568
+ false
569
+ );
570
+ if (portSecret.exitCode === 0) {
571
+ secrets.push("PORT");
572
+ }
573
+ return {
574
+ applicationId: config.appName,
575
+ databaseId: null,
576
+ secretsSet: secrets
577
+ };
578
+ }
579
+ /**
580
+ * Builds local artifacts consumed by Fly deploy.
581
+ */
582
+ async build(config) {
583
+ this.currentContext = {
584
+ projectRoot: config.projectRoot,
585
+ appName: config.appName,
586
+ region: config.region
587
+ };
588
+ const deployDirectory = join5(config.projectRoot, ".kora", "deploy");
589
+ await writeFlyTomlArtifact(deployDirectory, {
590
+ appName: config.appName,
591
+ region: config.region ?? "iad"
592
+ });
593
+ await bundleServer({
594
+ projectRoot: config.projectRoot,
595
+ deployDirectory
596
+ });
597
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
598
+ projectRoot: config.projectRoot,
599
+ outDir: join5(deployDirectory, "dist"),
600
+ mode: "production"
601
+ });
602
+ return {
603
+ clientDirectory: client?.outDir ?? null,
604
+ serverBundlePath: join5(deployDirectory, "server-bundled.js"),
605
+ deployDirectory
606
+ };
607
+ }
608
+ /**
609
+ * Deploys generated artifacts to Fly and returns live endpoints.
610
+ */
611
+ async deploy(artifacts) {
612
+ const context = this.requireContext();
613
+ const deploy = await this.runFlyCommand(
614
+ ["deploy", "--config", "fly.toml", "--app", context.appName],
615
+ artifacts.deployDirectory,
616
+ true
617
+ );
618
+ if (deploy.exitCode !== 0) {
619
+ throw new Error(`Fly deployment failed: ${normalizeError(deploy.stderr, deploy.stdout)}`);
620
+ }
621
+ const info = await this.runFlyCommand(
622
+ ["status", "--app", context.appName, "--json"],
623
+ context.projectRoot,
624
+ false
625
+ );
626
+ const hostname = parseFlyHostname(info.stdout) ?? `${context.appName}.fly.dev`;
627
+ const deploymentId = parseFlyDeploymentId(info.stdout) ?? (/* @__PURE__ */ new Date()).toISOString();
628
+ this.lastDeploymentId = deploymentId;
629
+ return {
630
+ deploymentId,
631
+ liveUrl: `https://${hostname}`,
632
+ syncUrl: `wss://${hostname}/kora-sync`
633
+ };
634
+ }
635
+ /**
636
+ * Rolls back to a deployment version.
637
+ */
638
+ async rollback(deploymentId) {
639
+ const context = this.requireContext();
640
+ const rollback = await this.runFlyCommand(
641
+ ["releases", "revert", deploymentId, "--app", context.appName],
642
+ context.projectRoot,
643
+ true
644
+ );
645
+ if (rollback.exitCode !== 0) {
646
+ throw new Error(`Fly rollback failed: ${normalizeError(rollback.stderr, rollback.stdout)}`);
647
+ }
648
+ }
649
+ /**
650
+ * Returns deployment logs as an async iterable.
651
+ */
652
+ async *logs(options) {
653
+ const context = this.requireContext();
654
+ const args = ["logs", "--app", context.appName, "--no-tail"];
655
+ if (typeof options.since === "string" && options.since.length > 0) {
656
+ args.push("--region", options.since);
657
+ }
658
+ const result = await this.runFlyCommand(args, context.projectRoot, false);
659
+ if (result.exitCode !== 0) {
660
+ throw new Error(`Fly logs failed: ${normalizeError(result.stderr, result.stdout)}`);
661
+ }
662
+ const lines = result.stdout.split(/\r?\n/).filter((line) => line.length > 0);
663
+ for (const line of lines) {
664
+ yield {
665
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
666
+ level: inferLogLevel(line),
667
+ message: line
668
+ };
669
+ }
670
+ }
671
+ /**
672
+ * Reads current deployment status from Fly.
673
+ */
674
+ async status() {
675
+ const context = this.requireContext();
676
+ const status = await this.runFlyCommand(
677
+ ["status", "--app", context.appName, "--json"],
678
+ context.projectRoot,
679
+ false
680
+ );
681
+ if (status.exitCode !== 0) {
682
+ return {
683
+ state: "failed",
684
+ message: normalizeError(status.stderr, status.stdout)
685
+ };
686
+ }
687
+ const hostname = parseFlyHostname(status.stdout);
688
+ return {
689
+ state: "healthy",
690
+ message: "Fly deployment is healthy.",
691
+ liveUrl: hostname ? `https://${hostname}` : void 0
692
+ };
693
+ }
694
+ async runFlyCommand(flyArgs, cwd, inheritOutput) {
695
+ const flyBinary = await this.resolveFlyCommand(cwd);
696
+ if (inheritOutput) {
697
+ this.logger.step(`fly ${flyArgs.join(" ")}`);
698
+ }
699
+ return await this.runner.run(flyBinary, flyArgs, cwd);
700
+ }
701
+ requireContext() {
702
+ if (!this.currentContext) {
703
+ throw new Error("Fly adapter context is not initialized. Run provision() first.");
704
+ }
705
+ return this.currentContext;
706
+ }
707
+ async resolveFlyCommand(cwd) {
708
+ if (this.flyCommand) {
709
+ return this.flyCommand;
710
+ }
711
+ const resolved = await this.tryResolveFlyCommand(cwd);
712
+ if (!resolved) {
713
+ throw new Error("Could not resolve a usable Fly CLI command (tried flyctl, fly).");
714
+ }
715
+ this.flyCommand = resolved;
716
+ return resolved;
717
+ }
718
+ async tryResolveFlyCommand(cwd) {
719
+ for (const command of this.commandCandidates) {
720
+ const versionCheck = await this.runner.run(command, ["version"], cwd);
721
+ if (versionCheck.exitCode === 0) {
722
+ return command;
723
+ }
724
+ }
725
+ return null;
726
+ }
727
+ };
728
+ var NodeFlyCommandRunner = class {
729
+ async run(command, args, cwd) {
730
+ return await new Promise((resolve2) => {
731
+ const child = spawn2(command, args, {
732
+ cwd,
733
+ env: process.env,
734
+ stdio: ["ignore", "pipe", "pipe"]
735
+ });
736
+ let stdout = "";
737
+ let stderr = "";
738
+ child.stdout?.on("data", (chunk) => {
739
+ stdout += chunk.toString("utf-8");
740
+ });
741
+ child.stderr?.on("data", (chunk) => {
742
+ stderr += chunk.toString("utf-8");
743
+ });
744
+ child.on("error", (error) => {
745
+ resolve2({
746
+ exitCode: 1,
747
+ stdout,
748
+ stderr: `${stderr}
749
+ ${error.message}`
750
+ });
751
+ });
752
+ child.on("exit", (code) => {
753
+ resolve2({
754
+ exitCode: code ?? 1,
755
+ stdout: stdout.trim(),
756
+ stderr: stderr.trim()
757
+ });
758
+ });
759
+ });
760
+ }
761
+ };
762
+ var DEFAULT_FLY_COMMAND_CANDIDATES = ["flyctl", "fly"];
763
+ function parseFlyHostname(rawJson) {
764
+ const parsed = parseJsonRecord(rawJson);
765
+ if (!parsed) return null;
766
+ const hostname = parsed.Hostname;
767
+ if (typeof hostname === "string" && hostname.length > 0) {
768
+ return hostname;
769
+ }
770
+ const hostnames = parsed.Hostnames;
771
+ if (Array.isArray(hostnames)) {
772
+ const first = hostnames.find((item) => typeof item === "string");
773
+ if (typeof first === "string" && first.length > 0) {
774
+ return first;
775
+ }
776
+ }
777
+ return null;
778
+ }
779
+ function parseFlyDeploymentId(rawJson) {
780
+ const parsed = parseJsonRecord(rawJson);
781
+ if (!parsed) return null;
782
+ const deploymentId = parsed.DeploymentID;
783
+ if (typeof deploymentId === "string" && deploymentId.length > 0) {
784
+ return deploymentId;
785
+ }
786
+ const latestDeployment = parsed.LatestDeployment;
787
+ if (typeof latestDeployment === "object" && latestDeployment !== null) {
788
+ const id = latestDeployment.ID;
789
+ if (typeof id === "string" && id.length > 0) {
790
+ return id;
791
+ }
792
+ }
793
+ return null;
794
+ }
795
+ function parseJsonRecord(value) {
796
+ try {
797
+ const parsed = JSON.parse(value);
798
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
799
+ return parsed;
800
+ }
801
+ return null;
802
+ } catch {
803
+ return null;
804
+ }
805
+ }
806
+ function inferLogLevel(line) {
807
+ const normalized = line.toLowerCase();
808
+ if (normalized.includes("error")) return "error";
809
+ if (normalized.includes("warn")) return "warn";
810
+ if (normalized.includes("debug")) return "debug";
811
+ return "info";
812
+ }
813
+ function normalizeError(stderr, stdout) {
814
+ if (stderr.length > 0) return stderr;
815
+ if (stdout.length > 0) return stdout;
816
+ return "unknown fly CLI error";
817
+ }
818
+ function isAlreadyExistsResponse(stderr, stdout) {
819
+ const text = `${stderr}
820
+ ${stdout}`.toLowerCase();
821
+ return text.includes("already exists") || text.includes("already been taken");
822
+ }
823
+
824
+ // src/commands/deploy/artifacts/railway-json-generator.ts
825
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
826
+ import { join as join6 } from "path";
827
+ var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
828
+ function generateRailwayJson(options) {
829
+ const file = {
830
+ $schema: "https://railway.app/railway.schema.json",
831
+ build: {
832
+ builder: "DOCKERFILE",
833
+ dockerfilePath: "Dockerfile"
834
+ },
835
+ deploy: {
836
+ startCommand: options.startCommand ?? "node ./server-bundled.js",
837
+ healthcheckPath: options.healthcheckPath ?? "/health",
838
+ restartPolicyType: "on_failure",
839
+ restartPolicyMaxRetries: 3
840
+ },
841
+ metadata: {
842
+ generatedBy: GENERATED_HEADER2,
843
+ appName: options.appName,
844
+ region: options.region
845
+ }
846
+ };
847
+ return `${JSON.stringify(file, null, 2)}
848
+ `;
849
+ }
850
+ async function writeRailwayJsonArtifact(deployDirectory, options) {
851
+ await mkdir3(deployDirectory, { recursive: true });
852
+ const filePath = join6(deployDirectory, "railway.json");
853
+ await writeFile3(filePath, generateRailwayJson(options), "utf-8");
854
+ return filePath;
855
+ }
856
+
857
+ // src/commands/deploy/adapters/railway-adapter.ts
858
+ import { spawn as spawn3 } from "child_process";
859
+ import { join as join7 } from "path";
860
+ var RailwayAdapter = class {
861
+ name = "railway";
862
+ logger = createLogger();
863
+ runner;
864
+ commandCandidates;
865
+ railwayCommand = null;
866
+ currentContext;
867
+ lastDeploymentId = null;
868
+ constructor(options = {}) {
869
+ this.runner = options.runner ?? new NodeRailwayCommandRunner();
870
+ this.commandCandidates = options.commandCandidates ?? DEFAULT_RAILWAY_COMMAND_CANDIDATES;
871
+ this.currentContext = options.context ?? null;
872
+ }
873
+ setContext(context) {
874
+ this.currentContext = context;
875
+ }
876
+ async detect() {
877
+ const command = await this.tryResolveRailwayCommand(process.cwd());
878
+ return command !== null;
879
+ }
880
+ async install() {
881
+ const available = await this.detect();
882
+ if (!available) {
883
+ throw new Error(
884
+ "Railway CLI is required but not installed. Install with `npm i -g @railway/cli` or see https://docs.railway.com/guides/cli."
885
+ );
886
+ }
887
+ }
888
+ async authenticate() {
889
+ const projectRoot = this.currentContext?.projectRoot ?? process.cwd();
890
+ const whoami = await this.runRailwayCommand(["whoami"], projectRoot, false);
891
+ if (whoami.exitCode === 0) return;
892
+ const login = await this.runRailwayCommand(["login"], projectRoot, true);
893
+ if (login.exitCode !== 0) {
894
+ throw new Error(
895
+ `Railway authentication failed: ${normalizeError2(login.stderr, login.stdout)}`
896
+ );
897
+ }
898
+ }
899
+ async provision(config) {
900
+ this.currentContext = {
901
+ projectRoot: config.projectRoot,
902
+ appName: config.appName,
903
+ region: config.region
904
+ };
905
+ const init = await this.runRailwayCommand(
906
+ ["init", "--name", config.appName, "--confirm"],
907
+ config.projectRoot,
908
+ false
909
+ );
910
+ if (init.exitCode !== 0 && !isAlreadyExistsResponse2(init.stderr, init.stdout)) {
911
+ throw new Error(
912
+ `Railway project provisioning failed for "${config.appName}": ${normalizeError2(init.stderr, init.stdout)}`
913
+ );
914
+ }
915
+ const link = await this.runRailwayCommand(
916
+ ["link", "--project", config.appName, "--environment", config.environment, "--yes"],
917
+ config.projectRoot,
918
+ false
919
+ );
920
+ if (link.exitCode !== 0 && !isAlreadyExistsResponse2(link.stderr, link.stdout)) {
921
+ throw new Error(`Railway link failed: ${normalizeError2(link.stderr, link.stdout)}`);
922
+ }
923
+ const vars = [];
924
+ const setPort = await this.runRailwayCommand(
925
+ ["variables", "set", "PORT=3000", "--yes"],
926
+ config.projectRoot,
927
+ false
928
+ );
929
+ if (setPort.exitCode === 0) {
930
+ vars.push("PORT");
931
+ }
932
+ return {
933
+ applicationId: config.appName,
934
+ databaseId: null,
935
+ secretsSet: vars
936
+ };
937
+ }
938
+ async build(config) {
939
+ this.currentContext = {
940
+ projectRoot: config.projectRoot,
941
+ appName: config.appName,
942
+ region: config.region
943
+ };
944
+ const deployDirectory = join7(config.projectRoot, ".kora", "deploy");
945
+ await writeRailwayJsonArtifact(deployDirectory, {
946
+ appName: config.appName,
947
+ environment: config.environment
948
+ });
949
+ await bundleServer({
950
+ projectRoot: config.projectRoot,
951
+ deployDirectory
952
+ });
953
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
954
+ projectRoot: config.projectRoot,
955
+ outDir: join7(deployDirectory, "dist"),
956
+ mode: "production"
957
+ });
958
+ return {
959
+ clientDirectory: client?.outDir ?? null,
960
+ serverBundlePath: join7(deployDirectory, "server-bundled.js"),
961
+ deployDirectory
962
+ };
963
+ }
964
+ async deploy(artifacts) {
965
+ const context = this.requireContext();
966
+ const up = await this.runRailwayCommand(["up", "--yes"], artifacts.deployDirectory, true);
967
+ if (up.exitCode !== 0) {
968
+ throw new Error(`Railway deployment failed: ${normalizeError2(up.stderr, up.stdout)}`);
969
+ }
970
+ const status = await this.runRailwayCommand(["status", "--json"], context.projectRoot, false);
971
+ const deploymentId = parseRailwayDeploymentId(status.stdout) ?? (/* @__PURE__ */ new Date()).toISOString();
972
+ const liveUrl = parseRailwayUrl(status.stdout) ?? `https://${context.appName}.up.railway.app`;
973
+ this.lastDeploymentId = deploymentId;
974
+ return {
975
+ deploymentId,
976
+ liveUrl,
977
+ syncUrl: toSyncUrl(liveUrl)
978
+ };
979
+ }
980
+ async rollback(deploymentId) {
981
+ const context = this.requireContext();
982
+ const rollback = await this.runRailwayCommand(
983
+ ["redeploy", "--deployment", deploymentId, "--yes"],
984
+ context.projectRoot,
985
+ true
986
+ );
987
+ if (rollback.exitCode !== 0) {
988
+ throw new Error(
989
+ `Railway rollback failed: ${normalizeError2(rollback.stderr, rollback.stdout)}`
990
+ );
991
+ }
992
+ }
993
+ async *logs(options) {
994
+ const context = this.requireContext();
995
+ const args = ["logs"];
996
+ if (typeof options.tail === "number" && options.tail > 0) {
997
+ args.push("--lines", String(options.tail));
998
+ }
999
+ const result = await this.runRailwayCommand(args, context.projectRoot, false);
1000
+ if (result.exitCode !== 0) {
1001
+ throw new Error(`Railway logs failed: ${normalizeError2(result.stderr, result.stdout)}`);
1002
+ }
1003
+ const lines = result.stdout.split(/\r?\n/).filter((line) => line.length > 0);
1004
+ for (const line of lines) {
1005
+ yield {
1006
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1007
+ level: inferLogLevel2(line),
1008
+ message: line
1009
+ };
1010
+ }
1011
+ }
1012
+ async status() {
1013
+ const context = this.requireContext();
1014
+ const status = await this.runRailwayCommand(["status", "--json"], context.projectRoot, false);
1015
+ if (status.exitCode !== 0) {
1016
+ return {
1017
+ state: "failed",
1018
+ message: normalizeError2(status.stderr, status.stdout)
1019
+ };
1020
+ }
1021
+ const liveUrl = parseRailwayUrl(status.stdout);
1022
+ return {
1023
+ state: "healthy",
1024
+ message: "Railway deployment is healthy.",
1025
+ liveUrl: liveUrl ?? void 0
1026
+ };
1027
+ }
1028
+ async runRailwayCommand(railwayArgs, cwd, inheritOutput) {
1029
+ const command = await this.resolveRailwayCommand(cwd);
1030
+ if (inheritOutput) {
1031
+ this.logger.step(`railway ${railwayArgs.join(" ")}`);
1032
+ }
1033
+ return await this.runner.run(command, railwayArgs, cwd);
1034
+ }
1035
+ requireContext() {
1036
+ if (!this.currentContext) {
1037
+ throw new Error("Railway adapter context is not initialized. Run provision() first.");
1038
+ }
1039
+ return this.currentContext;
1040
+ }
1041
+ async resolveRailwayCommand(cwd) {
1042
+ if (this.railwayCommand) {
1043
+ return this.railwayCommand;
1044
+ }
1045
+ const resolved = await this.tryResolveRailwayCommand(cwd);
1046
+ if (!resolved) {
1047
+ throw new Error("Could not resolve a usable Railway CLI command (tried railway).");
1048
+ }
1049
+ this.railwayCommand = resolved;
1050
+ return resolved;
1051
+ }
1052
+ async tryResolveRailwayCommand(cwd) {
1053
+ for (const command of this.commandCandidates) {
1054
+ const version = await this.runner.run(command, ["--version"], cwd);
1055
+ if (version.exitCode === 0) {
1056
+ return command;
1057
+ }
1058
+ }
1059
+ return null;
1060
+ }
1061
+ };
1062
+ var NodeRailwayCommandRunner = class {
1063
+ async run(command, args, cwd) {
1064
+ return await new Promise((resolve2) => {
1065
+ const child = spawn3(command, args, {
1066
+ cwd,
1067
+ env: process.env,
1068
+ stdio: ["ignore", "pipe", "pipe"]
1069
+ });
1070
+ let stdout = "";
1071
+ let stderr = "";
1072
+ child.stdout?.on("data", (chunk) => {
1073
+ stdout += chunk.toString("utf-8");
1074
+ });
1075
+ child.stderr?.on("data", (chunk) => {
1076
+ stderr += chunk.toString("utf-8");
1077
+ });
1078
+ child.on("error", (error) => {
1079
+ resolve2({
1080
+ exitCode: 1,
1081
+ stdout,
1082
+ stderr: `${stderr}
1083
+ ${error.message}`
1084
+ });
1085
+ });
1086
+ child.on("exit", (code) => {
1087
+ resolve2({
1088
+ exitCode: code ?? 1,
1089
+ stdout: stdout.trim(),
1090
+ stderr: stderr.trim()
1091
+ });
1092
+ });
1093
+ });
1094
+ }
1095
+ };
1096
+ var DEFAULT_RAILWAY_COMMAND_CANDIDATES = ["railway"];
1097
+ function parseRailwayUrl(rawJson) {
1098
+ const record = parseJsonRecord2(rawJson);
1099
+ if (!record) return null;
1100
+ const url = record.url;
1101
+ if (typeof url === "string" && url.length > 0) {
1102
+ return ensureHttpsUrl(url);
1103
+ }
1104
+ const service = record.service;
1105
+ if (typeof service === "object" && service !== null) {
1106
+ const domain = service.domain;
1107
+ if (typeof domain === "string" && domain.length > 0) {
1108
+ return ensureHttpsUrl(domain);
1109
+ }
1110
+ }
1111
+ return null;
1112
+ }
1113
+ function parseRailwayDeploymentId(rawJson) {
1114
+ const record = parseJsonRecord2(rawJson);
1115
+ if (!record) return null;
1116
+ const deploymentId = record.deploymentId;
1117
+ if (typeof deploymentId === "string" && deploymentId.length > 0) {
1118
+ return deploymentId;
1119
+ }
1120
+ const deployment = record.deployment;
1121
+ if (typeof deployment === "object" && deployment !== null) {
1122
+ const id = deployment.id;
1123
+ if (typeof id === "string" && id.length > 0) {
1124
+ return id;
1125
+ }
1126
+ }
1127
+ return null;
1128
+ }
1129
+ function parseJsonRecord2(value) {
1130
+ try {
1131
+ const parsed = JSON.parse(value);
1132
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1133
+ return parsed;
1134
+ }
1135
+ return null;
1136
+ } catch {
1137
+ return null;
1138
+ }
1139
+ }
1140
+ function toSyncUrl(liveUrl) {
1141
+ try {
1142
+ const url = new URL(liveUrl);
1143
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
1144
+ url.pathname = "/kora-sync";
1145
+ return url.toString().replace(/\/$/, "");
1146
+ } catch {
1147
+ return null;
1148
+ }
1149
+ }
1150
+ function ensureHttpsUrl(value) {
1151
+ if (value.startsWith("http://") || value.startsWith("https://")) {
1152
+ return value;
1153
+ }
1154
+ return `https://${value}`;
1155
+ }
1156
+ function inferLogLevel2(line) {
1157
+ const normalized = line.toLowerCase();
1158
+ if (normalized.includes("error")) return "error";
1159
+ if (normalized.includes("warn")) return "warn";
1160
+ if (normalized.includes("debug")) return "debug";
1161
+ return "info";
1162
+ }
1163
+ function normalizeError2(stderr, stdout) {
1164
+ if (stderr.length > 0) return stderr;
1165
+ if (stdout.length > 0) return stdout;
1166
+ return "unknown railway CLI error";
1167
+ }
1168
+ function isAlreadyExistsResponse2(stderr, stdout) {
1169
+ const text = `${stderr}
1170
+ ${stdout}`.toLowerCase();
1171
+ return text.includes("already exists");
1172
+ }
1173
+
1174
+ // src/commands/deploy/adapters/stub-adapter.ts
1175
+ var StubDeployAdapter = class {
1176
+ name;
1177
+ contextLabel;
1178
+ constructor(platform) {
1179
+ this.name = platform;
1180
+ this.contextLabel = `Deploy adapter "${platform}" is not implemented yet.`;
1181
+ }
1182
+ setContext(_context) {
1183
+ }
1184
+ async detect() {
1185
+ return false;
1186
+ }
1187
+ async install() {
1188
+ throw this.notImplementedError();
1189
+ }
1190
+ async authenticate() {
1191
+ throw this.notImplementedError();
1192
+ }
1193
+ async provision(_config) {
1194
+ throw this.notImplementedError();
1195
+ }
1196
+ async build(_config) {
1197
+ throw this.notImplementedError();
1198
+ }
1199
+ async deploy(_artifacts) {
1200
+ throw this.notImplementedError();
1201
+ }
1202
+ async rollback(_deploymentId) {
1203
+ throw this.notImplementedError();
1204
+ }
1205
+ logs(_options) {
1206
+ throw this.notImplementedError();
1207
+ }
1208
+ async status() {
1209
+ return {
1210
+ state: "unknown",
1211
+ message: this.notImplementedMessage()
1212
+ };
1213
+ }
1214
+ notImplementedError() {
1215
+ return new Error(this.notImplementedMessage());
1216
+ }
1217
+ notImplementedMessage() {
1218
+ return `${this.contextLabel} Start with --platform fly for now.`;
1219
+ }
1220
+ };
1221
+
1222
+ // src/commands/deploy/adapters/aws-ecs-adapter.ts
1223
+ import { spawn as spawn4 } from "child_process";
1224
+ import { join as join8 } from "path";
1225
+ var AwsEcsAdapter = class {
1226
+ name = "aws-ecs";
1227
+ logger = createLogger();
1228
+ runner;
1229
+ currentContext;
1230
+ constructor(options = {}) {
1231
+ this.runner = options.runner ?? new NodeAwsCommandRunner();
1232
+ this.currentContext = options.context ?? null;
1233
+ }
1234
+ setContext(context) {
1235
+ this.currentContext = context;
1236
+ }
1237
+ async detect() {
1238
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
1239
+ return result.exitCode === 0;
1240
+ }
1241
+ async install() {
1242
+ const available = await this.detect();
1243
+ if (!available) {
1244
+ throw new Error(
1245
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1246
+ );
1247
+ }
1248
+ }
1249
+ async authenticate() {
1250
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
1251
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
1252
+ if (result.exitCode !== 0) {
1253
+ throw new Error(
1254
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
1255
+ );
1256
+ }
1257
+ }
1258
+ async provision(config) {
1259
+ this.currentContext = {
1260
+ projectRoot: config.projectRoot,
1261
+ appName: config.appName,
1262
+ region: config.region ?? "us-east-1"
1263
+ };
1264
+ const region = config.region ?? "us-east-1";
1265
+ const repoName = `kora/${config.appName}`;
1266
+ const createRepo = await this.runner.run(
1267
+ "aws",
1268
+ [
1269
+ "ecr",
1270
+ "create-repository",
1271
+ "--repository-name",
1272
+ repoName,
1273
+ "--region",
1274
+ region,
1275
+ "--image-scanning-configuration",
1276
+ "scanOnPush=true"
1277
+ ],
1278
+ config.projectRoot
1279
+ );
1280
+ if (createRepo.exitCode !== 0 && !createRepo.stderr.includes("RepositoryAlreadyExistsException")) {
1281
+ throw new Error(`Failed to create ECR repository: ${createRepo.stderr}`);
1282
+ }
1283
+ const identity = await this.runner.run(
1284
+ "aws",
1285
+ ["sts", "get-caller-identity", "--query", "Account", "--output", "text"],
1286
+ config.projectRoot
1287
+ );
1288
+ const accountId = identity.stdout.trim();
1289
+ await this.runner.run(
1290
+ "aws",
1291
+ ["ecs", "create-cluster", "--cluster-name", config.appName, "--region", region],
1292
+ config.projectRoot
1293
+ );
1294
+ await this.runner.run(
1295
+ "aws",
1296
+ [
1297
+ "logs",
1298
+ "create-log-group",
1299
+ "--log-group-name",
1300
+ `/ecs/${config.appName}`,
1301
+ "--region",
1302
+ region
1303
+ ],
1304
+ config.projectRoot
1305
+ );
1306
+ return {
1307
+ applicationId: `${accountId}.dkr.ecr.${region}.amazonaws.com/${repoName}`,
1308
+ databaseId: null,
1309
+ secretsSet: ["PORT"]
1310
+ };
1311
+ }
1312
+ async build(config) {
1313
+ this.currentContext = {
1314
+ projectRoot: config.projectRoot,
1315
+ appName: config.appName,
1316
+ region: config.region
1317
+ };
1318
+ const deployDirectory = join8(config.projectRoot, ".kora", "deploy");
1319
+ await bundleServer({
1320
+ projectRoot: config.projectRoot,
1321
+ deployDirectory
1322
+ });
1323
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1324
+ projectRoot: config.projectRoot,
1325
+ outDir: join8(deployDirectory, "dist"),
1326
+ mode: "production"
1327
+ });
1328
+ return {
1329
+ clientDirectory: client?.outDir ?? null,
1330
+ serverBundlePath: join8(deployDirectory, "server-bundled.js"),
1331
+ deployDirectory
1332
+ };
1333
+ }
1334
+ async deploy(artifacts) {
1335
+ const context = this.requireContext();
1336
+ const region = context.region ?? "us-east-1";
1337
+ const repoName = `kora/${context.appName}`;
1338
+ const loginPassword = await this.runner.run(
1339
+ "aws",
1340
+ ["ecr", "get-login-password", "--region", region],
1341
+ context.projectRoot
1342
+ );
1343
+ if (loginPassword.exitCode !== 0) {
1344
+ throw new Error(`ECR login failed: ${loginPassword.stderr}`);
1345
+ }
1346
+ const identity = await this.runner.run(
1347
+ "aws",
1348
+ ["sts", "get-caller-identity", "--query", "Account", "--output", "text"],
1349
+ context.projectRoot
1350
+ );
1351
+ const accountId = identity.stdout.trim();
1352
+ const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
1353
+ const imageUri = `${ecrUri}/${repoName}:latest`;
1354
+ const dockerLogin = await this.runner.run(
1355
+ "docker",
1356
+ ["login", "--username", "AWS", "--password-stdin", ecrUri],
1357
+ artifacts.deployDirectory
1358
+ );
1359
+ this.logger.step("Building Docker image...");
1360
+ const dockerBuild = await this.runner.run(
1361
+ "docker",
1362
+ ["build", "--platform", "linux/amd64", "-t", imageUri, "."],
1363
+ artifacts.deployDirectory
1364
+ );
1365
+ if (dockerBuild.exitCode !== 0) {
1366
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1367
+ }
1368
+ this.logger.step("Pushing image to ECR...");
1369
+ const dockerPush = await this.runner.run(
1370
+ "docker",
1371
+ ["push", imageUri],
1372
+ artifacts.deployDirectory
1373
+ );
1374
+ if (dockerPush.exitCode !== 0) {
1375
+ throw new Error(`Docker push failed: ${dockerPush.stderr}`);
1376
+ }
1377
+ const taskDef = JSON.stringify({
1378
+ family: context.appName,
1379
+ networkMode: "awsvpc",
1380
+ requiresCompatibilities: ["FARGATE"],
1381
+ cpu: "256",
1382
+ memory: "512",
1383
+ executionRoleArn: `arn:aws:iam::${accountId}:role/ecsTaskExecutionRole`,
1384
+ containerDefinitions: [
1385
+ {
1386
+ name: context.appName,
1387
+ image: imageUri,
1388
+ essential: true,
1389
+ portMappings: [{ containerPort: 3001, protocol: "tcp" }],
1390
+ logConfiguration: {
1391
+ logDriver: "awslogs",
1392
+ options: {
1393
+ "awslogs-group": `/ecs/${context.appName}`,
1394
+ "awslogs-region": region,
1395
+ "awslogs-stream-prefix": "ecs"
1396
+ }
1397
+ },
1398
+ healthCheck: {
1399
+ command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
1400
+ interval: 30,
1401
+ timeout: 5,
1402
+ retries: 3,
1403
+ startPeriod: 60
1404
+ }
1405
+ }
1406
+ ]
1407
+ });
1408
+ const registerTask = await this.runner.run(
1409
+ "aws",
1410
+ ["ecs", "register-task-definition", "--cli-input-json", taskDef, "--region", region],
1411
+ context.projectRoot
1412
+ );
1413
+ if (registerTask.exitCode !== 0) {
1414
+ throw new Error(`Task definition registration failed: ${registerTask.stderr}`);
1415
+ }
1416
+ const updateService = await this.runner.run(
1417
+ "aws",
1418
+ [
1419
+ "ecs",
1420
+ "update-service",
1421
+ "--cluster",
1422
+ context.appName,
1423
+ "--service",
1424
+ context.appName,
1425
+ "--task-definition",
1426
+ context.appName,
1427
+ "--force-new-deployment",
1428
+ "--region",
1429
+ region
1430
+ ],
1431
+ context.projectRoot
1432
+ );
1433
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1434
+ if (updateService.exitCode !== 0) {
1435
+ this.logger.step("Service not found, creating new service...");
1436
+ this.logger.step(
1437
+ `Task definition registered. Create the ECS service with:
1438
+ aws ecs create-service \\
1439
+ --cluster ${context.appName} \\
1440
+ --service-name ${context.appName} \\
1441
+ --task-definition ${context.appName} \\
1442
+ --desired-count 1 \\
1443
+ --launch-type FARGATE \\
1444
+ --network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<sg-id>],assignPublicIp=ENABLED}" \\
1445
+ --region ${region}`
1446
+ );
1447
+ }
1448
+ return {
1449
+ deploymentId,
1450
+ liveUrl: `https://${context.appName}.${region}.amazonaws.com`,
1451
+ syncUrl: `wss://${context.appName}.${region}.amazonaws.com/kora-sync`
1452
+ };
1453
+ }
1454
+ async rollback(deploymentId) {
1455
+ const context = this.requireContext();
1456
+ const region = context.region ?? "us-east-1";
1457
+ const result = await this.runner.run(
1458
+ "aws",
1459
+ [
1460
+ "ecs",
1461
+ "update-service",
1462
+ "--cluster",
1463
+ context.appName,
1464
+ "--service",
1465
+ context.appName,
1466
+ "--task-definition",
1467
+ `${context.appName}:${deploymentId}`,
1468
+ "--force-new-deployment",
1469
+ "--region",
1470
+ region
1471
+ ],
1472
+ context.projectRoot
1473
+ );
1474
+ if (result.exitCode !== 0) {
1475
+ throw new Error(`ECS rollback failed: ${result.stderr}`);
1476
+ }
1477
+ }
1478
+ async *logs(options) {
1479
+ const context = this.requireContext();
1480
+ const region = context.region ?? "us-east-1";
1481
+ const args = [
1482
+ "logs",
1483
+ "get-log-events",
1484
+ "--log-group-name",
1485
+ `/ecs/${context.appName}`,
1486
+ "--log-stream-name",
1487
+ "ecs/latest",
1488
+ "--region",
1489
+ region
1490
+ ];
1491
+ if (options.tail) {
1492
+ args.push("--limit", String(options.tail));
1493
+ }
1494
+ const result = await this.runner.run("aws", args, context.projectRoot);
1495
+ if (result.exitCode !== 0) {
1496
+ return;
1497
+ }
1498
+ try {
1499
+ const parsed = JSON.parse(result.stdout);
1500
+ for (const event of parsed.events ?? []) {
1501
+ yield {
1502
+ timestamp: new Date(event.timestamp).toISOString(),
1503
+ level: inferLogLevel3(event.message),
1504
+ message: event.message
1505
+ };
1506
+ }
1507
+ } catch {
1508
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
1509
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
1510
+ }
1511
+ }
1512
+ }
1513
+ async status() {
1514
+ const context = this.requireContext();
1515
+ const region = context.region ?? "us-east-1";
1516
+ const result = await this.runner.run(
1517
+ "aws",
1518
+ [
1519
+ "ecs",
1520
+ "describe-services",
1521
+ "--cluster",
1522
+ context.appName,
1523
+ "--services",
1524
+ context.appName,
1525
+ "--region",
1526
+ region
1527
+ ],
1528
+ context.projectRoot
1529
+ );
1530
+ if (result.exitCode !== 0) {
1531
+ return { state: "failed", message: result.stderr };
1532
+ }
1533
+ try {
1534
+ const parsed = JSON.parse(result.stdout);
1535
+ const service = parsed.services?.[0];
1536
+ if (!service) {
1537
+ return { state: "unknown", message: "Service not found" };
1538
+ }
1539
+ if (service.status === "ACTIVE" && service.runningCount > 0) {
1540
+ return {
1541
+ state: "healthy",
1542
+ message: `Running ${service.runningCount}/${service.desiredCount} tasks`
1543
+ };
1544
+ }
1545
+ return {
1546
+ state: service.runningCount === 0 ? "pending" : "healthy",
1547
+ message: `Status: ${service.status}, running: ${service.runningCount}/${service.desiredCount}`
1548
+ };
1549
+ } catch {
1550
+ return { state: "unknown", message: "Could not parse service status" };
1551
+ }
1552
+ }
1553
+ requireContext() {
1554
+ if (!this.currentContext) {
1555
+ throw new Error("AWS ECS adapter context is not initialized. Run provision() first.");
1556
+ }
1557
+ return this.currentContext;
1558
+ }
1559
+ };
1560
+ var NodeAwsCommandRunner = class {
1561
+ async run(command, args, cwd) {
1562
+ return new Promise((resolve2) => {
1563
+ const child = spawn4(command, args, {
1564
+ cwd,
1565
+ env: process.env,
1566
+ stdio: ["ignore", "pipe", "pipe"]
1567
+ });
1568
+ let stdout = "";
1569
+ let stderr = "";
1570
+ child.stdout?.on("data", (chunk) => {
1571
+ stdout += chunk.toString("utf-8");
1572
+ });
1573
+ child.stderr?.on("data", (chunk) => {
1574
+ stderr += chunk.toString("utf-8");
1575
+ });
1576
+ child.on("error", (error) => {
1577
+ resolve2({ exitCode: 1, stdout, stderr: `${stderr}
1578
+ ${error.message}` });
1579
+ });
1580
+ child.on("exit", (code) => {
1581
+ resolve2({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1582
+ });
1583
+ });
1584
+ }
1585
+ };
1586
+ function inferLogLevel3(line) {
1587
+ const normalized = line.toLowerCase();
1588
+ if (normalized.includes("error")) return "error";
1589
+ if (normalized.includes("warn")) return "warn";
1590
+ if (normalized.includes("debug")) return "debug";
1591
+ return "info";
1592
+ }
1593
+
1594
+ // src/commands/deploy/adapters/aws-lightsail-adapter.ts
1595
+ import { spawn as spawn5 } from "child_process";
1596
+ import { join as join9 } from "path";
1597
+ var AwsLightsailAdapter = class {
1598
+ name = "aws-lightsail";
1599
+ logger = createLogger();
1600
+ runner;
1601
+ currentContext;
1602
+ constructor(options = {}) {
1603
+ this.runner = options.runner ?? new NodeAwsLightsailCommandRunner();
1604
+ this.currentContext = options.context ?? null;
1605
+ }
1606
+ setContext(context) {
1607
+ this.currentContext = context;
1608
+ }
1609
+ async detect() {
1610
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
1611
+ return result.exitCode === 0;
1612
+ }
1613
+ async install() {
1614
+ const available = await this.detect();
1615
+ if (!available) {
1616
+ throw new Error(
1617
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1618
+ );
1619
+ }
1620
+ }
1621
+ async authenticate() {
1622
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
1623
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
1624
+ if (result.exitCode !== 0) {
1625
+ throw new Error(
1626
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
1627
+ );
1628
+ }
1629
+ }
1630
+ async provision(config) {
1631
+ this.currentContext = {
1632
+ projectRoot: config.projectRoot,
1633
+ appName: config.appName,
1634
+ region: config.region ?? "us-east-1"
1635
+ };
1636
+ const region = config.region ?? "us-east-1";
1637
+ const serviceName = sanitizeLightsailName(config.appName);
1638
+ const createService = await this.runner.run(
1639
+ "aws",
1640
+ [
1641
+ "lightsail",
1642
+ "create-container-service",
1643
+ "--service-name",
1644
+ serviceName,
1645
+ "--power",
1646
+ "nano",
1647
+ "--scale",
1648
+ "1",
1649
+ "--region",
1650
+ region
1651
+ ],
1652
+ config.projectRoot
1653
+ );
1654
+ if (createService.exitCode !== 0 && !createService.stderr.includes("already exists")) {
1655
+ throw new Error(`Failed to create Lightsail container service: ${createService.stderr}`);
1656
+ }
1657
+ return {
1658
+ applicationId: serviceName,
1659
+ databaseId: null,
1660
+ secretsSet: ["PORT"]
1661
+ };
1662
+ }
1663
+ async build(config) {
1664
+ this.currentContext = {
1665
+ projectRoot: config.projectRoot,
1666
+ appName: config.appName,
1667
+ region: config.region
1668
+ };
1669
+ const deployDirectory = join9(config.projectRoot, ".kora", "deploy");
1670
+ await bundleServer({
1671
+ projectRoot: config.projectRoot,
1672
+ deployDirectory
1673
+ });
1674
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1675
+ projectRoot: config.projectRoot,
1676
+ outDir: join9(deployDirectory, "dist"),
1677
+ mode: "production"
1678
+ });
1679
+ return {
1680
+ clientDirectory: client?.outDir ?? null,
1681
+ serverBundlePath: join9(deployDirectory, "server-bundled.js"),
1682
+ deployDirectory
1683
+ };
1684
+ }
1685
+ async deploy(artifacts) {
1686
+ const context = this.requireContext();
1687
+ const region = context.region ?? "us-east-1";
1688
+ const serviceName = sanitizeLightsailName(context.appName);
1689
+ const imageTag = `${serviceName}:latest`;
1690
+ this.logger.step("Building Docker image...");
1691
+ const dockerBuild = await this.runner.run(
1692
+ "docker",
1693
+ ["build", "--platform", "linux/amd64", "-t", imageTag, "."],
1694
+ artifacts.deployDirectory
1695
+ );
1696
+ if (dockerBuild.exitCode !== 0) {
1697
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1698
+ }
1699
+ this.logger.step("Pushing image to Lightsail...");
1700
+ const pushImage = await this.runner.run(
1701
+ "aws",
1702
+ [
1703
+ "lightsail",
1704
+ "push-container-image",
1705
+ "--service-name",
1706
+ serviceName,
1707
+ "--label",
1708
+ "latest",
1709
+ "--image",
1710
+ imageTag,
1711
+ "--region",
1712
+ region
1713
+ ],
1714
+ artifacts.deployDirectory
1715
+ );
1716
+ if (pushImage.exitCode !== 0) {
1717
+ throw new Error(`Lightsail image push failed: ${pushImage.stderr}`);
1718
+ }
1719
+ const lightsailImage = parseLightsailImageRef(pushImage.stdout) ?? `:${serviceName}.latest.1`;
1720
+ this.logger.step("Creating Lightsail deployment...");
1721
+ const environment = { PORT: "3001" };
1722
+ for (const key of PASSTHROUGH_ENV_VARS) {
1723
+ const value = process.env[key];
1724
+ if (value) {
1725
+ environment[key] = value;
1726
+ }
1727
+ }
1728
+ const containers = JSON.stringify({
1729
+ [serviceName]: {
1730
+ image: lightsailImage,
1731
+ ports: { "3001": "HTTP" },
1732
+ environment
1733
+ }
1734
+ });
1735
+ const publicEndpoint = JSON.stringify({
1736
+ containerName: serviceName,
1737
+ containerPort: 3001,
1738
+ healthCheck: {
1739
+ path: "/health",
1740
+ intervalSeconds: 30,
1741
+ timeoutSeconds: 5,
1742
+ healthyThreshold: 2,
1743
+ unhealthyThreshold: 3
1744
+ }
1745
+ });
1746
+ const createDeploy = await this.runner.run(
1747
+ "aws",
1748
+ [
1749
+ "lightsail",
1750
+ "create-container-service-deployment",
1751
+ "--service-name",
1752
+ serviceName,
1753
+ "--containers",
1754
+ containers,
1755
+ "--public-endpoint",
1756
+ publicEndpoint,
1757
+ "--region",
1758
+ region
1759
+ ],
1760
+ context.projectRoot
1761
+ );
1762
+ if (createDeploy.exitCode !== 0) {
1763
+ throw new Error(`Lightsail deployment failed: ${createDeploy.stderr}`);
1764
+ }
1765
+ const serviceInfo = await this.runner.run(
1766
+ "aws",
1767
+ ["lightsail", "get-container-services", "--service-name", serviceName, "--region", region],
1768
+ context.projectRoot
1769
+ );
1770
+ const rawUrl = parseLightsailUrl(serviceInfo.stdout) ?? `https://${serviceName}.${region}.cs.amazonlightsail.com`;
1771
+ const serviceUrl = rawUrl.replace(/\/+$/, "");
1772
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1773
+ return {
1774
+ deploymentId,
1775
+ liveUrl: serviceUrl,
1776
+ syncUrl: `${serviceUrl.replace("https://", "wss://")}/kora-sync`
1777
+ };
1778
+ }
1779
+ async rollback(_deploymentId) {
1780
+ const context = this.requireContext();
1781
+ const region = context.region ?? "us-east-1";
1782
+ const serviceName = sanitizeLightsailName(context.appName);
1783
+ const deployments = await this.runner.run(
1784
+ "aws",
1785
+ [
1786
+ "lightsail",
1787
+ "get-container-service-deployments",
1788
+ "--service-name",
1789
+ serviceName,
1790
+ "--region",
1791
+ region
1792
+ ],
1793
+ context.projectRoot
1794
+ );
1795
+ if (deployments.exitCode !== 0) {
1796
+ throw new Error(`Lightsail rollback failed: ${deployments.stderr}`);
1797
+ }
1798
+ const previousDeployment = parsePreviousDeployment(deployments.stdout, serviceName);
1799
+ if (!previousDeployment) {
1800
+ throw new Error("No previous deployment found to rollback to.");
1801
+ }
1802
+ const redeploy = await this.runner.run(
1803
+ "aws",
1804
+ [
1805
+ "lightsail",
1806
+ "create-container-service-deployment",
1807
+ "--service-name",
1808
+ serviceName,
1809
+ "--containers",
1810
+ JSON.stringify(previousDeployment.containers),
1811
+ "--public-endpoint",
1812
+ JSON.stringify(previousDeployment.publicEndpoint),
1813
+ "--region",
1814
+ region
1815
+ ],
1816
+ context.projectRoot
1817
+ );
1818
+ if (redeploy.exitCode !== 0) {
1819
+ throw new Error(`Lightsail rollback deployment failed: ${redeploy.stderr}`);
1820
+ }
1821
+ }
1822
+ async *logs(options) {
1823
+ const context = this.requireContext();
1824
+ const region = context.region ?? "us-east-1";
1825
+ const serviceName = sanitizeLightsailName(context.appName);
1826
+ const args = [
1827
+ "lightsail",
1828
+ "get-container-log",
1829
+ "--service-name",
1830
+ serviceName,
1831
+ "--container-name",
1832
+ serviceName,
1833
+ "--region",
1834
+ region
1835
+ ];
1836
+ if (options.since) {
1837
+ args.push("--start-time", options.since);
1838
+ }
1839
+ const result = await this.runner.run("aws", args, context.projectRoot);
1840
+ if (result.exitCode !== 0) {
1841
+ return;
1842
+ }
1843
+ try {
1844
+ const parsed = JSON.parse(result.stdout);
1845
+ const events = parsed.logEvents ?? [];
1846
+ const limited = options.tail ? events.slice(-options.tail) : events;
1847
+ for (const event of limited) {
1848
+ yield {
1849
+ timestamp: event.createdAt,
1850
+ level: inferLogLevel4(event.message),
1851
+ message: event.message
1852
+ };
1853
+ }
1854
+ } catch {
1855
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
1856
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
1857
+ }
1858
+ }
1859
+ }
1860
+ async status() {
1861
+ const context = this.requireContext();
1862
+ const region = context.region ?? "us-east-1";
1863
+ const serviceName = sanitizeLightsailName(context.appName);
1864
+ const result = await this.runner.run(
1865
+ "aws",
1866
+ ["lightsail", "get-container-services", "--service-name", serviceName, "--region", region],
1867
+ context.projectRoot
1868
+ );
1869
+ if (result.exitCode !== 0) {
1870
+ return { state: "failed", message: result.stderr };
1871
+ }
1872
+ try {
1873
+ const parsed = JSON.parse(result.stdout);
1874
+ const service = parsed.containerServices?.[0];
1875
+ if (!service) {
1876
+ return { state: "unknown", message: "Container service not found" };
1877
+ }
1878
+ const deployState = service.currentDeployment?.state ?? "UNKNOWN";
1879
+ if (service.state === "RUNNING" && deployState === "ACTIVE") {
1880
+ return {
1881
+ state: "healthy",
1882
+ message: "Lightsail container service is running",
1883
+ liveUrl: service.url
1884
+ };
1885
+ }
1886
+ if (service.state === "DEPLOYING" || deployState === "ACTIVATING") {
1887
+ return {
1888
+ state: "pending",
1889
+ message: `Service: ${service.state}, Deployment: ${deployState}`
1890
+ };
1891
+ }
1892
+ return {
1893
+ state: service.state === "DISABLED" ? "failed" : "unknown",
1894
+ message: `Service: ${service.state}, Deployment: ${deployState}`
1895
+ };
1896
+ } catch {
1897
+ return { state: "unknown", message: "Could not parse service status" };
1898
+ }
1899
+ }
1900
+ requireContext() {
1901
+ if (!this.currentContext) {
1902
+ throw new Error("AWS Lightsail adapter context is not initialized. Run provision() first.");
1903
+ }
1904
+ return this.currentContext;
1905
+ }
1906
+ };
1907
+ var NodeAwsLightsailCommandRunner = class {
1908
+ async run(command, args, cwd) {
1909
+ return new Promise((resolve2) => {
1910
+ const child = spawn5(command, args, {
1911
+ cwd,
1912
+ env: process.env,
1913
+ stdio: ["ignore", "pipe", "pipe"]
1914
+ });
1915
+ let stdout = "";
1916
+ let stderr = "";
1917
+ child.stdout?.on("data", (chunk) => {
1918
+ stdout += chunk.toString("utf-8");
1919
+ });
1920
+ child.stderr?.on("data", (chunk) => {
1921
+ stderr += chunk.toString("utf-8");
1922
+ });
1923
+ child.on("error", (error) => {
1924
+ resolve2({ exitCode: 1, stdout, stderr: `${stderr}
1925
+ ${error.message}` });
1926
+ });
1927
+ child.on("exit", (code) => {
1928
+ resolve2({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1929
+ });
1930
+ });
1931
+ }
1932
+ };
1933
+ var PASSTHROUGH_ENV_VARS = ["DATABASE_URL", "AUTH_SECRET", "PUBLIC_URL", "NODE_ENV"];
1934
+ function sanitizeLightsailName(name) {
1935
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 255) || "kora-app";
1936
+ }
1937
+ function parseLightsailImageRef(output) {
1938
+ const match = output.match(/Refer to this image as\s+"?(:[^"\s]+)"?/i);
1939
+ if (match?.[1]) {
1940
+ return match[1];
1941
+ }
1942
+ const refMatch = output.match(/(:\S+\.\S+\.\d+)/);
1943
+ return refMatch?.[1] ?? null;
1944
+ }
1945
+ function parseLightsailUrl(rawJson) {
1946
+ try {
1947
+ const parsed = JSON.parse(rawJson);
1948
+ const url = parsed.containerServices?.[0]?.url;
1949
+ if (typeof url === "string" && url.length > 0) {
1950
+ return url.startsWith("https://") ? url : `https://${url}`;
1951
+ }
1952
+ return null;
1953
+ } catch {
1954
+ return null;
1955
+ }
1956
+ }
1957
+ function parsePreviousDeployment(rawJson, serviceName) {
1958
+ try {
1959
+ const parsed = JSON.parse(rawJson);
1960
+ const deployments = parsed.deployments ?? [];
1961
+ const previous = deployments.length > 1 ? deployments[1] : null;
1962
+ if (!previous) return null;
1963
+ return {
1964
+ containers: previous.containers,
1965
+ publicEndpoint: previous.publicEndpoint
1966
+ };
1967
+ } catch {
1968
+ return null;
1969
+ }
1970
+ }
1971
+ function inferLogLevel4(line) {
1972
+ const normalized = line.toLowerCase();
1973
+ if (normalized.includes("error")) return "error";
1974
+ if (normalized.includes("warn")) return "warn";
1975
+ if (normalized.includes("debug")) return "debug";
1976
+ return "info";
1977
+ }
1978
+
1979
+ // src/commands/deploy/adapters/factory.ts
1980
+ function createDeployAdapter(platform) {
1981
+ switch (platform) {
1982
+ case "fly":
1983
+ return new FlyAdapter();
1984
+ case "railway":
1985
+ return new RailwayAdapter();
1986
+ case "aws-ecs":
1987
+ return new AwsEcsAdapter();
1988
+ case "aws-lightsail":
1989
+ return new AwsLightsailAdapter();
1990
+ case "render":
1991
+ case "docker":
1992
+ case "kora-cloud":
1993
+ return new StubDeployAdapter(platform);
1994
+ default: {
1995
+ const exhaustiveCheck = platform;
1996
+ return exhaustiveCheck;
1997
+ }
1998
+ }
1999
+ }
2000
+
2001
+ // src/commands/deploy/artifacts/dockerfile-generator.ts
2002
+ import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
2003
+ import { join as join10 } from "path";
2004
+ var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
2005
+ function generateDockerfile(options = {}) {
2006
+ const nodeVersion = options.nodeVersion ?? "20-alpine";
2007
+ const port = options.port ?? 3e3;
2008
+ const clientDirectory = options.clientDirectory === void 0 ? "dist" : options.clientDirectory;
2009
+ const serverBundleFile = options.serverBundleFile ?? "server-bundled.js";
2010
+ const hasNativeDeps = options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0;
2011
+ const lines = [
2012
+ GENERATED_HEADER3,
2013
+ "",
2014
+ `FROM node:${nodeVersion} AS runtime`,
2015
+ "WORKDIR /app",
2016
+ "ENV NODE_ENV=production",
2017
+ `ENV PORT=${String(port)}`,
2018
+ ""
2019
+ ];
2020
+ if (hasNativeDeps) {
2021
+ lines.push("# Install build tools for native modules");
2022
+ lines.push("RUN apk add --no-cache python3 make g++");
2023
+ lines.push("");
2024
+ lines.push("COPY package.json ./package.json");
2025
+ lines.push("RUN npm install --omit=dev");
2026
+ lines.push("");
2027
+ }
2028
+ if (clientDirectory !== null) {
2029
+ lines.push(`COPY ${clientDirectory} ./dist`);
2030
+ }
2031
+ lines.push(`COPY ${serverBundleFile} ./server-bundled.js`);
2032
+ lines.push("");
2033
+ lines.push(`EXPOSE ${String(port)}`);
2034
+ lines.push('CMD ["node", "./server-bundled.js"]');
2035
+ lines.push("");
2036
+ return lines.join("\n");
2037
+ }
2038
+ function generateDeployPackageJson(nativeDependencies) {
2039
+ const pkg = {
2040
+ name: "kora-deploy",
2041
+ version: "1.0.0",
2042
+ private: true,
2043
+ type: "module",
2044
+ dependencies: nativeDependencies
2045
+ };
2046
+ return `${JSON.stringify(pkg, null, 2)}
2047
+ `;
2048
+ }
2049
+ function generateDockerIgnore() {
2050
+ return [
2051
+ GENERATED_HEADER3,
2052
+ "",
2053
+ "node_modules",
2054
+ "npm-debug.log*",
2055
+ "pnpm-debug.log*",
2056
+ "yarn-error.log*",
2057
+ ".env",
2058
+ ".env.*",
2059
+ ".git",
2060
+ ".gitignore",
2061
+ "coverage",
2062
+ ".turbo",
2063
+ "*.test.ts",
2064
+ "*.spec.ts",
2065
+ ""
2066
+ ].join("\n");
2067
+ }
2068
+ async function writeDockerfileArtifact(deployDirectory, options = {}) {
2069
+ await mkdir4(deployDirectory, { recursive: true });
2070
+ const dockerfilePath = join10(deployDirectory, "Dockerfile");
2071
+ await writeFile4(dockerfilePath, generateDockerfile(options), "utf-8");
2072
+ if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
2073
+ const pkgJsonPath = join10(deployDirectory, "package.json");
2074
+ await writeFile4(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
2075
+ }
2076
+ return dockerfilePath;
2077
+ }
2078
+ async function writeDockerIgnoreArtifact(deployDirectory) {
2079
+ await mkdir4(deployDirectory, { recursive: true });
2080
+ const dockerIgnorePath = join10(deployDirectory, ".dockerignore");
2081
+ await writeFile4(dockerIgnorePath, generateDockerIgnore(), "utf-8");
2082
+ return dockerIgnorePath;
2083
+ }
2084
+
2085
+ // src/commands/deploy/state/deploy-state.ts
2086
+ import { mkdir as mkdir5, readFile as readFile2, rm, writeFile as writeFile5 } from "fs/promises";
2087
+ import { join as join11 } from "path";
2088
+ var KORA_DEPLOY_DIRECTORY = join11(".kora", "deploy");
2089
+ var DEPLOY_STATE_FILENAME = "deploy.json";
2090
+ function resolveDeployDirectory(projectRoot) {
2091
+ return join11(projectRoot, KORA_DEPLOY_DIRECTORY);
2092
+ }
2093
+ function resolveDeployStatePath(projectRoot) {
2094
+ return join11(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
2095
+ }
2096
+ async function readDeployState(projectRoot) {
2097
+ const statePath = resolveDeployStatePath(projectRoot);
2098
+ try {
2099
+ const source = await readFile2(statePath, "utf-8");
2100
+ const parsed = JSON.parse(source);
2101
+ return parseDeployState(parsed);
2102
+ } catch (error) {
2103
+ const code = error.code;
2104
+ if (code === "ENOENT") return null;
2105
+ throw error;
2106
+ }
2107
+ }
2108
+ async function writeDeployState(projectRoot, input, now = /* @__PURE__ */ new Date()) {
2109
+ const timestamp = now.toISOString();
2110
+ const state = {
2111
+ platform: input.platform,
2112
+ appName: input.appName,
2113
+ region: input.region,
2114
+ projectRoot: input.projectRoot,
2115
+ liveUrl: input.liveUrl ?? null,
2116
+ syncUrl: input.syncUrl ?? null,
2117
+ databaseId: input.databaseId ?? null,
2118
+ lastDeploymentId: input.lastDeploymentId ?? null,
2119
+ createdAt: timestamp,
2120
+ updatedAt: timestamp
2121
+ };
2122
+ await persistDeployState(projectRoot, state);
2123
+ return state;
2124
+ }
2125
+ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new Date()) {
2126
+ const existing = await readDeployState(projectRoot);
2127
+ if (!existing) {
2128
+ throw new Error("Cannot update deploy state because deploy.json does not exist yet.");
2129
+ }
2130
+ const nextState = {
2131
+ platform: patch.platform ?? existing.platform,
2132
+ appName: patch.appName ?? existing.appName,
2133
+ region: patch.region === void 0 ? existing.region : patch.region,
2134
+ projectRoot: patch.projectRoot ?? existing.projectRoot,
2135
+ liveUrl: patch.liveUrl === void 0 ? existing.liveUrl : patch.liveUrl,
2136
+ syncUrl: patch.syncUrl === void 0 ? existing.syncUrl : patch.syncUrl,
2137
+ databaseId: patch.databaseId === void 0 ? existing.databaseId : patch.databaseId,
2138
+ lastDeploymentId: patch.lastDeploymentId === void 0 ? existing.lastDeploymentId : patch.lastDeploymentId,
2139
+ createdAt: existing.createdAt,
2140
+ updatedAt: now.toISOString()
2141
+ };
2142
+ await persistDeployState(projectRoot, nextState);
2143
+ return nextState;
2144
+ }
2145
+ async function resetDeployState(projectRoot) {
2146
+ await rm(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
2147
+ }
2148
+ async function persistDeployState(projectRoot, state) {
2149
+ const deployDir = resolveDeployDirectory(projectRoot);
2150
+ const statePath = resolveDeployStatePath(projectRoot);
2151
+ await mkdir5(deployDir, { recursive: true });
2152
+ await writeFile5(statePath, `${JSON.stringify(state, null, 2)}
2153
+ `, "utf-8");
2154
+ }
2155
+ function parseDeployState(value) {
2156
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2157
+ throw new Error("Invalid deploy state: expected an object.");
2158
+ }
2159
+ const record = value;
2160
+ const platform = readPlatform(record.platform);
2161
+ const appName = readString(record.appName, "appName");
2162
+ const region = readOptionalString(record.region, "region");
2163
+ const projectRoot = readString(record.projectRoot, "projectRoot");
2164
+ const liveUrl = readOptionalString(record.liveUrl, "liveUrl");
2165
+ const syncUrl = readOptionalString(record.syncUrl, "syncUrl");
2166
+ const databaseId = readOptionalString(record.databaseId, "databaseId");
2167
+ const lastDeploymentId = readOptionalString(record.lastDeploymentId, "lastDeploymentId");
2168
+ const createdAt = readString(record.createdAt, "createdAt");
2169
+ const updatedAt = readString(record.updatedAt, "updatedAt");
2170
+ return {
2171
+ platform,
2172
+ appName,
2173
+ region,
2174
+ projectRoot,
2175
+ liveUrl,
2176
+ syncUrl,
2177
+ databaseId,
2178
+ lastDeploymentId,
2179
+ createdAt,
2180
+ updatedAt
2181
+ };
2182
+ }
2183
+ function readPlatform(value) {
2184
+ if (typeof value !== "string" || !isDeployPlatform(value)) {
2185
+ throw new Error('Invalid deploy state: "platform" must be a supported platform.');
2186
+ }
2187
+ return value;
2188
+ }
2189
+ function readString(value, field) {
2190
+ if (typeof value !== "string" || value.length === 0) {
2191
+ throw new Error(`Invalid deploy state: "${field}" must be a non-empty string.`);
2192
+ }
2193
+ return value;
2194
+ }
2195
+ function readOptionalString(value, field) {
2196
+ if (value === null) return null;
2197
+ if (typeof value === "string") return value;
2198
+ throw new Error(`Invalid deploy state: "${field}" must be a string or null.`);
2199
+ }
2200
+
2201
+ // src/commands/deploy/deploy-command.ts
2202
+ import { existsSync as existsSync2 } from "fs";
2203
+ import { basename, join as join12 } from "path";
2204
+ import { defineCommand as defineCommand2 } from "citty";
2205
+ var deployCommand = defineCommand2({
2206
+ meta: {
2207
+ name: "deploy",
2208
+ description: "Deploy a Kora project to your selected platform"
2209
+ },
2210
+ args: {
2211
+ platform: {
2212
+ type: "string",
2213
+ description: `Deployment platform (${DEPLOY_PLATFORMS.join(", ")})`
2214
+ },
2215
+ app: {
2216
+ type: "string",
2217
+ description: "Application name used by the target platform"
2218
+ },
2219
+ region: {
2220
+ type: "string",
2221
+ description: "Preferred deployment region (for example: iad, lhr, syd)"
2222
+ },
2223
+ prod: {
2224
+ type: "boolean",
2225
+ description: "Deploy to production environment",
2226
+ default: false
2227
+ },
2228
+ confirm: {
2229
+ type: "boolean",
2230
+ description: "Non-interactive mode (fail fast on missing required data)",
2231
+ default: false
2232
+ },
2233
+ reset: {
2234
+ type: "boolean",
2235
+ description: "Delete .kora/deploy state and generated artifacts",
2236
+ default: false
2237
+ }
2238
+ },
2239
+ subCommands: {
2240
+ status: defineCommand2({
2241
+ meta: {
2242
+ name: "status",
2243
+ description: "Show current deployment status"
2244
+ },
2245
+ async run() {
2246
+ const logger = createLogger();
2247
+ const projectRoot = await requireProjectRoot();
2248
+ const state = await readDeployState(projectRoot);
2249
+ if (!state) {
2250
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2251
+ return;
2252
+ }
2253
+ logger.banner();
2254
+ logger.info(`Platform: ${state.platform}`);
2255
+ logger.step(`App: ${state.appName}`);
2256
+ logger.step(`Region: ${state.region ?? "n/a"}`);
2257
+ logger.step(`Last deployment: ${state.lastDeploymentId ?? "n/a"}`);
2258
+ const adapter = createDeployAdapter(state.platform);
2259
+ configureAdapterContext(adapter, {
2260
+ projectRoot,
2261
+ appName: state.appName,
2262
+ region: state.region
2263
+ });
2264
+ const adapterStatus = await adapter.status();
2265
+ logger.step(`Status: ${adapterStatus.state}`);
2266
+ logger.step(`Message: ${adapterStatus.message}`);
2267
+ logger.step(`Live URL: ${adapterStatus.liveUrl ?? state.liveUrl ?? "n/a"}`);
2268
+ logger.step(`Sync URL: ${state.syncUrl ?? "n/a"}`);
2269
+ }
2270
+ }),
2271
+ rollback: defineCommand2({
2272
+ meta: {
2273
+ name: "rollback",
2274
+ description: "Rollback the current deployment"
2275
+ },
2276
+ args: {
2277
+ id: {
2278
+ type: "positional",
2279
+ description: "Optional deployment identifier",
2280
+ required: false
2281
+ }
2282
+ },
2283
+ async run({ args }) {
2284
+ const logger = createLogger();
2285
+ const projectRoot = await requireProjectRoot();
2286
+ const state = await readDeployState(projectRoot);
2287
+ if (!state) {
2288
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2289
+ return;
2290
+ }
2291
+ const adapter = createDeployAdapter(state.platform);
2292
+ configureAdapterContext(adapter, {
2293
+ projectRoot,
2294
+ appName: state.appName,
2295
+ region: state.region
2296
+ });
2297
+ const deploymentId = typeof args.id === "string" && args.id.length > 0 ? args.id : state.lastDeploymentId ?? "latest";
2298
+ await adapter.rollback(deploymentId);
2299
+ logger.success(`Rolled back ${state.platform} deployment to ${deploymentId}.`);
2300
+ }
2301
+ }),
2302
+ logs: defineCommand2({
2303
+ meta: {
2304
+ name: "logs",
2305
+ description: "Read deployment logs"
2306
+ },
2307
+ async run() {
2308
+ const logger = createLogger();
2309
+ const projectRoot = await requireProjectRoot();
2310
+ const state = await readDeployState(projectRoot);
2311
+ if (!state) {
2312
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2313
+ return;
2314
+ }
2315
+ const adapter = createDeployAdapter(state.platform);
2316
+ configureAdapterContext(adapter, {
2317
+ projectRoot,
2318
+ appName: state.appName,
2319
+ region: state.region
2320
+ });
2321
+ const logLines = adapter.logs({ tail: 200 });
2322
+ let hasLines = false;
2323
+ for await (const line of logLines) {
2324
+ hasLines = true;
2325
+ logger.step(`[${line.level}] ${line.message}`);
2326
+ }
2327
+ if (!hasLines) {
2328
+ logger.warn(`No logs returned from ${state.platform}.`);
2329
+ }
2330
+ }
2331
+ })
2332
+ },
2333
+ async run({ args, rawArgs }) {
2334
+ const subCommandNames = ["status", "rollback", "logs"];
2335
+ if (rawArgs.some((arg) => subCommandNames.includes(arg))) {
2336
+ return;
2337
+ }
2338
+ const logger = createLogger();
2339
+ const prompts = createPromptClient();
2340
+ const projectRoot = await requireProjectRoot();
2341
+ if (args.reset === true) {
2342
+ await resetDeployState(projectRoot);
2343
+ logger.success("Reset .kora/deploy state.");
2344
+ return;
2345
+ }
2346
+ const existingState = await readDeployState(projectRoot);
2347
+ const confirmMode = args.confirm === true;
2348
+ const platform = await resolvePlatform({
2349
+ promptClient: prompts,
2350
+ platformArg: args.platform,
2351
+ storedPlatform: existingState?.platform,
2352
+ confirm: confirmMode
2353
+ });
2354
+ if (!isImplementedDeployPlatform(platform)) {
2355
+ throw new DeployPlatformUnavailableError(platform, IMPLEMENTED_DEPLOY_PLATFORMS);
2356
+ }
2357
+ const appName = resolveAppName(args.app, existingState?.appName, projectRoot, confirmMode);
2358
+ const region = resolveRegion(args.region, existingState?.region, confirmMode);
2359
+ const deployDirectory = resolveDeployDirectory(projectRoot);
2360
+ const environment = args.prod === true ? "production" : "preview";
2361
+ const deployTarget = detectDeployTarget(projectRoot);
2362
+ const config = {
2363
+ projectRoot,
2364
+ appName,
2365
+ region,
2366
+ environment,
2367
+ confirm: confirmMode,
2368
+ deployTarget
2369
+ };
2370
+ const adapter = createDeployAdapter(platform);
2371
+ configureAdapterContext(adapter, {
2372
+ projectRoot,
2373
+ appName,
2374
+ region
2375
+ });
2376
+ logger.banner();
2377
+ logger.info(
2378
+ `Deploying to ${platform} (${appName}${region ? `, ${region}` : ""}, ${environment})`
2379
+ );
2380
+ if (deployTarget === "sync-server") {
2381
+ logger.step("Detected Tauri project. Deploying the sync server only.");
2382
+ }
2383
+ if (confirmMode) {
2384
+ logger.step("Running in --confirm mode (non-interactive, fail-fast).");
2385
+ }
2386
+ await writeDockerfileArtifact(deployDirectory, {
2387
+ clientDirectory: deployTarget === "sync-server" ? null : void 0,
2388
+ nativeDependencies: {
2389
+ "better-sqlite3": "^11.0.0",
2390
+ "drizzle-orm": "^0.45.2",
2391
+ postgres: "^3.4.0"
2392
+ }
2393
+ });
2394
+ await writeDockerIgnoreArtifact(deployDirectory);
2395
+ const detected = await adapter.detect();
2396
+ if (!detected) {
2397
+ await adapter.install();
2398
+ }
2399
+ await adapter.authenticate();
2400
+ const provisionResult = await adapter.provision(config);
2401
+ const artifacts = await adapter.build(config);
2402
+ const deployResult = await adapter.deploy(artifacts);
2403
+ if (existingState) {
2404
+ await updateDeployState(projectRoot, {
2405
+ platform,
2406
+ appName,
2407
+ region,
2408
+ projectRoot,
2409
+ liveUrl: deployResult.liveUrl,
2410
+ syncUrl: deployResult.syncUrl,
2411
+ databaseId: provisionResult.databaseId,
2412
+ lastDeploymentId: deployResult.deploymentId
2413
+ });
2414
+ } else {
2415
+ await writeDeployState(projectRoot, {
2416
+ platform,
2417
+ appName,
2418
+ region,
2419
+ projectRoot,
2420
+ liveUrl: deployResult.liveUrl,
2421
+ syncUrl: deployResult.syncUrl,
2422
+ databaseId: provisionResult.databaseId,
2423
+ lastDeploymentId: deployResult.deploymentId
2424
+ });
2425
+ }
2426
+ logger.success(`Deployment completed: ${deployResult.liveUrl}`);
2427
+ if (deployResult.syncUrl) {
2428
+ logger.step(`Sync endpoint: ${deployResult.syncUrl}`);
2429
+ }
2430
+ }
2431
+ });
2432
+ async function resolvePlatform(options) {
2433
+ if (typeof options.platformArg === "string") {
2434
+ if (!isDeployPlatform(options.platformArg)) {
2435
+ throw new Error(
2436
+ `Invalid --platform value "${options.platformArg}". Valid options: ${DEPLOY_PLATFORMS.join(", ")}`
2437
+ );
2438
+ }
2439
+ return options.platformArg;
2440
+ }
2441
+ if (options.storedPlatform) {
2442
+ return options.storedPlatform;
2443
+ }
2444
+ if (options.confirm || !isInteractiveTerminal()) {
2445
+ throw new Error(
2446
+ "Missing deploy platform in --confirm mode. Provide --platform or run an interactive deploy first."
2447
+ );
2448
+ }
2449
+ return await options.promptClient.select("Where do you want to deploy?", [
2450
+ {
2451
+ label: "Fly.io (recommended for sync apps)",
2452
+ value: "fly"
2453
+ },
2454
+ {
2455
+ label: "Railway",
2456
+ value: "railway"
2457
+ },
2458
+ {
2459
+ label: "AWS ECS Fargate (production multi-instance)",
2460
+ value: "aws-ecs"
2461
+ },
2462
+ {
2463
+ label: "AWS Lightsail Containers (simple, cost-effective)",
2464
+ value: "aws-lightsail"
2465
+ },
2466
+ {
2467
+ label: "Render (coming soon)",
2468
+ value: "render"
2469
+ },
2470
+ {
2471
+ label: "Docker (self-hosted, coming soon)",
2472
+ value: "docker"
2473
+ },
2474
+ {
2475
+ label: "Kora Cloud (coming soon)",
2476
+ value: "kora-cloud"
2477
+ }
2478
+ ]);
2479
+ }
2480
+ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
2481
+ if (typeof argValue === "string" && argValue.length > 0) {
2482
+ return sanitizeAppName(argValue);
2483
+ }
2484
+ if (storedValue && storedValue.length > 0) {
2485
+ return storedValue;
2486
+ }
2487
+ if (confirm) {
2488
+ throw new Error(
2489
+ "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
2490
+ );
2491
+ }
2492
+ return sanitizeAppName(basename(projectRoot));
2493
+ }
2494
+ function resolveRegion(argValue, storedValue, confirm) {
2495
+ if (typeof argValue === "string" && argValue.length > 0) return argValue;
2496
+ if (storedValue !== void 0) return storedValue;
2497
+ if (confirm) {
2498
+ throw new Error(
2499
+ "Missing region in --confirm mode. Provide --region or run an interactive deploy first."
2500
+ );
2501
+ }
2502
+ return "iad";
2503
+ }
2504
+ function sanitizeAppName(value) {
2505
+ const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
2506
+ if (normalized.length === 0) {
2507
+ return "kora-app";
2508
+ }
2509
+ return normalized;
2510
+ }
2511
+ async function requireProjectRoot() {
2512
+ const projectRoot = await findProjectRoot();
2513
+ if (!projectRoot) {
2514
+ throw new InvalidProjectError(process.cwd());
2515
+ }
2516
+ return projectRoot;
2517
+ }
2518
+ function isInteractiveTerminal() {
2519
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
2520
+ }
2521
+ function configureAdapterContext(adapter, context) {
2522
+ if (hasContextSetter(adapter)) {
2523
+ adapter.setContext(context);
2524
+ }
2525
+ }
2526
+ function detectDeployTarget(projectRoot) {
2527
+ return existsSync2(join12(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
2528
+ }
2529
+ function hasContextSetter(adapter) {
2530
+ return typeof adapter.setContext === "function";
2531
+ }
2532
+
2533
+ export {
2534
+ AGENTS_FRAMEWORKS,
2535
+ isAgentsFramework,
2536
+ detectFrameworkFromManifest,
2537
+ generateAgentsMd,
2538
+ agentsCommand,
2539
+ generateTypes,
2540
+ DEPLOY_PLATFORMS,
2541
+ IMPLEMENTED_DEPLOY_PLATFORMS,
2542
+ isImplementedDeployPlatform,
2543
+ isDeployPlatform,
2544
+ buildClient,
2545
+ bundleServer,
2546
+ generateFlyToml,
2547
+ writeFlyTomlArtifact,
2548
+ FlyAdapter,
2549
+ NodeFlyCommandRunner,
2550
+ generateRailwayJson,
2551
+ writeRailwayJsonArtifact,
2552
+ RailwayAdapter,
2553
+ NodeRailwayCommandRunner,
2554
+ StubDeployAdapter,
2555
+ createDeployAdapter,
2556
+ generateDockerfile,
2557
+ generateDeployPackageJson,
2558
+ generateDockerIgnore,
2559
+ writeDockerfileArtifact,
2560
+ writeDockerIgnoreArtifact,
2561
+ resolveDeployDirectory,
2562
+ resolveDeployStatePath,
2563
+ readDeployState,
2564
+ writeDeployState,
2565
+ updateDeployState,
2566
+ resetDeployState,
2567
+ deployCommand
2568
+ };
2569
+ //# sourceMappingURL=chunk-TVIRMDER.js.map