@supatype/cli 0.1.0-alpha.10

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 (416) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +221 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/assets/supatype-logo-wordmark.ascii.txt +6 -0
  5. package/bin/dev-entry.ts +2 -0
  6. package/bin/supatype.js +5 -0
  7. package/dist/app/framework.d.ts +44 -0
  8. package/dist/app/framework.d.ts.map +1 -0
  9. package/dist/app/framework.js +200 -0
  10. package/dist/app/framework.js.map +1 -0
  11. package/dist/app/proxy-dev-app.d.ts +13 -0
  12. package/dist/app/proxy-dev-app.d.ts.map +1 -0
  13. package/dist/app/proxy-dev-app.js +54 -0
  14. package/dist/app/proxy-dev-app.js.map +1 -0
  15. package/dist/app-config.d.ts +7 -0
  16. package/dist/app-config.d.ts.map +1 -0
  17. package/dist/app-config.js +113 -0
  18. package/dist/app-config.js.map +1 -0
  19. package/dist/assets/supatype-logo-wordmark.ascii.txt +6 -0
  20. package/dist/augmentation-generator.d.ts +2 -0
  21. package/dist/augmentation-generator.d.ts.map +1 -0
  22. package/dist/augmentation-generator.js +111 -0
  23. package/dist/augmentation-generator.js.map +1 -0
  24. package/dist/binary-cache.d.ts +98 -0
  25. package/dist/binary-cache.d.ts.map +1 -0
  26. package/dist/binary-cache.js +687 -0
  27. package/dist/binary-cache.js.map +1 -0
  28. package/dist/cli.d.ts +2 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +61 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/commands/admin.d.ts +4 -0
  33. package/dist/commands/admin.d.ts.map +1 -0
  34. package/dist/commands/admin.js +271 -0
  35. package/dist/commands/admin.js.map +1 -0
  36. package/dist/commands/app.d.ts +3 -0
  37. package/dist/commands/app.d.ts.map +1 -0
  38. package/dist/commands/app.js +82 -0
  39. package/dist/commands/app.js.map +1 -0
  40. package/dist/commands/cache.d.ts +6 -0
  41. package/dist/commands/cache.d.ts.map +1 -0
  42. package/dist/commands/cache.js +105 -0
  43. package/dist/commands/cache.js.map +1 -0
  44. package/dist/commands/cloud.d.ts +23 -0
  45. package/dist/commands/cloud.d.ts.map +1 -0
  46. package/dist/commands/cloud.js +254 -0
  47. package/dist/commands/cloud.js.map +1 -0
  48. package/dist/commands/db.d.ts +8 -0
  49. package/dist/commands/db.d.ts.map +1 -0
  50. package/dist/commands/db.js +116 -0
  51. package/dist/commands/db.js.map +1 -0
  52. package/dist/commands/deploy-types.d.ts +14 -0
  53. package/dist/commands/deploy-types.d.ts.map +1 -0
  54. package/dist/commands/deploy-types.js +38 -0
  55. package/dist/commands/deploy-types.js.map +1 -0
  56. package/dist/commands/deploy.d.ts +15 -0
  57. package/dist/commands/deploy.d.ts.map +1 -0
  58. package/dist/commands/deploy.js +322 -0
  59. package/dist/commands/deploy.js.map +1 -0
  60. package/dist/commands/dev.d.ts +14 -0
  61. package/dist/commands/dev.d.ts.map +1 -0
  62. package/dist/commands/dev.js +806 -0
  63. package/dist/commands/dev.js.map +1 -0
  64. package/dist/commands/diff.d.ts +3 -0
  65. package/dist/commands/diff.d.ts.map +1 -0
  66. package/dist/commands/diff.js +54 -0
  67. package/dist/commands/diff.js.map +1 -0
  68. package/dist/commands/engine.d.ts +7 -0
  69. package/dist/commands/engine.d.ts.map +1 -0
  70. package/dist/commands/engine.js +27 -0
  71. package/dist/commands/engine.js.map +1 -0
  72. package/dist/commands/functions.d.ts +3 -0
  73. package/dist/commands/functions.d.ts.map +1 -0
  74. package/dist/commands/functions.js +749 -0
  75. package/dist/commands/functions.js.map +1 -0
  76. package/dist/commands/generate.d.ts +3 -0
  77. package/dist/commands/generate.d.ts.map +1 -0
  78. package/dist/commands/generate.js +38 -0
  79. package/dist/commands/generate.js.map +1 -0
  80. package/dist/commands/init.d.ts +7 -0
  81. package/dist/commands/init.d.ts.map +1 -0
  82. package/dist/commands/init.js +228 -0
  83. package/dist/commands/init.js.map +1 -0
  84. package/dist/commands/keys.d.ts +4 -0
  85. package/dist/commands/keys.d.ts.map +1 -0
  86. package/dist/commands/keys.js +57 -0
  87. package/dist/commands/keys.js.map +1 -0
  88. package/dist/commands/logs.d.ts +6 -0
  89. package/dist/commands/logs.d.ts.map +1 -0
  90. package/dist/commands/logs.js +52 -0
  91. package/dist/commands/logs.js.map +1 -0
  92. package/dist/commands/migrate-from-v1.d.ts +5 -0
  93. package/dist/commands/migrate-from-v1.d.ts.map +1 -0
  94. package/dist/commands/migrate-from-v1.js +125 -0
  95. package/dist/commands/migrate-from-v1.js.map +1 -0
  96. package/dist/commands/migrate.d.ts +3 -0
  97. package/dist/commands/migrate.d.ts.map +1 -0
  98. package/dist/commands/migrate.js +75 -0
  99. package/dist/commands/migrate.js.map +1 -0
  100. package/dist/commands/pg.d.ts +8 -0
  101. package/dist/commands/pg.d.ts.map +1 -0
  102. package/dist/commands/pg.js +102 -0
  103. package/dist/commands/pg.js.map +1 -0
  104. package/dist/commands/plugins.d.ts +3 -0
  105. package/dist/commands/plugins.d.ts.map +1 -0
  106. package/dist/commands/plugins.js +431 -0
  107. package/dist/commands/plugins.js.map +1 -0
  108. package/dist/commands/pull.d.ts +3 -0
  109. package/dist/commands/pull.d.ts.map +1 -0
  110. package/dist/commands/pull.js +12 -0
  111. package/dist/commands/pull.js.map +1 -0
  112. package/dist/commands/push.d.ts +3 -0
  113. package/dist/commands/push.d.ts.map +1 -0
  114. package/dist/commands/push.js +179 -0
  115. package/dist/commands/push.js.map +1 -0
  116. package/dist/commands/seed.d.ts +5 -0
  117. package/dist/commands/seed.d.ts.map +1 -0
  118. package/dist/commands/seed.js +55 -0
  119. package/dist/commands/seed.js.map +1 -0
  120. package/dist/commands/self-host.d.ts +9 -0
  121. package/dist/commands/self-host.d.ts.map +1 -0
  122. package/dist/commands/self-host.js +310 -0
  123. package/dist/commands/self-host.js.map +1 -0
  124. package/dist/commands/self-update.d.ts +9 -0
  125. package/dist/commands/self-update.d.ts.map +1 -0
  126. package/dist/commands/self-update.js +33 -0
  127. package/dist/commands/self-update.js.map +1 -0
  128. package/dist/commands/status.d.ts +6 -0
  129. package/dist/commands/status.d.ts.map +1 -0
  130. package/dist/commands/status.js +70 -0
  131. package/dist/commands/status.js.map +1 -0
  132. package/dist/commands/types.d.ts +3 -0
  133. package/dist/commands/types.d.ts.map +1 -0
  134. package/dist/commands/types.js +62 -0
  135. package/dist/commands/types.js.map +1 -0
  136. package/dist/commands/update.d.ts +7 -0
  137. package/dist/commands/update.d.ts.map +1 -0
  138. package/dist/commands/update.js +118 -0
  139. package/dist/commands/update.js.map +1 -0
  140. package/dist/components.d.ts +5 -0
  141. package/dist/components.d.ts.map +1 -0
  142. package/dist/components.js +3 -0
  143. package/dist/components.js.map +1 -0
  144. package/dist/config.d.ts +65 -0
  145. package/dist/config.d.ts.map +1 -0
  146. package/dist/config.js +134 -0
  147. package/dist/config.js.map +1 -0
  148. package/dist/dev-compose.d.ts +19 -0
  149. package/dist/dev-compose.d.ts.map +1 -0
  150. package/dist/dev-compose.js +468 -0
  151. package/dist/dev-compose.js.map +1 -0
  152. package/dist/dev-log-bus.d.ts +30 -0
  153. package/dist/dev-log-bus.d.ts.map +1 -0
  154. package/dist/dev-log-bus.js +87 -0
  155. package/dist/dev-log-bus.js.map +1 -0
  156. package/dist/dev-log-filter.d.ts +10 -0
  157. package/dist/dev-log-filter.d.ts.map +1 -0
  158. package/dist/dev-log-filter.js +36 -0
  159. package/dist/dev-log-filter.js.map +1 -0
  160. package/dist/dev-logo.d.ts +12 -0
  161. package/dist/dev-logo.d.ts.map +1 -0
  162. package/dist/dev-logo.js +57 -0
  163. package/dist/dev-logo.js.map +1 -0
  164. package/dist/dev-session.d.ts +26 -0
  165. package/dist/dev-session.d.ts.map +1 -0
  166. package/dist/dev-session.js +106 -0
  167. package/dist/dev-session.js.map +1 -0
  168. package/dist/dev-shutdown.d.ts +9 -0
  169. package/dist/dev-shutdown.d.ts.map +1 -0
  170. package/dist/dev-shutdown.js +50 -0
  171. package/dist/dev-shutdown.js.map +1 -0
  172. package/dist/dev-task-colors.d.ts +14 -0
  173. package/dist/dev-task-colors.d.ts.map +1 -0
  174. package/dist/dev-task-colors.js +44 -0
  175. package/dist/dev-task-colors.js.map +1 -0
  176. package/dist/dev-tui.d.ts +24 -0
  177. package/dist/dev-tui.d.ts.map +1 -0
  178. package/dist/dev-tui.js +188 -0
  179. package/dist/dev-tui.js.map +1 -0
  180. package/dist/diff-output.d.ts +4 -0
  181. package/dist/diff-output.d.ts.map +1 -0
  182. package/dist/diff-output.js +12 -0
  183. package/dist/diff-output.js.map +1 -0
  184. package/dist/docker-postgres.d.ts +57 -0
  185. package/dist/docker-postgres.d.ts.map +1 -0
  186. package/dist/docker-postgres.js +208 -0
  187. package/dist/docker-postgres.js.map +1 -0
  188. package/dist/engine-client.d.ts +69 -0
  189. package/dist/engine-client.d.ts.map +1 -0
  190. package/dist/engine-client.js +157 -0
  191. package/dist/engine-client.js.map +1 -0
  192. package/dist/engine-push-output.d.ts +16 -0
  193. package/dist/engine-push-output.d.ts.map +1 -0
  194. package/dist/engine-push-output.js +61 -0
  195. package/dist/engine-push-output.js.map +1 -0
  196. package/dist/ensure-binary.d.ts +7 -0
  197. package/dist/ensure-binary.d.ts.map +1 -0
  198. package/dist/ensure-binary.js +17 -0
  199. package/dist/ensure-binary.js.map +1 -0
  200. package/dist/functions-router-gen.d.ts +14 -0
  201. package/dist/functions-router-gen.d.ts.map +1 -0
  202. package/dist/functions-router-gen.js +199 -0
  203. package/dist/functions-router-gen.js.map +1 -0
  204. package/dist/index.d.ts +11 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +9 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/jwt.d.ts +3 -0
  209. package/dist/jwt.d.ts.map +1 -0
  210. package/dist/jwt.js +13 -0
  211. package/dist/jwt.js.map +1 -0
  212. package/dist/kong-config.d.ts +25 -0
  213. package/dist/kong-config.d.ts.map +1 -0
  214. package/dist/kong-config.js +71 -0
  215. package/dist/kong-config.js.map +1 -0
  216. package/dist/local-gateway.d.ts +7 -0
  217. package/dist/local-gateway.d.ts.map +1 -0
  218. package/dist/local-gateway.js +9 -0
  219. package/dist/local-gateway.js.map +1 -0
  220. package/dist/local-storage.d.ts +8 -0
  221. package/dist/local-storage.d.ts.map +1 -0
  222. package/dist/local-storage.js +14 -0
  223. package/dist/local-storage.js.map +1 -0
  224. package/dist/pgbouncer-userlist.d.ts +5 -0
  225. package/dist/pgbouncer-userlist.d.ts.map +1 -0
  226. package/dist/pgbouncer-userlist.js +14 -0
  227. package/dist/pgbouncer-userlist.js.map +1 -0
  228. package/dist/postgres-ctl.d.ts +44 -0
  229. package/dist/postgres-ctl.d.ts.map +1 -0
  230. package/dist/postgres-ctl.js +137 -0
  231. package/dist/postgres-ctl.js.map +1 -0
  232. package/dist/process-manager.d.ts +49 -0
  233. package/dist/process-manager.d.ts.map +1 -0
  234. package/dist/process-manager.js +177 -0
  235. package/dist/process-manager.js.map +1 -0
  236. package/dist/project-config.d.ts +238 -0
  237. package/dist/project-config.d.ts.map +1 -0
  238. package/dist/project-config.js +159 -0
  239. package/dist/project-config.js.map +1 -0
  240. package/dist/pull-utils.d.ts +31 -0
  241. package/dist/pull-utils.d.ts.map +1 -0
  242. package/dist/pull-utils.js +77 -0
  243. package/dist/pull-utils.js.map +1 -0
  244. package/dist/release-pins.d.ts +7 -0
  245. package/dist/release-pins.d.ts.map +1 -0
  246. package/dist/release-pins.js +27 -0
  247. package/dist/release-pins.js.map +1 -0
  248. package/dist/release-public-key.d.ts +8 -0
  249. package/dist/release-public-key.d.ts.map +1 -0
  250. package/dist/release-public-key.js +13 -0
  251. package/dist/release-public-key.js.map +1 -0
  252. package/dist/restore-system-relation-targets.d.ts +3 -0
  253. package/dist/restore-system-relation-targets.d.ts.map +1 -0
  254. package/dist/restore-system-relation-targets.js +45 -0
  255. package/dist/restore-system-relation-targets.js.map +1 -0
  256. package/dist/runtime-routes.d.ts +34 -0
  257. package/dist/runtime-routes.d.ts.map +1 -0
  258. package/dist/runtime-routes.js +252 -0
  259. package/dist/runtime-routes.js.map +1 -0
  260. package/dist/schema-ast-v2.d.ts +127 -0
  261. package/dist/schema-ast-v2.d.ts.map +1 -0
  262. package/dist/schema-ast-v2.js +226 -0
  263. package/dist/schema-ast-v2.js.map +1 -0
  264. package/dist/scripts/postinstall.d.ts +11 -0
  265. package/dist/scripts/postinstall.d.ts.map +1 -0
  266. package/dist/scripts/postinstall.js +47 -0
  267. package/dist/scripts/postinstall.js.map +1 -0
  268. package/dist/seed.d.ts +8 -0
  269. package/dist/seed.d.ts.map +1 -0
  270. package/dist/seed.js +32 -0
  271. package/dist/seed.js.map +1 -0
  272. package/dist/self-host-compose.d.ts +43 -0
  273. package/dist/self-host-compose.d.ts.map +1 -0
  274. package/dist/self-host-compose.js +400 -0
  275. package/dist/self-host-compose.js.map +1 -0
  276. package/dist/storage-provision.d.ts +24 -0
  277. package/dist/storage-provision.d.ts.map +1 -0
  278. package/dist/storage-provision.js +44 -0
  279. package/dist/storage-provision.js.map +1 -0
  280. package/dist/studio-admin-roles.d.ts +7 -0
  281. package/dist/studio-admin-roles.d.ts.map +1 -0
  282. package/dist/studio-admin-roles.js +14 -0
  283. package/dist/studio-admin-roles.js.map +1 -0
  284. package/dist/studio-dev-server.d.ts +22 -0
  285. package/dist/studio-dev-server.d.ts.map +1 -0
  286. package/dist/studio-dev-server.js +28 -0
  287. package/dist/studio-dev-server.js.map +1 -0
  288. package/dist/supatype-eval-1781522769253.d.mts +2 -0
  289. package/dist/supatype-eval-1781522769253.d.mts.map +1 -0
  290. package/dist/supatype-eval-1781522769253.mjs +3 -0
  291. package/dist/supatype-eval-1781522769253.mjs.map +1 -0
  292. package/dist/systemd.d.ts +26 -0
  293. package/dist/systemd.d.ts.map +1 -0
  294. package/dist/systemd.js +102 -0
  295. package/dist/systemd.js.map +1 -0
  296. package/dist/tsx-runner.d.ts +18 -0
  297. package/dist/tsx-runner.d.ts.map +1 -0
  298. package/dist/tsx-runner.js +69 -0
  299. package/dist/tsx-runner.js.map +1 -0
  300. package/dist/type-extractor.d.ts +4 -0
  301. package/dist/type-extractor.d.ts.map +1 -0
  302. package/dist/type-extractor.js +1213 -0
  303. package/dist/type-extractor.js.map +1 -0
  304. package/dist/type-resolver.d.ts +33 -0
  305. package/dist/type-resolver.d.ts.map +1 -0
  306. package/dist/type-resolver.js +338 -0
  307. package/dist/type-resolver.js.map +1 -0
  308. package/package.json +41 -0
  309. package/releases/deno/VERSION +1 -0
  310. package/scripts/mirror-deno-release.sh +76 -0
  311. package/src/TYPE-RESOLUTION.md +294 -0
  312. package/src/app/framework.ts +249 -0
  313. package/src/app/proxy-dev-app.ts +68 -0
  314. package/src/app-config.ts +128 -0
  315. package/src/augmentation-generator.ts +126 -0
  316. package/src/binary-cache.ts +845 -0
  317. package/src/cli.ts +63 -0
  318. package/src/commands/admin.ts +372 -0
  319. package/src/commands/app.ts +97 -0
  320. package/src/commands/cache.ts +117 -0
  321. package/src/commands/cloud.ts +325 -0
  322. package/src/commands/db.ts +136 -0
  323. package/src/commands/deploy-types.ts +49 -0
  324. package/src/commands/deploy.ts +400 -0
  325. package/src/commands/dev.ts +1009 -0
  326. package/src/commands/diff.ts +63 -0
  327. package/src/commands/engine.ts +30 -0
  328. package/src/commands/functions.ts +901 -0
  329. package/src/commands/generate.ts +44 -0
  330. package/src/commands/init.ts +253 -0
  331. package/src/commands/keys.ts +66 -0
  332. package/src/commands/logs.ts +58 -0
  333. package/src/commands/migrate-from-v1.ts +131 -0
  334. package/src/commands/migrate.ts +87 -0
  335. package/src/commands/pg.ts +133 -0
  336. package/src/commands/plugins.ts +508 -0
  337. package/src/commands/pull.ts +17 -0
  338. package/src/commands/push.ts +226 -0
  339. package/src/commands/seed.ts +68 -0
  340. package/src/commands/self-host.ts +364 -0
  341. package/src/commands/self-update.ts +45 -0
  342. package/src/commands/status.ts +84 -0
  343. package/src/commands/types.ts +76 -0
  344. package/src/commands/update.ts +136 -0
  345. package/src/components.ts +6 -0
  346. package/src/config.ts +223 -0
  347. package/src/dev-compose.ts +583 -0
  348. package/src/dev-log-bus.ts +101 -0
  349. package/src/dev-log-filter.ts +32 -0
  350. package/src/dev-logo.ts +62 -0
  351. package/src/dev-session.ts +130 -0
  352. package/src/dev-shutdown.ts +54 -0
  353. package/src/dev-task-colors.ts +47 -0
  354. package/src/dev-tui.ts +232 -0
  355. package/src/diff-output.ts +12 -0
  356. package/src/docker-postgres.ts +295 -0
  357. package/src/engine-client.ts +236 -0
  358. package/src/engine-push-output.ts +71 -0
  359. package/src/ensure-binary.ts +28 -0
  360. package/src/functions-router-gen.ts +224 -0
  361. package/src/index.ts +11 -0
  362. package/src/jwt.ts +14 -0
  363. package/src/kong-config.ts +93 -0
  364. package/src/local-gateway.ts +9 -0
  365. package/src/local-storage.ts +14 -0
  366. package/src/pgbouncer-userlist.ts +15 -0
  367. package/src/postgres-ctl.ts +171 -0
  368. package/src/process-manager.ts +220 -0
  369. package/src/project-config.ts +388 -0
  370. package/src/pull-utils.ts +81 -0
  371. package/src/release-pins.ts +31 -0
  372. package/src/release-public-key.ts +12 -0
  373. package/src/restore-system-relation-targets.ts +45 -0
  374. package/src/runtime-routes.ts +291 -0
  375. package/src/schema-ast-v2.ts +324 -0
  376. package/src/scripts/postinstall.ts +51 -0
  377. package/src/seed.ts +43 -0
  378. package/src/self-host-compose.ts +452 -0
  379. package/src/storage-provision.ts +58 -0
  380. package/src/studio-admin-roles.ts +16 -0
  381. package/src/studio-dev-server.ts +53 -0
  382. package/src/supatype-eval-1781522769253.mts +1 -0
  383. package/src/systemd.ts +137 -0
  384. package/src/tsx-runner.ts +89 -0
  385. package/src/type-extractor.ts +1479 -0
  386. package/src/type-resolver.ts +457 -0
  387. package/tests/app-command.test.ts +54 -0
  388. package/tests/augmentation-generator.test.ts +59 -0
  389. package/tests/binary-cache-cloud-overrides.test.ts +123 -0
  390. package/tests/cached-artifact-format.test.ts +84 -0
  391. package/tests/cli-help.test.ts +133 -0
  392. package/tests/config.test.ts +252 -0
  393. package/tests/dev-ui.test.ts +139 -0
  394. package/tests/docker-postgres.test.ts +39 -0
  395. package/tests/engine-distribution.test.ts +418 -0
  396. package/tests/engine-push-output.test.ts +67 -0
  397. package/tests/ensure-binary.test.ts +59 -0
  398. package/tests/init.test.ts +127 -0
  399. package/tests/keys.test.ts +160 -0
  400. package/tests/migrate-from-v1.test.ts +29 -0
  401. package/tests/normalize-admin-config.test.ts +48 -0
  402. package/tests/pg-spawn-env.test.ts +18 -0
  403. package/tests/postgres-archive-tag.test.ts +9 -0
  404. package/tests/proxy-dev-app.test.ts +33 -0
  405. package/tests/pull-utils.test.ts +150 -0
  406. package/tests/release-pins.test.ts +28 -0
  407. package/tests/runtime-contract.test.ts +370 -0
  408. package/tests/seed-discover.test.ts +31 -0
  409. package/tests/studio-admin-roles.test.ts +27 -0
  410. package/tests/tsconfig.json +9 -0
  411. package/tests/tsx-runner.test.ts +66 -0
  412. package/tests/type-extractor.test.ts +985 -0
  413. package/tests/type-resolver.test.ts +59 -0
  414. package/tsconfig.json +10 -0
  415. package/tsconfig.tsbuildinfo +1 -0
  416. package/vitest.config.ts +12 -0
@@ -0,0 +1,901 @@
1
+ import type { Command } from "commander"
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ statSync,
8
+ writeFileSync,
9
+ unlinkSync,
10
+ } from "node:fs"
11
+ import { resolve, join, basename, relative, isAbsolute } from "node:path"
12
+ import { spawnSync, execSync } from "node:child_process"
13
+ import { localKongBaseUrl } from "../local-gateway.js"
14
+ import { loadConfig } from "../config.js"
15
+ import { ensureBinary } from "../ensure-binary.js"
16
+ import {
17
+ functionsPathCandidatesFromProject,
18
+ preferredFunctionsPathFromProject,
19
+ } from "../project-config.js"
20
+ import {
21
+ discoverTsFunctionsInDir,
22
+ generateFunctionsRouterSource,
23
+ } from "../functions-router-gen.js"
24
+ import { selfHostComposePaths } from "../self-host-compose.js"
25
+
26
+ // ─── Constants ───────────────────────────────────────────────────────────────
27
+
28
+ const SHARED_DIR = "_shared"
29
+ const ENV_LOCAL = ".env.local"
30
+ const ENV_PRODUCTION = ".env.production"
31
+
32
+ // ─── Registration ────────────────────────────────────────────────────────────
33
+
34
+ export function registerFunctions(program: Command): void {
35
+ const fnCmd = program
36
+ .command("functions")
37
+ .description("Manage Supatype Edge Functions (Deno-based serverless TypeScript)")
38
+
39
+ fnCmd
40
+ .command("new <name>")
41
+ .description("Scaffold a new edge function")
42
+ .action((name: string) => {
43
+ scaffoldFunction(process.cwd(), name)
44
+ })
45
+
46
+ fnCmd
47
+ .command("serve")
48
+ .description("Start a local Deno server that serves all functions with hot reload")
49
+ .option("--port <port>", "Port to serve on", "54321")
50
+ .option("--env-file <path>", "Path to env file", ENV_LOCAL)
51
+ .action(async (opts: { port: string; envFile: string }) => {
52
+ await serve(process.cwd(), opts)
53
+ })
54
+
55
+ fnCmd
56
+ .command("deploy")
57
+ .description("Deploy all functions (or --only <name> for one) to the linked project")
58
+ .option("--only <name>", "Deploy a single function")
59
+ .option("--dry-run", "Show what would be deployed without deploying")
60
+ .action(async (opts: { only?: string; dryRun?: boolean }) => {
61
+ await deploy(process.cwd(), opts)
62
+ })
63
+
64
+ fnCmd
65
+ .command("list")
66
+ .description("List all deployed functions for the linked project")
67
+ .action(async () => {
68
+ await listFunctions(process.cwd())
69
+ })
70
+
71
+ fnCmd
72
+ .command("delete <name>")
73
+ .description("Remove a deployed function")
74
+ .action(async (name: string) => {
75
+ await deleteFunction(process.cwd(), name)
76
+ })
77
+
78
+ fnCmd
79
+ .command("logs <name>")
80
+ .description("Tail logs for a deployed function")
81
+ .option("--since <duration>", "Show logs since duration (e.g. 1h, 30m)", "1h")
82
+ .action(async (name: string, opts: { since: string }) => {
83
+ await functionLogs(process.cwd(), name, opts)
84
+ })
85
+
86
+ fnCmd
87
+ .command("invoke <name>")
88
+ .description("Invoke a local or deployed function")
89
+ .option("--data <json>", "JSON body to send", "{}")
90
+ .option("--auth", "Include a test JWT in the request")
91
+ .option("--local", "Invoke the local dev server (default if serve is running)")
92
+ .action(async (name: string, opts: { data: string; auth?: boolean; local?: boolean }) => {
93
+ await invoke(process.cwd(), name, opts)
94
+ })
95
+
96
+ const envCmd = fnCmd
97
+ .command("env")
98
+ .description("Manage function environment variables")
99
+
100
+ envCmd
101
+ .command("list")
102
+ .description("List environment variables (values masked)")
103
+ .action(async () => {
104
+ await envList(process.cwd())
105
+ })
106
+
107
+ envCmd
108
+ .command("set <keyvalue>")
109
+ .description("Set an environment variable (KEY=value)")
110
+ .action(async (keyvalue: string) => {
111
+ await envSet(process.cwd(), keyvalue)
112
+ })
113
+
114
+ envCmd
115
+ .command("unset <key>")
116
+ .description("Remove an environment variable")
117
+ .action(async (key: string) => {
118
+ await envUnset(process.cwd(), key)
119
+ })
120
+ }
121
+
122
+ // ─── Scaffold ────────────────────────────────────────────────────────────────
123
+
124
+ function scaffoldFunction(cwd: string, name: string): void {
125
+ const functionsDir = resolveFunctionsDir(cwd, "write")
126
+ const fnDir = resolve(functionsDir, name)
127
+ if (existsSync(fnDir)) {
128
+ console.error(`Function "${name}" already exists at ${relative(cwd, fnDir)}`)
129
+ process.exit(1)
130
+ }
131
+
132
+ mkdirSync(fnDir, { recursive: true })
133
+
134
+ const indexContent = `// ${name} — Supatype Edge Function
135
+ // Docs: https://supatype.com/docs/edge-functions
136
+
137
+ export default async function handler(req: Request): Promise<Response> {
138
+ const { method, url } = req
139
+
140
+ // Example: read request body for POST requests
141
+ if (method === "POST") {
142
+ const body = await req.json()
143
+ return new Response(JSON.stringify({ message: "Hello from ${name}!", received: body }), {
144
+ status: 200,
145
+ headers: { "Content-Type": "application/json" },
146
+ })
147
+ }
148
+
149
+ return new Response(JSON.stringify({ message: "Hello from ${name}!" }), {
150
+ status: 200,
151
+ headers: { "Content-Type": "application/json" },
152
+ })
153
+ }
154
+ `
155
+
156
+ writeFileSync(join(fnDir, "index.ts"), indexContent, "utf8")
157
+
158
+ // Ensure _shared directory exists
159
+ const sharedDir = resolve(functionsDir, SHARED_DIR)
160
+ if (!existsSync(sharedDir)) {
161
+ mkdirSync(sharedDir, { recursive: true })
162
+ writeFileSync(
163
+ join(sharedDir, "README.md"),
164
+ "# Shared Code\n\nFiles in `_shared/` are available to all functions via relative imports.\nThis directory is not deployed as a function.\n\nExample: `import { sendEmail } from '../_shared/email.ts'`\n",
165
+ "utf8",
166
+ )
167
+ }
168
+
169
+ // Ensure .env.local exists
170
+ const envLocalPath = resolve(functionsDir, ENV_LOCAL)
171
+ if (!existsSync(envLocalPath)) {
172
+ writeFileSync(
173
+ envLocalPath,
174
+ "# Local environment variables for edge functions\n# These are NOT committed to git\n# Set production env vars via: npx supatype functions env set KEY=value\n",
175
+ "utf8",
176
+ )
177
+ }
178
+
179
+ const functionsDirLabel = relativeFunctionsDir(cwd, functionsDir)
180
+ console.log(`Created function: ${functionsDirLabel}/${name}/index.ts`)
181
+ console.log()
182
+ console.log(" Local dev: npx supatype functions serve")
183
+ console.log(` Invoke: npx supatype functions invoke ${name}`)
184
+ console.log(" Deploy: npx supatype functions deploy")
185
+ }
186
+
187
+ // ─── Discover functions ──────────────────────────────────────────────────────
188
+
189
+ interface DiscoveredFunction {
190
+ name: string
191
+ entrypoint: string
192
+ absPath: string
193
+ }
194
+
195
+ function resolveFunctionsDir(cwd: string, mode: "read" | "write"): string {
196
+ try {
197
+ const cfg = loadConfig(cwd)
198
+ if (mode === "write") {
199
+ return preferredFunctionsPathFromProject(cfg, cwd)
200
+ }
201
+ const candidates = functionsPathCandidatesFromProject(cfg, cwd)
202
+ return candidates.find(dir => existsSync(dir)) ?? candidates[0] ?? resolve(cwd, "functions")
203
+ } catch {
204
+ // Keep commands usable even if config cannot be loaded yet.
205
+ const modern = resolve(cwd, "functions")
206
+ const legacy = resolve(cwd, "supatype/functions")
207
+ if (mode === "write") return existsSync(legacy) ? legacy : modern
208
+ return existsSync(modern) ? modern : legacy
209
+ }
210
+ }
211
+
212
+ function relativeFunctionsDir(cwd: string, functionsDir: string): string {
213
+ const rel = relative(cwd, functionsDir)
214
+ return rel.length > 0 ? rel : "."
215
+ }
216
+
217
+ function resolveEnvFilePath(cwd: string, functionsDir: string, envFile: string): string {
218
+ if (isAbsolute(envFile)) return envFile
219
+ if (envFile.includes("/") || envFile.includes("\\")) return resolve(cwd, envFile)
220
+ return resolve(functionsDir, envFile)
221
+ }
222
+
223
+ function discoverFunctions(cwd: string): DiscoveredFunction[] {
224
+ const functionsDir = resolveFunctionsDir(cwd, "read")
225
+ if (!existsSync(functionsDir)) return []
226
+
227
+ const entries = readdirSync(functionsDir)
228
+ const fns: DiscoveredFunction[] = []
229
+
230
+ for (const entry of entries) {
231
+ if (entry.startsWith("_") || entry.startsWith(".")) continue
232
+
233
+ const fullPath = join(functionsDir, entry)
234
+ const stat = statSync(fullPath)
235
+
236
+ if (stat.isDirectory()) {
237
+ // Directory function — look for index.ts
238
+ const indexPath = join(fullPath, "index.ts")
239
+ if (existsSync(indexPath)) {
240
+ fns.push({ name: entry, entrypoint: indexPath, absPath: fullPath })
241
+ }
242
+ } else if (entry.endsWith(".ts") && !entry.endsWith(".d.ts")) {
243
+ // Single-file function
244
+ const name = basename(entry, ".ts")
245
+ fns.push({ name, entrypoint: fullPath, absPath: fullPath })
246
+ }
247
+ }
248
+
249
+ return fns.sort((a, b) => a.name.localeCompare(b.name))
250
+ }
251
+
252
+ // ─── Serve (local dev) ──────────────────────────────────────────────────────
253
+
254
+ async function serve(cwd: string, opts: { port: string; envFile: string }): Promise<void> {
255
+ const config = loadConfig(cwd)
256
+ const functionsDir = resolveFunctionsDir(cwd, "read")
257
+ const functionsDirLabel = relativeFunctionsDir(cwd, functionsDir)
258
+ const routes = discoverTsFunctionsInDir(functionsDir)
259
+ if (routes.length === 0) {
260
+ console.error(`No functions found in ${functionsDirLabel}/`)
261
+ console.error("Create one with: npx supatype functions new <name>")
262
+ process.exit(1)
263
+ }
264
+
265
+ console.log(`Discovered ${routes.length} function(s):`)
266
+ for (const fn of routes) {
267
+ console.log(` /${fn.name} → ${relative(cwd, fn.entrypoint)}`)
268
+ }
269
+ console.log()
270
+
271
+ // Generate a Deno entry script that routes requests to the correct function
272
+ const routerPath = resolve(functionsDir, ".serve-router.ts")
273
+ const routerScript = generateFunctionsRouterSource(routerPath, routes)
274
+ writeFileSync(routerPath, routerScript, "utf8")
275
+
276
+ const envFilePath = resolveEnvFilePath(cwd, functionsDir, opts.envFile)
277
+ const envArgs: string[] = []
278
+ if (existsSync(envFilePath)) {
279
+ envArgs.push("--env-file", envFilePath)
280
+ }
281
+
282
+ console.log(`Serving functions at http://localhost:${opts.port}/functions/v1/`)
283
+ console.log("Watching for changes...\n")
284
+
285
+ let denoBin: string
286
+ try {
287
+ denoBin = await ensureBinary("deno", config)
288
+ } catch (err) {
289
+ console.error(`[supatype] Could not provision Deno: ${(err as Error).message}`)
290
+ process.exit(1)
291
+ }
292
+
293
+ const result = spawnSync(
294
+ denoBin,
295
+ [
296
+ "run",
297
+ "--allow-net",
298
+ "--allow-env",
299
+ "--allow-read",
300
+ "--watch",
301
+ ...envArgs,
302
+ routerPath,
303
+ ],
304
+ {
305
+ stdio: "inherit",
306
+ cwd,
307
+ env: {
308
+ ...process.env,
309
+ PORT: opts.port,
310
+ SUPATYPE_DENO_FUNCTIONS_DIR: functionsDir,
311
+ SUPATYPE_SHARED_ENV_FILE: resolve(functionsDir, ENV_LOCAL),
312
+ SUPATYPE_URL: process.env["SUPATYPE_URL"] ?? localKongBaseUrl(),
313
+ SUPATYPE_ANON_KEY: process.env["SUPATYPE_ANON_KEY"] ?? "",
314
+ SUPATYPE_SERVICE_ROLE_KEY: process.env["SUPATYPE_SERVICE_ROLE_KEY"] ?? "",
315
+ },
316
+ },
317
+ )
318
+
319
+ // Clean up router script
320
+ try { unlinkSync(routerPath) } catch { /* ignore */ }
321
+
322
+ if (result.status !== 0) {
323
+ console.error("Function server exited with errors.")
324
+ process.exit(result.status ?? 1)
325
+ }
326
+ }
327
+
328
+ // ─── Deploy ──────────────────────────────────────────────────────────────────
329
+
330
+ async function deploy(cwd: string, opts: { only?: string; dryRun?: boolean }): Promise<void> {
331
+ const allFns = discoverFunctions(cwd)
332
+ const fns = opts.only
333
+ ? allFns.filter(f => f.name === opts.only)
334
+ : allFns
335
+
336
+ if (fns.length === 0) {
337
+ const functionsDir = resolveFunctionsDir(cwd, "read")
338
+ const functionsDirLabel = relativeFunctionsDir(cwd, functionsDir)
339
+ if (opts.only) {
340
+ console.error(`Function "${opts.only}" not found in ${functionsDirLabel}/`)
341
+ } else {
342
+ console.error(`No functions found in ${functionsDirLabel}/`)
343
+ }
344
+ process.exit(1)
345
+ }
346
+
347
+ if (opts.dryRun) {
348
+ console.log("Dry run — the following functions would be deployed:\n")
349
+ for (const fn of fns) {
350
+ console.log(` ${fn.name} → ${relative(cwd, fn.entrypoint)}`)
351
+ }
352
+ console.log(`\nTotal: ${fns.length} function(s)`)
353
+ return
354
+ }
355
+
356
+ const composePath = selfHostComposePaths(cwd).composePath
357
+ if (existsSync(composePath)) {
358
+ await deploySelfHosted(cwd, fns)
359
+ return
360
+ }
361
+
362
+ await deployCloud(cwd, fns)
363
+ }
364
+
365
+ async function deploySelfHosted(cwd: string, fns: DiscoveredFunction[]): Promise<void> {
366
+ console.log("Self-host Compose deployment.\n")
367
+ console.log("Functions are served from your project functions/ directory (no bundle step).\n")
368
+
369
+ for (const fn of fns) {
370
+ console.log(` ${fn.name} → ${relative(cwd, fn.entrypoint)}`)
371
+ }
372
+
373
+ console.log(`\n${fns.length} function(s) ready on disk.`)
374
+ console.log("Restart the functions-worker container to load changes:")
375
+ console.log(" supatype self-host compose restart functions-worker")
376
+ console.log("\nKong → supatype-server → functions-worker (per-project worker).")
377
+ }
378
+
379
+ async function deployCloud(cwd: string, fns: DiscoveredFunction[]): Promise<void> {
380
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
381
+ const linked = getLinkedProject(cwd)
382
+
383
+ if (!linked) {
384
+ console.error("No linked project. Run: npx supatype cloud link")
385
+ process.exit(1)
386
+ }
387
+
388
+ const token = getCloudToken()
389
+ if (!token) {
390
+ console.error("Not logged in. Run: npx supatype cloud login")
391
+ process.exit(1)
392
+ }
393
+
394
+ const apiUrl = getCloudApiUrl()
395
+ console.log(`Deploying to project: ${linked.ref}\n`)
396
+
397
+ for (const fn of fns) {
398
+ const start = Date.now()
399
+
400
+ // Read source code
401
+ const source = readFunctionSource(fn)
402
+
403
+ try {
404
+ const res = await fetch(`${apiUrl}/api/v1/projects/${linked.ref}/functions/deploy`, {
405
+ method: "POST",
406
+ headers: {
407
+ Authorization: `Bearer ${token}`,
408
+ "Content-Type": "application/json",
409
+ "X-Org-Id": linked.orgId ?? "",
410
+ },
411
+ body: JSON.stringify({
412
+ functions: [{
413
+ name: fn.name,
414
+ source,
415
+ entrypoint: `${fn.name}/index.ts`,
416
+ }],
417
+ }),
418
+ signal: AbortSignal.timeout(60_000),
419
+ })
420
+
421
+ if (!res.ok) {
422
+ const body = await res.json().catch(() => ({})) as Record<string, string>
423
+ console.error(` ${fn.name} ✗ ${body["message"] ?? res.statusText}`)
424
+ continue
425
+ }
426
+
427
+ const duration = Date.now() - start
428
+ console.log(` ${fn.name} ✓ deployed (${duration}ms)`)
429
+ } catch (err) {
430
+ console.error(` ${fn.name} ✗ ${err instanceof Error ? err.message : "unknown error"}`)
431
+ }
432
+ }
433
+
434
+ console.log(`\nDeployed ${fns.length} function(s)`)
435
+ console.log(`Invoke: https://${linked.ref}.supatype.dev/functions/v1/<name>`)
436
+ }
437
+
438
+ function readFunctionSource(fn: DiscoveredFunction): string {
439
+ const stat = statSync(fn.absPath)
440
+ if (stat.isFile()) {
441
+ return readFileSync(fn.absPath, "utf8")
442
+ }
443
+
444
+ // Directory function — read all .ts files
445
+ const files: Record<string, string> = {}
446
+ const entries = readdirSync(fn.absPath, { recursive: true }) as string[]
447
+ for (const entry of entries) {
448
+ const fullPath = join(fn.absPath, entry)
449
+ if (statSync(fullPath).isFile() && (entry.endsWith(".ts") || entry.endsWith(".js"))) {
450
+ files[entry] = readFileSync(fullPath, "utf8")
451
+ }
452
+ }
453
+ return JSON.stringify(files)
454
+ }
455
+
456
+ // ─── List ────────────────────────────────────────────────────────────────────
457
+
458
+ async function listFunctions(cwd: string): Promise<void> {
459
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
460
+ const linked = getLinkedProject(cwd)
461
+
462
+ if (!linked) {
463
+ // Show local functions instead
464
+ const fns = discoverFunctions(cwd)
465
+ if (fns.length === 0) {
466
+ console.log("No functions found locally or remotely.")
467
+ return
468
+ }
469
+ console.log("Local functions (not linked to a cloud project):\n")
470
+ for (const fn of fns) {
471
+ console.log(` ${fn.name.padEnd(30)} ${relative(cwd, fn.entrypoint)}`)
472
+ }
473
+ return
474
+ }
475
+
476
+ const token = getCloudToken()
477
+ if (!token) {
478
+ console.error("Not logged in. Run: npx supatype cloud login")
479
+ process.exit(1)
480
+ }
481
+
482
+ try {
483
+ const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions`, {
484
+ headers: {
485
+ Authorization: `Bearer ${token}`,
486
+ "X-Org-Id": linked.orgId ?? "",
487
+ },
488
+ signal: AbortSignal.timeout(10_000),
489
+ })
490
+
491
+ if (!res.ok) {
492
+ console.error(`Failed to list functions: ${res.statusText}`)
493
+ process.exit(1)
494
+ }
495
+
496
+ const { data } = await res.json() as { data: Array<{ name: string; deployedAt: string; invocations24h: number; avgDurationMs: number }> }
497
+
498
+ if (data.length === 0) {
499
+ console.log("No deployed functions.")
500
+ return
501
+ }
502
+
503
+ console.log("Deployed functions:\n")
504
+ console.log(` ${"Name".padEnd(28)} ${"Last Deployed".padEnd(24)} ${"Invocations (24h)".padEnd(20)} Avg Duration`)
505
+ console.log(` ${"─".repeat(28)} ${"─".repeat(24)} ${"─".repeat(20)} ${"─".repeat(12)}`)
506
+
507
+ for (const fn of data) {
508
+ const deployed = fn.deployedAt ? new Date(fn.deployedAt).toLocaleString() : "—"
509
+ console.log(
510
+ ` ${fn.name.padEnd(28)} ${deployed.padEnd(24)} ${String(fn.invocations24h ?? 0).padEnd(20)} ${fn.avgDurationMs ?? 0}ms`,
511
+ )
512
+ }
513
+ } catch (err) {
514
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
515
+ process.exit(1)
516
+ }
517
+ }
518
+
519
+ // ─── Delete ──────────────────────────────────────────────────────────────────
520
+
521
+ async function deleteFunction(cwd: string, name: string): Promise<void> {
522
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
523
+ const linked = getLinkedProject(cwd)
524
+
525
+ if (!linked) {
526
+ console.error("No linked project. Run: npx supatype cloud link")
527
+ process.exit(1)
528
+ }
529
+
530
+ const token = getCloudToken()
531
+ if (!token) {
532
+ console.error("Not logged in. Run: npx supatype cloud login")
533
+ process.exit(1)
534
+ }
535
+
536
+ try {
537
+ const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/${name}`, {
538
+ method: "DELETE",
539
+ headers: {
540
+ Authorization: `Bearer ${token}`,
541
+ "X-Org-Id": linked.orgId ?? "",
542
+ },
543
+ signal: AbortSignal.timeout(10_000),
544
+ })
545
+
546
+ if (!res.ok) {
547
+ const body = await res.json().catch(() => ({})) as Record<string, string>
548
+ console.error(`Failed to delete "${name}": ${body["message"] ?? res.statusText}`)
549
+ process.exit(1)
550
+ }
551
+
552
+ console.log(`Function "${name}" deleted. It will return 404 immediately.`)
553
+ } catch (err) {
554
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
555
+ process.exit(1)
556
+ }
557
+ }
558
+
559
+ // ─── Logs ────────────────────────────────────────────────────────────────────
560
+
561
+ async function functionLogs(cwd: string, name: string, opts: { since: string }): Promise<void> {
562
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
563
+ const linked = getLinkedProject(cwd)
564
+
565
+ if (!linked) {
566
+ console.error("No linked project. Run: npx supatype cloud link")
567
+ process.exit(1)
568
+ }
569
+
570
+ const token = getCloudToken()
571
+ if (!token) {
572
+ console.error("Not logged in. Run: npx supatype cloud login")
573
+ process.exit(1)
574
+ }
575
+
576
+ try {
577
+ const res = await fetch(
578
+ `${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/${name}/logs?since=${opts.since}`,
579
+ {
580
+ headers: {
581
+ Authorization: `Bearer ${token}`,
582
+ "X-Org-Id": linked.orgId ?? "",
583
+ },
584
+ signal: AbortSignal.timeout(10_000),
585
+ },
586
+ )
587
+
588
+ if (!res.ok) {
589
+ console.error(`Failed to fetch logs: ${res.statusText}`)
590
+ process.exit(1)
591
+ }
592
+
593
+ const { data } = await res.json() as { data: Array<{ timestamp: string; level: string; message: string }> }
594
+
595
+ if (data.length === 0) {
596
+ console.log(`No logs for "${name}" in the last ${opts.since}.`)
597
+ return
598
+ }
599
+
600
+ for (const entry of data) {
601
+ const ts = new Date(entry.timestamp).toISOString().slice(11, 23)
602
+ const level = entry.level.toUpperCase().padEnd(5)
603
+ console.log(`${ts} [${level}] ${entry.message}`)
604
+ }
605
+ } catch (err) {
606
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
607
+ process.exit(1)
608
+ }
609
+ }
610
+
611
+ // ─── Invoke ──────────────────────────────────────────────────────────────────
612
+
613
+ async function invoke(
614
+ cwd: string,
615
+ name: string,
616
+ opts: { data: string; auth?: boolean; local?: boolean },
617
+ ): Promise<void> {
618
+ let url: string
619
+ const headers: Record<string, string> = { "Content-Type": "application/json" }
620
+
621
+ if (opts.local) {
622
+ url = `http://localhost:54321/functions/v1/${name}`
623
+ } else {
624
+ const { getLinkedProject, getCloudToken } = await loadCloudHelpers()
625
+ const linked = getLinkedProject(cwd)
626
+ if (linked) {
627
+ url = `https://${linked.ref}.supatype.dev/functions/v1/${name}`
628
+ const token = getCloudToken()
629
+ if (token && opts.auth) {
630
+ headers["Authorization"] = `Bearer ${token}`
631
+ }
632
+ } else {
633
+ // Default to local
634
+ url = `http://localhost:54321/functions/v1/${name}`
635
+ }
636
+ }
637
+
638
+ if (opts.auth && !headers["Authorization"]) {
639
+ // Generate a test JWT for local invocation
640
+ headers["Authorization"] = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0LXVzZXIiLCJyb2xlIjoiYXV0aGVudGljYXRlZCIsImlhdCI6MTcwMDAwMDAwMH0.test"
641
+ }
642
+
643
+ try {
644
+ let body: string | undefined
645
+ try {
646
+ JSON.parse(opts.data)
647
+ body = opts.data
648
+ } catch {
649
+ console.error("Invalid JSON data. Use --data '{\"key\": \"value\"}'")
650
+ process.exit(1)
651
+ }
652
+
653
+ const start = Date.now()
654
+ const res = await fetch(url, {
655
+ method: "POST",
656
+ headers,
657
+ body,
658
+ signal: AbortSignal.timeout(30_000),
659
+ })
660
+
661
+ const duration = Date.now() - start
662
+ const responseBody = await res.text()
663
+
664
+ console.log(`Status: ${res.status} (${duration}ms)`)
665
+ console.log()
666
+
667
+ // Try to pretty-print JSON
668
+ try {
669
+ const json = JSON.parse(responseBody)
670
+ console.log(JSON.stringify(json, null, 2))
671
+ } catch {
672
+ console.log(responseBody)
673
+ }
674
+ } catch (err) {
675
+ if (err instanceof TypeError && (err as Error).message.includes("fetch")) {
676
+ console.error(`Cannot reach ${url}`)
677
+ console.error("Is the function server running? Start it with: npx supatype functions serve")
678
+ } else {
679
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
680
+ }
681
+ process.exit(1)
682
+ }
683
+ }
684
+
685
+ // ─── Env management ──────────────────────────────────────────────────────────
686
+
687
+ async function envList(cwd: string): Promise<void> {
688
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
689
+ const linked = getLinkedProject(cwd)
690
+
691
+ if (!linked) {
692
+ // Show local env vars
693
+ const envPath = resolve(resolveFunctionsDir(cwd, "read"), ENV_LOCAL)
694
+ if (!existsSync(envPath)) {
695
+ console.log("No environment variables configured.")
696
+ return
697
+ }
698
+
699
+ const lines = readFileSync(envPath, "utf8").split("\n")
700
+ console.log("Local environment variables:\n")
701
+ for (const line of lines) {
702
+ const trimmed = line.trim()
703
+ if (!trimmed || trimmed.startsWith("#")) continue
704
+ const eqIdx = trimmed.indexOf("=")
705
+ if (eqIdx > 0) {
706
+ const key = trimmed.slice(0, eqIdx)
707
+ console.log(` ${key} = ••••••••`)
708
+ }
709
+ }
710
+ return
711
+ }
712
+
713
+ const token = getCloudToken()
714
+ if (!token) {
715
+ console.error("Not logged in. Run: npx supatype cloud login")
716
+ process.exit(1)
717
+ }
718
+
719
+ try {
720
+ const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env`, {
721
+ headers: {
722
+ Authorization: `Bearer ${token}`,
723
+ "X-Org-Id": linked.orgId ?? "",
724
+ },
725
+ signal: AbortSignal.timeout(10_000),
726
+ })
727
+
728
+ if (!res.ok) {
729
+ console.error(`Failed to list env vars: ${res.statusText}`)
730
+ process.exit(1)
731
+ }
732
+
733
+ const { data } = await res.json() as { data: string[] }
734
+
735
+ if (data.length === 0) {
736
+ console.log("No environment variables set.")
737
+ return
738
+ }
739
+
740
+ console.log("Environment variables (values masked):\n")
741
+ for (const key of data) {
742
+ console.log(` ${key} = ••••••••`)
743
+ }
744
+ } catch (err) {
745
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
746
+ process.exit(1)
747
+ }
748
+ }
749
+
750
+ async function envSet(cwd: string, keyvalue: string): Promise<void> {
751
+ const eqIdx = keyvalue.indexOf("=")
752
+ if (eqIdx <= 0) {
753
+ console.error("Invalid format. Use: npx supatype functions env set KEY=value")
754
+ process.exit(1)
755
+ }
756
+
757
+ const key = keyvalue.slice(0, eqIdx)
758
+ const value = keyvalue.slice(eqIdx + 1)
759
+
760
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
761
+ const linked = getLinkedProject(cwd)
762
+
763
+ if (!linked) {
764
+ // Set in local env file
765
+ const envPath = resolve(resolveFunctionsDir(cwd, "write"), ENV_LOCAL)
766
+ let content = existsSync(envPath) ? readFileSync(envPath, "utf8") : ""
767
+
768
+ // Replace existing or append
769
+ const regex = new RegExp(`^${key}=.*$`, "m")
770
+ if (regex.test(content)) {
771
+ content = content.replace(regex, `${key}=${value}`)
772
+ } else {
773
+ content = content.trimEnd() + `\n${key}=${value}\n`
774
+ }
775
+
776
+ writeFileSync(envPath, content, "utf8")
777
+ console.log(`Set ${key} in local env file.`)
778
+ return
779
+ }
780
+
781
+ const token = getCloudToken()
782
+ if (!token) {
783
+ console.error("Not logged in. Run: npx supatype cloud login")
784
+ process.exit(1)
785
+ }
786
+
787
+ try {
788
+ const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env`, {
789
+ method: "POST",
790
+ headers: {
791
+ Authorization: `Bearer ${token}`,
792
+ "Content-Type": "application/json",
793
+ "X-Org-Id": linked.orgId ?? "",
794
+ },
795
+ body: JSON.stringify({ key, value }),
796
+ signal: AbortSignal.timeout(10_000),
797
+ })
798
+
799
+ if (!res.ok) {
800
+ const body = await res.json().catch(() => ({})) as Record<string, string>
801
+ console.error(`Failed to set env var: ${body["message"] ?? res.statusText}`)
802
+ process.exit(1)
803
+ }
804
+
805
+ console.log(`Set ${key} for project ${linked.ref}.`)
806
+ } catch (err) {
807
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
808
+ process.exit(1)
809
+ }
810
+ }
811
+
812
+ async function envUnset(cwd: string, key: string): Promise<void> {
813
+ const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
814
+ const linked = getLinkedProject(cwd)
815
+
816
+ if (!linked) {
817
+ const envPath = resolve(resolveFunctionsDir(cwd, "read"), ENV_LOCAL)
818
+ if (!existsSync(envPath)) {
819
+ console.error("No local env file found.")
820
+ process.exit(1)
821
+ }
822
+
823
+ let content = readFileSync(envPath, "utf8")
824
+ const regex = new RegExp(`^${key}=.*\n?`, "m")
825
+ content = content.replace(regex, "")
826
+ writeFileSync(envPath, content, "utf8")
827
+ console.log(`Removed ${key} from local env file.`)
828
+ return
829
+ }
830
+
831
+ const token = getCloudToken()
832
+ if (!token) {
833
+ console.error("Not logged in. Run: npx supatype cloud login")
834
+ process.exit(1)
835
+ }
836
+
837
+ try {
838
+ const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env/${key}`, {
839
+ method: "DELETE",
840
+ headers: {
841
+ Authorization: `Bearer ${token}`,
842
+ "X-Org-Id": linked.orgId ?? "",
843
+ },
844
+ signal: AbortSignal.timeout(10_000),
845
+ })
846
+
847
+ if (!res.ok) {
848
+ const body = await res.json().catch(() => ({})) as Record<string, string>
849
+ console.error(`Failed to unset env var: ${body["message"] ?? res.statusText}`)
850
+ process.exit(1)
851
+ }
852
+
853
+ console.log(`Removed ${key} for project ${linked.ref}.`)
854
+ } catch (err) {
855
+ console.error(`Error: ${err instanceof Error ? err.message : "unknown"}`)
856
+ process.exit(1)
857
+ }
858
+ }
859
+
860
+ // ─── Cloud helpers (lazy loaded) ─────────────────────────────────────────────
861
+
862
+ interface CloudHelpers {
863
+ getLinkedProject(cwd: string): { ref: string; orgId?: string } | null
864
+ getCloudToken(): string | null
865
+ getCloudApiUrl(): string
866
+ }
867
+
868
+ async function loadCloudHelpers(): Promise<CloudHelpers> {
869
+ // These helpers read the local .supatype/linked.json and auth token
870
+ return {
871
+ getLinkedProject(cwd: string): { ref: string; orgId?: string } | null {
872
+ const linkedPath = resolve(cwd, ".supatype/linked.json")
873
+ if (!existsSync(linkedPath)) return null
874
+ try {
875
+ const data = JSON.parse(readFileSync(linkedPath, "utf8")) as Record<string, string>
876
+ const ref = data["ref"]
877
+ const orgId = data["orgId"]
878
+ return ref ? { ref, ...(orgId !== undefined ? { orgId } : {}) } : null
879
+ } catch {
880
+ return null
881
+ }
882
+ },
883
+
884
+ getCloudToken(): string | null {
885
+ // Check env first, then config file
886
+ if (process.env["SUPATYPE_ACCESS_TOKEN"]) {
887
+ return process.env["SUPATYPE_ACCESS_TOKEN"]
888
+ }
889
+ const tokenPath = resolve(
890
+ process.env["HOME"] ?? process.env["USERPROFILE"] ?? "~",
891
+ ".supatype/token",
892
+ )
893
+ if (!existsSync(tokenPath)) return null
894
+ return readFileSync(tokenPath, "utf8").trim() || null
895
+ },
896
+
897
+ getCloudApiUrl(): string {
898
+ return process.env["SUPATYPE_API_URL"] ?? "https://api.supatype.com"
899
+ },
900
+ }
901
+ }