@sixthsummit/ship-api 0.0.0-stage → 4.1.1

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 (556) hide show
  1. package/CHANGELOG.md +605 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +58 -2
  5. package/cli/commands/add.js +537 -0
  6. package/cli/commands/contract.js +2 -0
  7. package/cli/commands/create.js +250 -0
  8. package/cli/commands/dev.js +227 -0
  9. package/cli/commands/doctor.js +286 -0
  10. package/cli/commands/generate.js +937 -0
  11. package/cli/commands/graph.js +2 -0
  12. package/cli/commands/migrate-orm.js +1171 -0
  13. package/cli/commands/update.js +37 -0
  14. package/cli/ship-api.js +285 -0
  15. package/lib/app/auth/AuthProvider.d.ts +91 -0
  16. package/lib/app/auth/AuthProvider.d.ts.map +1 -0
  17. package/lib/app/auth/AuthProvider.js +13 -0
  18. package/lib/app/auth/AuthProvider.js.map +1 -0
  19. package/lib/app/auth/adapters/firebase.d.ts +79 -0
  20. package/lib/app/auth/adapters/firebase.d.ts.map +1 -0
  21. package/lib/app/auth/adapters/firebase.js +122 -0
  22. package/lib/app/auth/adapters/firebase.js.map +1 -0
  23. package/lib/app/auth/adapters/jwtCookie.d.ts +55 -0
  24. package/lib/app/auth/adapters/jwtCookie.d.ts.map +1 -0
  25. package/lib/app/auth/adapters/jwtCookie.js +120 -0
  26. package/lib/app/auth/adapters/jwtCookie.js.map +1 -0
  27. package/lib/app/auth/index.d.ts +8 -0
  28. package/lib/app/auth/index.d.ts.map +1 -0
  29. package/lib/app/auth/index.js +7 -0
  30. package/lib/app/auth/index.js.map +1 -0
  31. package/lib/app/auth/jwt/AuthFlow.d.ts +94 -0
  32. package/lib/app/auth/jwt/AuthFlow.d.ts.map +1 -0
  33. package/lib/app/auth/jwt/AuthFlow.js +127 -0
  34. package/lib/app/auth/jwt/AuthFlow.js.map +1 -0
  35. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts +80 -0
  36. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.d.ts.map +1 -0
  37. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js +134 -0
  38. package/lib/app/auth/jwt/DrizzleRefreshTokenStore.js.map +1 -0
  39. package/lib/app/auth/jwt/JwtSigner.d.ts +123 -0
  40. package/lib/app/auth/jwt/JwtSigner.d.ts.map +1 -0
  41. package/lib/app/auth/jwt/JwtSigner.js +118 -0
  42. package/lib/app/auth/jwt/JwtSigner.js.map +1 -0
  43. package/lib/app/auth/jwt/RefreshTokenStore.d.ts +97 -0
  44. package/lib/app/auth/jwt/RefreshTokenStore.d.ts.map +1 -0
  45. package/lib/app/auth/jwt/RefreshTokenStore.js +122 -0
  46. package/lib/app/auth/jwt/RefreshTokenStore.js.map +1 -0
  47. package/lib/app/auth/permissionDefinition.d.ts +20 -0
  48. package/lib/app/auth/permissionDefinition.d.ts.map +1 -0
  49. package/lib/app/auth/permissionDefinition.js +2 -0
  50. package/lib/app/auth/permissionDefinition.js.map +1 -0
  51. package/lib/app/context/RequestContext.d.ts +59 -0
  52. package/lib/app/context/RequestContext.d.ts.map +1 -0
  53. package/lib/app/context/RequestContext.js +25 -0
  54. package/lib/app/context/RequestContext.js.map +1 -0
  55. package/lib/app/db/QueryOptions.d.ts +82 -0
  56. package/lib/app/db/QueryOptions.d.ts.map +1 -0
  57. package/lib/app/db/QueryOptions.js +37 -0
  58. package/lib/app/db/QueryOptions.js.map +1 -0
  59. package/lib/app/db/baseFields.d.ts +60 -0
  60. package/lib/app/db/baseFields.d.ts.map +1 -0
  61. package/lib/app/db/baseFields.js +54 -0
  62. package/lib/app/db/baseFields.js.map +1 -0
  63. package/lib/app/db/buildWhere.d.ts +22 -0
  64. package/lib/app/db/buildWhere.d.ts.map +1 -0
  65. package/lib/app/db/buildWhere.js +75 -0
  66. package/lib/app/db/buildWhere.js.map +1 -0
  67. package/lib/app/db/client.d.ts +111 -0
  68. package/lib/app/db/client.d.ts.map +1 -0
  69. package/lib/app/db/client.js +99 -0
  70. package/lib/app/db/client.js.map +1 -0
  71. package/lib/app/db/tenantScoped.d.ts +59 -0
  72. package/lib/app/db/tenantScoped.d.ts.map +1 -0
  73. package/lib/app/db/tenantScoped.js +54 -0
  74. package/lib/app/db/tenantScoped.js.map +1 -0
  75. package/lib/app/db/transaction.d.ts +58 -0
  76. package/lib/app/db/transaction.d.ts.map +1 -0
  77. package/lib/app/db/transaction.js +106 -0
  78. package/lib/app/db/transaction.js.map +1 -0
  79. package/lib/app/health/HealthCheck.d.ts +121 -0
  80. package/lib/app/health/HealthCheck.d.ts.map +1 -0
  81. package/lib/app/health/HealthCheck.js +151 -0
  82. package/lib/app/health/HealthCheck.js.map +1 -0
  83. package/lib/app/health/redisHealthCheck.d.ts +14 -0
  84. package/lib/app/health/redisHealthCheck.d.ts.map +1 -0
  85. package/lib/app/health/redisHealthCheck.js +21 -0
  86. package/lib/app/health/redisHealthCheck.js.map +1 -0
  87. package/lib/app/lifecycle/Bootstrapper.d.ts +92 -0
  88. package/lib/app/lifecycle/Bootstrapper.d.ts.map +1 -0
  89. package/lib/app/lifecycle/Bootstrapper.js +99 -0
  90. package/lib/app/lifecycle/Bootstrapper.js.map +1 -0
  91. package/lib/app/registry/routes.d.ts +89 -0
  92. package/lib/app/registry/routes.d.ts.map +1 -0
  93. package/lib/app/registry/routes.js +72 -0
  94. package/lib/app/registry/routes.js.map +1 -0
  95. package/lib/app/services/DrizzleEntityService.d.ts +123 -0
  96. package/lib/app/services/DrizzleEntityService.d.ts.map +1 -0
  97. package/lib/app/services/DrizzleEntityService.js +342 -0
  98. package/lib/app/services/DrizzleEntityService.js.map +1 -0
  99. package/lib/app/services/RedisProvider.d.ts +52 -0
  100. package/lib/app/services/RedisProvider.d.ts.map +1 -0
  101. package/lib/app/services/RedisProvider.js +75 -0
  102. package/lib/app/services/RedisProvider.js.map +1 -0
  103. package/lib/app/services/cache/Cache.d.ts +70 -0
  104. package/lib/app/services/cache/Cache.d.ts.map +1 -0
  105. package/lib/app/services/cache/Cache.js +18 -0
  106. package/lib/app/services/cache/Cache.js.map +1 -0
  107. package/lib/app/services/cache/InMemoryCache.d.ts +35 -0
  108. package/lib/app/services/cache/InMemoryCache.d.ts.map +1 -0
  109. package/lib/app/services/cache/InMemoryCache.js +76 -0
  110. package/lib/app/services/cache/InMemoryCache.js.map +1 -0
  111. package/lib/app/services/cache/RedisCache.d.ts +99 -0
  112. package/lib/app/services/cache/RedisCache.d.ts.map +1 -0
  113. package/lib/app/services/cache/RedisCache.js +150 -0
  114. package/lib/app/services/cache/RedisCache.js.map +1 -0
  115. package/lib/app/services/cache/cached.d.ts +30 -0
  116. package/lib/app/services/cache/cached.d.ts.map +1 -0
  117. package/lib/app/services/cache/cached.js +38 -0
  118. package/lib/app/services/cache/cached.js.map +1 -0
  119. package/lib/app/services/cache/index.d.ts +5 -0
  120. package/lib/app/services/cache/index.d.ts.map +1 -0
  121. package/lib/app/services/cache/index.js +4 -0
  122. package/lib/app/services/cache/index.js.map +1 -0
  123. package/lib/app/services/cron/defineCron.d.ts +57 -0
  124. package/lib/app/services/cron/defineCron.d.ts.map +1 -0
  125. package/lib/app/services/cron/defineCron.js +15 -0
  126. package/lib/app/services/cron/defineCron.js.map +1 -0
  127. package/lib/app/services/cron/index.d.ts +2 -0
  128. package/lib/app/services/cron/index.d.ts.map +1 -0
  129. package/lib/app/services/cron/index.js +2 -0
  130. package/lib/app/services/cron/index.js.map +1 -0
  131. package/lib/app/services/hooks/LifecycleHooks.d.ts +61 -0
  132. package/lib/app/services/hooks/LifecycleHooks.d.ts.map +1 -0
  133. package/lib/app/services/hooks/LifecycleHooks.js +85 -0
  134. package/lib/app/services/hooks/LifecycleHooks.js.map +1 -0
  135. package/lib/app/services/jobs/JobsManager.d.ts +67 -0
  136. package/lib/app/services/jobs/JobsManager.d.ts.map +1 -0
  137. package/lib/app/services/jobs/JobsManager.js +121 -0
  138. package/lib/app/services/jobs/JobsManager.js.map +1 -0
  139. package/lib/app/services/jobs/bullBoard.d.ts +72 -0
  140. package/lib/app/services/jobs/bullBoard.d.ts.map +1 -0
  141. package/lib/app/services/jobs/bullBoard.js +96 -0
  142. package/lib/app/services/jobs/bullBoard.js.map +1 -0
  143. package/lib/app/services/jobs/defineJob.d.ts +81 -0
  144. package/lib/app/services/jobs/defineJob.d.ts.map +1 -0
  145. package/lib/app/services/jobs/defineJob.js +49 -0
  146. package/lib/app/services/jobs/defineJob.js.map +1 -0
  147. package/lib/app/services/jobs/index.d.ts +4 -0
  148. package/lib/app/services/jobs/index.d.ts.map +1 -0
  149. package/lib/app/services/jobs/index.js +4 -0
  150. package/lib/app/services/jobs/index.js.map +1 -0
  151. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts +23 -0
  152. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.d.ts.map +1 -0
  153. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js +51 -0
  154. package/lib/app/services/rateLimit/InMemorySlidingWindowBackend.js.map +1 -0
  155. package/lib/app/services/rateLimit/RateLimitBackend.d.ts +41 -0
  156. package/lib/app/services/rateLimit/RateLimitBackend.d.ts.map +1 -0
  157. package/lib/app/services/rateLimit/RateLimitBackend.js +19 -0
  158. package/lib/app/services/rateLimit/RateLimitBackend.js.map +1 -0
  159. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts +16 -0
  160. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.d.ts.map +1 -0
  161. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js +80 -0
  162. package/lib/app/services/rateLimit/RedisSlidingWindowBackend.js.map +1 -0
  163. package/lib/app/services/rateLimit/index.d.ts +4 -0
  164. package/lib/app/services/rateLimit/index.d.ts.map +1 -0
  165. package/lib/app/services/rateLimit/index.js +3 -0
  166. package/lib/app/services/rateLimit/index.js.map +1 -0
  167. package/lib/app/services/storage/DiskStorageAdapter.d.ts +62 -0
  168. package/lib/app/services/storage/DiskStorageAdapter.d.ts.map +1 -0
  169. package/lib/app/services/storage/DiskStorageAdapter.js +295 -0
  170. package/lib/app/services/storage/DiskStorageAdapter.js.map +1 -0
  171. package/lib/app/services/storage/GCSStorageAdapter.d.ts +44 -0
  172. package/lib/app/services/storage/GCSStorageAdapter.d.ts.map +1 -0
  173. package/lib/app/services/storage/GCSStorageAdapter.js +133 -0
  174. package/lib/app/services/storage/GCSStorageAdapter.js.map +1 -0
  175. package/lib/app/services/storage/S3StorageAdapter.d.ts +57 -0
  176. package/lib/app/services/storage/S3StorageAdapter.d.ts.map +1 -0
  177. package/lib/app/services/storage/S3StorageAdapter.js +162 -0
  178. package/lib/app/services/storage/S3StorageAdapter.js.map +1 -0
  179. package/lib/app/services/storage/StorageService.d.ts +88 -0
  180. package/lib/app/services/storage/StorageService.d.ts.map +1 -0
  181. package/lib/app/services/storage/StorageService.js +18 -0
  182. package/lib/app/services/storage/StorageService.js.map +1 -0
  183. package/lib/app/services/storage/index.d.ts +5 -0
  184. package/lib/app/services/storage/index.d.ts.map +1 -0
  185. package/lib/app/services/storage/index.js +4 -0
  186. package/lib/app/services/storage/index.js.map +1 -0
  187. package/lib/app/utils/ContextValidators.d.ts +46 -0
  188. package/lib/app/utils/ContextValidators.d.ts.map +1 -0
  189. package/lib/app/utils/ContextValidators.js +81 -0
  190. package/lib/app/utils/ContextValidators.js.map +1 -0
  191. package/lib/app/utils/defineEnv.d.ts +80 -0
  192. package/lib/app/utils/defineEnv.d.ts.map +1 -0
  193. package/lib/app/utils/defineEnv.js +95 -0
  194. package/lib/app/utils/defineEnv.js.map +1 -0
  195. package/lib/client.d.ts +139 -0
  196. package/lib/client.d.ts.map +1 -0
  197. package/lib/client.js +98 -0
  198. package/lib/client.js.map +1 -0
  199. package/lib/contract/application.d.ts +3 -0
  200. package/lib/contract/application.d.ts.map +1 -0
  201. package/lib/contract/application.js +2 -0
  202. package/lib/contract/application.js.map +1 -0
  203. package/lib/contract/client.d.ts +3 -0
  204. package/lib/contract/client.d.ts.map +1 -0
  205. package/lib/contract/client.js +3 -0
  206. package/lib/contract/client.js.map +1 -0
  207. package/lib/contract/index.d.ts +3 -0
  208. package/lib/contract/index.d.ts.map +1 -0
  209. package/lib/contract/index.js +3 -0
  210. package/lib/contract/index.js.map +1 -0
  211. package/lib/contract/manifest.d.ts +3 -0
  212. package/lib/contract/manifest.d.ts.map +1 -0
  213. package/lib/contract/manifest.js +2 -0
  214. package/lib/contract/manifest.js.map +1 -0
  215. package/lib/contract/openapi.d.ts +3 -0
  216. package/lib/contract/openapi.d.ts.map +1 -0
  217. package/lib/contract/openapi.js +3 -0
  218. package/lib/contract/openapi.js.map +1 -0
  219. package/lib/contract/project.d.ts +3 -0
  220. package/lib/contract/project.d.ts.map +1 -0
  221. package/lib/contract/project.js +3 -0
  222. package/lib/contract/project.js.map +1 -0
  223. package/lib/contract/schema.d.ts +3 -0
  224. package/lib/contract/schema.d.ts.map +1 -0
  225. package/lib/contract/schema.js +3 -0
  226. package/lib/contract/schema.js.map +1 -0
  227. package/lib/drizzle/index.d.ts +3 -0
  228. package/lib/drizzle/index.d.ts.map +1 -0
  229. package/lib/drizzle/index.js +3 -0
  230. package/lib/drizzle/index.js.map +1 -0
  231. package/lib/hono/auth.d.ts +6 -0
  232. package/lib/hono/auth.d.ts.map +1 -0
  233. package/lib/hono/auth.js +54 -0
  234. package/lib/hono/auth.js.map +1 -0
  235. package/lib/hono/index.d.ts +4 -0
  236. package/lib/hono/index.d.ts.map +1 -0
  237. package/lib/hono/index.js +4 -0
  238. package/lib/hono/index.js.map +1 -0
  239. package/lib/http/app/createApp.d.ts +132 -0
  240. package/lib/http/app/createApp.d.ts.map +1 -0
  241. package/lib/http/app/createApp.js +215 -0
  242. package/lib/http/app/createApp.js.map +1 -0
  243. package/lib/http/app/devRoutes.d.ts +31 -0
  244. package/lib/http/app/devRoutes.d.ts.map +1 -0
  245. package/lib/http/app/devRoutes.js +145 -0
  246. package/lib/http/app/devRoutes.js.map +1 -0
  247. package/lib/http/app/errors.d.ts +45 -0
  248. package/lib/http/app/errors.d.ts.map +1 -0
  249. package/lib/http/app/errors.js +93 -0
  250. package/lib/http/app/errors.js.map +1 -0
  251. package/lib/http/app/health.d.ts +66 -0
  252. package/lib/http/app/health.d.ts.map +1 -0
  253. package/lib/http/app/health.js +104 -0
  254. package/lib/http/app/health.js.map +1 -0
  255. package/lib/http/errors/AppError.d.ts +10 -0
  256. package/lib/http/errors/AppError.d.ts.map +1 -0
  257. package/lib/http/errors/AppError.js +19 -0
  258. package/lib/http/errors/AppError.js.map +1 -0
  259. package/lib/http/errors/AuthenticationError.d.ts +8 -0
  260. package/lib/http/errors/AuthenticationError.d.ts.map +1 -0
  261. package/lib/http/errors/AuthenticationError.js +10 -0
  262. package/lib/http/errors/AuthenticationError.js.map +1 -0
  263. package/lib/http/errors/AuthorizationError.d.ts +8 -0
  264. package/lib/http/errors/AuthorizationError.d.ts.map +1 -0
  265. package/lib/http/errors/AuthorizationError.js +10 -0
  266. package/lib/http/errors/AuthorizationError.js.map +1 -0
  267. package/lib/http/errors/BadRequestError.d.ts +8 -0
  268. package/lib/http/errors/BadRequestError.d.ts.map +1 -0
  269. package/lib/http/errors/BadRequestError.js +10 -0
  270. package/lib/http/errors/BadRequestError.js.map +1 -0
  271. package/lib/http/errors/ConflictError.d.ts +8 -0
  272. package/lib/http/errors/ConflictError.d.ts.map +1 -0
  273. package/lib/http/errors/ConflictError.js +10 -0
  274. package/lib/http/errors/ConflictError.js.map +1 -0
  275. package/lib/http/errors/InternalServerError.d.ts +8 -0
  276. package/lib/http/errors/InternalServerError.d.ts.map +1 -0
  277. package/lib/http/errors/InternalServerError.js +10 -0
  278. package/lib/http/errors/InternalServerError.js.map +1 -0
  279. package/lib/http/errors/NotFoundError.d.ts +8 -0
  280. package/lib/http/errors/NotFoundError.d.ts.map +1 -0
  281. package/lib/http/errors/NotFoundError.js +10 -0
  282. package/lib/http/errors/NotFoundError.js.map +1 -0
  283. package/lib/http/errors/RateLimitError.d.ts +13 -0
  284. package/lib/http/errors/RateLimitError.d.ts.map +1 -0
  285. package/lib/http/errors/RateLimitError.js +16 -0
  286. package/lib/http/errors/RateLimitError.js.map +1 -0
  287. package/lib/http/errors/ServiceUnavailableError.d.ts +8 -0
  288. package/lib/http/errors/ServiceUnavailableError.d.ts.map +1 -0
  289. package/lib/http/errors/ServiceUnavailableError.js +10 -0
  290. package/lib/http/errors/ServiceUnavailableError.js.map +1 -0
  291. package/lib/http/errors/ValidationError.d.ts +9 -0
  292. package/lib/http/errors/ValidationError.d.ts.map +1 -0
  293. package/lib/http/errors/ValidationError.js +12 -0
  294. package/lib/http/errors/ValidationError.js.map +1 -0
  295. package/lib/http/errors/index.d.ts +11 -0
  296. package/lib/http/errors/index.d.ts.map +1 -0
  297. package/lib/http/errors/index.js +12 -0
  298. package/lib/http/errors/index.js.map +1 -0
  299. package/lib/http/middleware/apiKey.d.ts +9 -0
  300. package/lib/http/middleware/apiKey.d.ts.map +1 -0
  301. package/lib/http/middleware/apiKey.js +71 -0
  302. package/lib/http/middleware/apiKey.js.map +1 -0
  303. package/lib/http/middleware/csrf.d.ts +63 -0
  304. package/lib/http/middleware/csrf.d.ts.map +1 -0
  305. package/lib/http/middleware/csrf.js +169 -0
  306. package/lib/http/middleware/csrf.js.map +1 -0
  307. package/lib/http/middleware/httpLogger.d.ts +39 -0
  308. package/lib/http/middleware/httpLogger.d.ts.map +1 -0
  309. package/lib/http/middleware/httpLogger.js +118 -0
  310. package/lib/http/middleware/httpLogger.js.map +1 -0
  311. package/lib/http/middleware/idempotency.d.ts +48 -0
  312. package/lib/http/middleware/idempotency.d.ts.map +1 -0
  313. package/lib/http/middleware/idempotency.js +180 -0
  314. package/lib/http/middleware/idempotency.js.map +1 -0
  315. package/lib/http/middleware/rateLimit.d.ts +105 -0
  316. package/lib/http/middleware/rateLimit.d.ts.map +1 -0
  317. package/lib/http/middleware/rateLimit.js +84 -0
  318. package/lib/http/middleware/rateLimit.js.map +1 -0
  319. package/lib/http/middleware/requireAuth.d.ts +58 -0
  320. package/lib/http/middleware/requireAuth.d.ts.map +1 -0
  321. package/lib/http/middleware/requireAuth.js +63 -0
  322. package/lib/http/middleware/requireAuth.js.map +1 -0
  323. package/lib/http/middleware/requirePermission.d.ts +23 -0
  324. package/lib/http/middleware/requirePermission.d.ts.map +1 -0
  325. package/lib/http/middleware/requirePermission.js +50 -0
  326. package/lib/http/middleware/requirePermission.js.map +1 -0
  327. package/lib/http/middleware/resolveTenant.d.ts +41 -0
  328. package/lib/http/middleware/resolveTenant.d.ts.map +1 -0
  329. package/lib/http/middleware/resolveTenant.js +17 -0
  330. package/lib/http/middleware/resolveTenant.js.map +1 -0
  331. package/lib/http/streaming/sse.d.ts +31 -0
  332. package/lib/http/streaming/sse.d.ts.map +1 -0
  333. package/lib/http/streaming/sse.js +11 -0
  334. package/lib/http/streaming/sse.js.map +1 -0
  335. package/lib/http/streaming/streamLLM.d.ts +94 -0
  336. package/lib/http/streaming/streamLLM.d.ts.map +1 -0
  337. package/lib/http/streaming/streamLLM.js +94 -0
  338. package/lib/http/streaming/streamLLM.js.map +1 -0
  339. package/lib/http/utils/authCookies.d.ts +74 -0
  340. package/lib/http/utils/authCookies.d.ts.map +1 -0
  341. package/lib/http/utils/authCookies.js +102 -0
  342. package/lib/http/utils/authCookies.js.map +1 -0
  343. package/lib/http/utils/clientIp.d.ts +85 -0
  344. package/lib/http/utils/clientIp.d.ts.map +1 -0
  345. package/lib/http/utils/clientIp.js +249 -0
  346. package/lib/http/utils/clientIp.js.map +1 -0
  347. package/lib/http/utils/index.d.ts +4 -0
  348. package/lib/http/utils/index.d.ts.map +1 -0
  349. package/lib/http/utils/index.js +5 -0
  350. package/lib/http/utils/index.js.map +1 -0
  351. package/lib/http/utils/metaTags.d.ts +49 -0
  352. package/lib/http/utils/metaTags.d.ts.map +1 -0
  353. package/lib/http/utils/metaTags.js +32 -0
  354. package/lib/http/utils/metaTags.js.map +1 -0
  355. package/lib/http/utils/publicSuffix.d.ts +34 -0
  356. package/lib/http/utils/publicSuffix.d.ts.map +1 -0
  357. package/lib/http/utils/publicSuffix.js +276 -0
  358. package/lib/http/utils/publicSuffix.js.map +1 -0
  359. package/lib/http/utils/uploads.d.ts +92 -0
  360. package/lib/http/utils/uploads.d.ts.map +1 -0
  361. package/lib/http/utils/uploads.js +126 -0
  362. package/lib/http/utils/uploads.js.map +1 -0
  363. package/lib/http/utils/validate.d.ts +41 -0
  364. package/lib/http/utils/validate.d.ts.map +1 -0
  365. package/lib/http/utils/validate.js +55 -0
  366. package/lib/http/utils/validate.js.map +1 -0
  367. package/lib/http/utils/webhooks.d.ts +63 -0
  368. package/lib/http/utils/webhooks.d.ts.map +1 -0
  369. package/lib/http/utils/webhooks.js +133 -0
  370. package/lib/http/utils/webhooks.js.map +1 -0
  371. package/lib/http/ws/auth.d.ts +46 -0
  372. package/lib/http/ws/auth.d.ts.map +1 -0
  373. package/lib/http/ws/auth.js +123 -0
  374. package/lib/http/ws/auth.js.map +1 -0
  375. package/lib/index.d.ts +80 -0
  376. package/lib/index.d.ts.map +1 -0
  377. package/lib/index.js +61 -0
  378. package/lib/index.js.map +1 -0
  379. package/lib/kernel/index.d.ts +3 -0
  380. package/lib/kernel/index.d.ts.map +1 -0
  381. package/lib/kernel/index.js +3 -0
  382. package/lib/kernel/index.js.map +1 -0
  383. package/lib/libraries/.gitkeep +0 -0
  384. package/lib/libraries/ai/CHANGELOG.md +17 -0
  385. package/lib/libraries/ai/README.md +164 -0
  386. package/lib/libraries/ai/adapters/AnthropicAdapter.ts +382 -0
  387. package/lib/libraries/ai/adapters/LLMProvider.ts +76 -0
  388. package/lib/libraries/ai/adapters/OpenAIAdapter.ts +466 -0
  389. package/lib/libraries/ai/experimental/index.ts +20 -0
  390. package/lib/libraries/ai/index.ts +128 -0
  391. package/lib/libraries/ai/library.json +19 -0
  392. package/lib/libraries/ai/loop/agentLoop.ts +377 -0
  393. package/lib/libraries/ai/prompts/definePrompt.ts +0 -0
  394. package/lib/libraries/ai/rag/retrieveAndAnswer.ts +177 -0
  395. package/lib/libraries/ai/streaming/streamLLM.ts +94 -0
  396. package/lib/libraries/ai/structured/completeStructured.ts +143 -0
  397. package/lib/libraries/ai/tokens/Tokenizer.ts +216 -0
  398. package/lib/libraries/ai/tools/defineTool.ts +208 -0
  399. package/lib/libraries/ai/types.ts +260 -0
  400. package/lib/libraries/chunking/ChunkingService.ts +103 -0
  401. package/lib/libraries/chunking/README.md +104 -0
  402. package/lib/libraries/chunking/docs/api-reference.md +549 -0
  403. package/lib/libraries/chunking/docs/best-practices.md +321 -0
  404. package/lib/libraries/chunking/docs/code-organization.md +538 -0
  405. package/lib/libraries/chunking/docs/configuration.md +418 -0
  406. package/lib/libraries/chunking/docs/index.md +236 -0
  407. package/lib/libraries/chunking/docs/quick-start.md +298 -0
  408. package/lib/libraries/chunking/docs/strategies.md +407 -0
  409. package/lib/libraries/chunking/docs/summary.md +258 -0
  410. package/lib/libraries/chunking/docs/troubleshooting.md +527 -0
  411. package/lib/libraries/chunking/index.ts +17 -0
  412. package/lib/libraries/chunking/library.json +16 -0
  413. package/lib/libraries/chunking/strategies/ChunkingStrategy.ts +13 -0
  414. package/lib/libraries/chunking/strategies/FixedLengthChunkingStrategy.ts +88 -0
  415. package/lib/libraries/chunking/strategies/SemanticChunkingStrategy.ts +389 -0
  416. package/lib/libraries/chunking/strategies/SlidingWindowChunkingStrategy.ts +258 -0
  417. package/lib/libraries/chunking/types/index.ts +63 -0
  418. package/lib/libraries/email/EmailService.ts +591 -0
  419. package/lib/libraries/email/README.md +777 -0
  420. package/lib/libraries/email/TemplateRegistry.ts +148 -0
  421. package/lib/libraries/email/TemplateRenderer.ts +273 -0
  422. package/lib/libraries/email/index.ts +4 -0
  423. package/lib/libraries/email/initializeEmailService.ts +60 -0
  424. package/lib/libraries/email/library.json +12 -0
  425. package/lib/libraries/email/types/EmailAddress.ts +4 -0
  426. package/lib/libraries/email/types/EmailAttachment.ts +11 -0
  427. package/lib/libraries/email/types/EmailTag.ts +9 -0
  428. package/lib/libraries/email/types/EmailTemplate.ts +124 -0
  429. package/lib/libraries/email/types/SendBatchEmailsOptions.ts +9 -0
  430. package/lib/libraries/email/types/SendBatchEmailsResult.ts +9 -0
  431. package/lib/libraries/email/types/SendEmailOptions.ts +33 -0
  432. package/lib/libraries/email/types/SendEmailResult.ts +9 -0
  433. package/lib/libraries/email/types/index.ts +8 -0
  434. package/lib/libraries/weaviate/QueryBuilder.ts +292 -0
  435. package/lib/libraries/weaviate/README.md +284 -0
  436. package/lib/libraries/weaviate/Weaviate.ts +1419 -0
  437. package/lib/libraries/weaviate/WeaviateConfig.ts +268 -0
  438. package/lib/libraries/weaviate/docs/.gitkeep +1 -0
  439. package/lib/libraries/weaviate/docs/API_REFERENCE.md +1067 -0
  440. package/lib/libraries/weaviate/docs/CONFIGURATION.md +320 -0
  441. package/lib/libraries/weaviate/docs/ERROR_HANDLING.md +271 -0
  442. package/lib/libraries/weaviate/docs/MULTI_TENANCY.md +348 -0
  443. package/lib/libraries/weaviate/docs/PERFORMANCE.md +602 -0
  444. package/lib/libraries/weaviate/docs/PLUGIN_DEVELOPMENT.md +547 -0
  445. package/lib/libraries/weaviate/docs/QUICK_START.md +443 -0
  446. package/lib/libraries/weaviate/docs/README.md +269 -0
  447. package/lib/libraries/weaviate/docs/SCHEMA_GUIDE.md +630 -0
  448. package/lib/libraries/weaviate/docs/WEAVIATE_README.md +520 -0
  449. package/lib/libraries/weaviate/errors/WeaviateError.ts +92 -0
  450. package/lib/libraries/weaviate/errors/index.ts +1 -0
  451. package/lib/libraries/weaviate/index.ts +35 -0
  452. package/lib/libraries/weaviate/library.json +26 -0
  453. package/lib/libraries/weaviate/plugins/index.ts +2 -0
  454. package/lib/libraries/weaviate/schemas/SchemaRegistry.ts +284 -0
  455. package/lib/libraries/weaviate/schemas/index.ts +2 -0
  456. package/lib/libraries/weaviate/types/BM25SearchOptions.ts +9 -0
  457. package/lib/libraries/weaviate/types/ExtendedWeaviateClient.ts +7 -0
  458. package/lib/libraries/weaviate/types/HybridSearchOptions.ts +11 -0
  459. package/lib/libraries/weaviate/types/MultiTenancy.ts +81 -0
  460. package/lib/libraries/weaviate/types/Plugin.ts +17 -0
  461. package/lib/libraries/weaviate/types/SchemaConfig.ts +25 -0
  462. package/lib/libraries/weaviate/types/SearchMethod.ts +8 -0
  463. package/lib/libraries/weaviate/types/SearchOptions.ts +16 -0
  464. package/lib/libraries/weaviate/types/SearchRequest.ts +19 -0
  465. package/lib/libraries/weaviate/types/SearchResult.ts +16 -0
  466. package/lib/libraries/weaviate/types/SearchableField.ts +87 -0
  467. package/lib/libraries/weaviate/types/VectorIndexConfig.ts +19 -0
  468. package/lib/libraries/weaviate/types/VectorSearchOptions.ts +18 -0
  469. package/lib/libraries/weaviate/types/VectorizerConfig.ts +8 -0
  470. package/lib/libraries/weaviate/types/index.ts +29 -0
  471. package/lib/libraries/weaviate/utils/TenantContextManager.ts +94 -0
  472. package/lib/modules/accounts/index.ts +47 -0
  473. package/lib/modules/accounts/module.json +15 -0
  474. package/lib/modules/accounts/permissions.ts +33 -0
  475. package/lib/modules/accounts/routes.ts +200 -0
  476. package/lib/modules/accounts/schema.ts +219 -0
  477. package/lib/modules/accounts/services/AccountService.ts +44 -0
  478. package/lib/modules/accounts/validation/schemas.ts +70 -0
  479. package/lib/modules/oauth/adapters/GmailOAuthAdapter.ts +35 -0
  480. package/lib/modules/oauth/adapters/GoogleOAuthAdapter.ts +278 -0
  481. package/lib/modules/oauth/adapters/MicrosoftOAuthAdapter.ts +269 -0
  482. package/lib/modules/oauth/adapters/MockOAuthAdapter.ts +138 -0
  483. package/lib/modules/oauth/adapters/OAuthAdapter.ts +211 -0
  484. package/lib/modules/oauth/adapters/OAuthAdapterRegistry.ts +77 -0
  485. package/lib/modules/oauth/adapters/OutlookOAuthAdapter.ts +38 -0
  486. package/lib/modules/oauth/index.ts +79 -0
  487. package/lib/modules/oauth/module.json +23 -0
  488. package/lib/modules/oauth/permissions.ts +39 -0
  489. package/lib/modules/oauth/routes.ts +497 -0
  490. package/lib/modules/oauth/schema.ts +127 -0
  491. package/lib/modules/oauth/services/OAuthConnectionService.ts +147 -0
  492. package/lib/modules/oauth/state.ts +234 -0
  493. package/lib/modules/oauth/stateJtiCache.ts +85 -0
  494. package/lib/modules/oauth/verifyIdToken.ts +178 -0
  495. package/lib/modules/outbox/EventEmitter.ts +106 -0
  496. package/lib/modules/outbox/OutboxDrainer.ts +163 -0
  497. package/lib/modules/outbox/handlers/EventHandler.ts +115 -0
  498. package/lib/modules/outbox/index.ts +43 -0
  499. package/lib/modules/outbox/module.json +15 -0
  500. package/lib/modules/outbox/permissions.ts +36 -0
  501. package/lib/modules/outbox/routes.ts +219 -0
  502. package/lib/modules/outbox/schema.ts +207 -0
  503. package/lib/modules/outbox/services/OutboxEventService.ts +223 -0
  504. package/lib/observability/logger.d.ts +30 -0
  505. package/lib/observability/logger.d.ts.map +1 -0
  506. package/lib/observability/logger.js +110 -0
  507. package/lib/observability/logger.js.map +1 -0
  508. package/lib/observability/metrics.d.ts +3 -0
  509. package/lib/observability/metrics.d.ts.map +1 -0
  510. package/lib/observability/metrics.js +3 -0
  511. package/lib/observability/metrics.js.map +1 -0
  512. package/lib/observability/otel.d.ts +3 -0
  513. package/lib/observability/otel.d.ts.map +1 -0
  514. package/lib/observability/otel.js +3 -0
  515. package/lib/observability/otel.js.map +1 -0
  516. package/lib/observability/safeErrorFields.d.ts +14 -0
  517. package/lib/observability/safeErrorFields.d.ts.map +1 -0
  518. package/lib/observability/safeErrorFields.js +14 -0
  519. package/lib/observability/safeErrorFields.js.map +1 -0
  520. package/lib/observability/tracing.d.ts +3 -0
  521. package/lib/observability/tracing.d.ts.map +1 -0
  522. package/lib/observability/tracing.js +3 -0
  523. package/lib/observability/tracing.js.map +1 -0
  524. package/lib/package.json +4 -0
  525. package/lib/testing.d.ts +3 -0
  526. package/lib/testing.d.ts.map +1 -0
  527. package/lib/testing.js +3 -0
  528. package/lib/testing.js.map +1 -0
  529. package/package.json +193 -3
  530. package/templates/default/.claude/agents/qa-brief.md +106 -0
  531. package/templates/default/.claude/commands/sherpa-brief.md +23 -0
  532. package/templates/default/.cursor/rules/api.mdc +80 -0
  533. package/templates/default/.cursor/rules/commits.mdc +88 -0
  534. package/templates/default/.cursor/rules/security.mdc +92 -0
  535. package/templates/default/.cursor/rules/testing.mdc +123 -0
  536. package/templates/default/.cursor/rules/typescript.mdc +86 -0
  537. package/templates/default/.env.example +79 -0
  538. package/templates/default/.github/workflows/sherpa-brief.yml +180 -0
  539. package/templates/default/AGENTS.md +166 -0
  540. package/templates/default/CLAUDE.md +17 -0
  541. package/templates/default/README.md +97 -0
  542. package/templates/default/_gitignore +27 -0
  543. package/templates/default/docker-compose.yml +83 -0
  544. package/templates/default/drizzle.config.ts +34 -0
  545. package/templates/default/eslint.config.mjs +106 -0
  546. package/templates/default/package.json +57 -0
  547. package/templates/default/ship-api.config.ts +21 -0
  548. package/templates/default/src/client.ts +34 -0
  549. package/templates/default/src/config/modules.ts +23 -0
  550. package/templates/default/src/db/schema.ts +26 -0
  551. package/templates/default/src/environment.ts +81 -0
  552. package/templates/default/src/index.test.ts +53 -0
  553. package/templates/default/src/index.ts +11 -0
  554. package/templates/default/src/instrumentation.ts +13 -0
  555. package/templates/default/src/server.ts +90 -0
  556. package/templates/default/tsconfig.json +24 -0
@@ -0,0 +1,937 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ /**
7
+ * `ship-api g <type> <name>` — framework component generators per plan §C.
8
+ *
9
+ * This file ships **`g module <name>`** — the foundational generator that
10
+ * scaffolds a complete v4 module (schema + service + routes + validation
11
+ * + permissions + index barrel) from the canonical template at
12
+ * `packages/core/cli/templates/module/`. The template was extracted from
13
+ * the `modules/accounts/` port (the canonical reference).
14
+ *
15
+ * Future subcommands (`g resource`, `g service`, `g schema`, `g middleware`,
16
+ * `g job`, `g test`) layer on the same template-substitution mechanism.
17
+ *
18
+ * Substitution placeholders:
19
+ *
20
+ * {{namePlural}} kebab-case-plural ("accounts", "user-tokens") — for paths + string literals
21
+ * {{nameSingular}} kebab-case-singular ("account", "user-token")
22
+ * {{nameCamelPlural}} camelCase-plural JS identifier ("accounts", "userTokens")
23
+ * {{nameCamelSingular}} camelCase-singular JS identifier ("account", "userToken")
24
+ * {{NamePlural}} PascalCase-plural ("Accounts", "UserTokens")
25
+ * {{NameSingular}} PascalCase-singular ("Account", "UserToken")
26
+ * {{NAME_PLURAL}} SCREAMING_SNAKE-plural ("ACCOUNTS", "USER_TOKENS")
27
+ * {{NAME_SINGULAR}} SCREAMING_SNAKE-singular ("ACCOUNT", "USER_TOKEN")
28
+ *
29
+ * The substitution applies to file *contents* and to *file names*: a
30
+ * template file `services/Service.ts.tpl` becomes
31
+ * `services/{{NameSingular}}Service.ts.tpl` in the generator's name-rule
32
+ * (e.g., `services/AccountService.ts`). Template files end in `.tpl` so
33
+ * they're not picked up by tsc / vitest scanning the cli/ tree.
34
+ */
35
+
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const __dirname = path.dirname(__filename);
38
+ const TEMPLATE_ROOT = path.join(__dirname, "..", "templates", "module");
39
+
40
+ export async function generateCommand(type, name, opts = {}) {
41
+ switch (type) {
42
+ case "module":
43
+ return generateModule(name, opts);
44
+ case "resource":
45
+ return generateResource(name, opts);
46
+ case "middleware":
47
+ return generateMiddleware(name, opts);
48
+ case "service":
49
+ return generateService(name, opts);
50
+ case "schema":
51
+ return generateSchema(name, opts);
52
+ case "job":
53
+ return generateJob(name, opts);
54
+ default:
55
+ throw new Error(
56
+ `ship-api g ${type} is not yet implemented. ` +
57
+ `Currently supported: module, resource, middleware, service, schema, job. ` +
58
+ `(Other generators like \`test\` land in subsequent commits.)`,
59
+ );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * `g resource <name> --fields "title:string,body:text,published:boolean?"`.
65
+ *
66
+ * Like `g module` but customizes the schema + validation files to the
67
+ * supplied field spec instead of the default `name:varchar(200)`. Field
68
+ * format: `name:type[?]` — the `?` suffix marks the field nullable
69
+ * (Drizzle column lacks `.notNull()`, Zod schema is `.optional()`).
70
+ *
71
+ * Supported types: string (varchar), text, integer, bigint, boolean,
72
+ * timestamp, uuid, json. Any unknown type maps to `text` with a
73
+ * `// TODO[ship-api]` marker on the column.
74
+ */
75
+ export async function generateResource(rawName, opts = {}) {
76
+ const fields = parseFieldSpec(opts.fields ?? "");
77
+ if (fields.length === 0) {
78
+ throw new Error(
79
+ `g resource: --fields is required (e.g. --fields "title:string,body:text,published:boolean?")`,
80
+ );
81
+ }
82
+
83
+ // Run the full module scaffold first (skeleton with the default `name`
84
+ // field). Post-process to swap in the field spec.
85
+ const moduleResult = await generateModule(rawName, {
86
+ ...opts,
87
+ silent: true,
88
+ generator: "resource",
89
+ });
90
+
91
+ await rewriteSchemaForFields(path.join(moduleResult.targetRoot, "schema.ts"), fields);
92
+ await rewriteValidationForFields(
93
+ path.join(moduleResult.targetRoot, "validation", "schemas.ts"),
94
+ fields,
95
+ );
96
+
97
+ if (!opts.silent) {
98
+ printResourceReport(rawName, moduleResult, fields);
99
+ }
100
+ return { ...moduleResult, fields };
101
+ }
102
+
103
+ const FIELD_TYPE_MAP = {
104
+ string: { drizzle: "varchar(NAME, { length: 255 })", zod: "z.string()" },
105
+ text: { drizzle: "text(NAME)", zod: "z.string()" },
106
+ integer: { drizzle: "integer(NAME)", zod: "z.number().int()" },
107
+ number: { drizzle: "integer(NAME)", zod: "z.number().int()" },
108
+ bigint: {
109
+ drizzle: 'bigint(NAME, { mode: "number" })',
110
+ zod: "z.number().int()",
111
+ },
112
+ boolean: { drizzle: "boolean(NAME)", zod: "z.boolean()" },
113
+ timestamp: {
114
+ drizzle: "timestamp(NAME, { withTimezone: true })",
115
+ zod: "z.coerce.date()",
116
+ },
117
+ uuid: { drizzle: "uuid(NAME)", zod: "z.string().uuid()" },
118
+ json: {
119
+ drizzle: "jsonb(NAME).$type<Record<string, unknown>>()",
120
+ zod: "z.record(z.string(), z.unknown())",
121
+ },
122
+ };
123
+
124
+ export function parseFieldSpec(spec) {
125
+ if (!spec) return [];
126
+ const fields = [];
127
+ for (const part of spec.split(",")) {
128
+ const trimmed = part.trim();
129
+ if (!trimmed) continue;
130
+ const colon = trimmed.indexOf(":");
131
+ if (colon === -1) {
132
+ throw new Error(`g resource: malformed field "${trimmed}" — expected "name:type[?]"`);
133
+ }
134
+ const name = trimmed.slice(0, colon).trim();
135
+ let typeRaw = trimmed.slice(colon + 1).trim();
136
+ let nullable = false;
137
+ if (typeRaw.endsWith("?")) {
138
+ nullable = true;
139
+ typeRaw = typeRaw.slice(0, -1);
140
+ }
141
+ if (!/^[a-z][a-zA-Z0-9]*$/.test(name)) {
142
+ throw new Error(
143
+ `g resource: invalid field name "${name}" — must be camelCase starting with a letter`,
144
+ );
145
+ }
146
+ const known = Object.hasOwn(FIELD_TYPE_MAP, typeRaw);
147
+ fields.push({
148
+ name,
149
+ type: typeRaw,
150
+ nullable,
151
+ known,
152
+ });
153
+ }
154
+ return fields;
155
+ }
156
+
157
+ function renderSchemaColumns(fields) {
158
+ const lines = [];
159
+ for (const f of fields) {
160
+ const colName = snakeize(f.name);
161
+ const mapping = FIELD_TYPE_MAP[f.type];
162
+ if (!mapping) {
163
+ lines.push(
164
+ ` ${f.name}: text("${colName}")${f.nullable ? "" : ".notNull()"}, // TODO[ship-api]: unknown type "${f.type}"`,
165
+ );
166
+ continue;
167
+ }
168
+ const drizzleExpr = mapping.drizzle.replace("NAME", `"${colName}"`);
169
+ const notNull = f.nullable ? "" : ".notNull()";
170
+ lines.push(` ${f.name}: ${drizzleExpr}${notNull},`);
171
+ }
172
+ return lines.join("\n");
173
+ }
174
+
175
+ function renderImportsForFields(fields) {
176
+ const imports = new Set();
177
+ for (const f of fields) {
178
+ if (!f.known) {
179
+ imports.add("text");
180
+ continue;
181
+ }
182
+ // Extract the function name from the drizzle expression.
183
+ const match = FIELD_TYPE_MAP[f.type].drizzle.match(/^(\w+)\(/);
184
+ if (match) imports.add(match[1]);
185
+ }
186
+ return imports;
187
+ }
188
+
189
+ function renderCreateSchemaFields(fields) {
190
+ const lines = [];
191
+ for (const f of fields) {
192
+ const mapping = FIELD_TYPE_MAP[f.type];
193
+ const zod = mapping ? mapping.zod : "z.unknown() /* TODO[ship-api]: unknown type */";
194
+ const optional = f.nullable ? ".optional()" : "";
195
+ lines.push(` ${f.name}: ${zod}${optional},`);
196
+ }
197
+ return lines.join("\n");
198
+ }
199
+
200
+ function renderUpdateSchemaFields(fields) {
201
+ // Update schemas: every field is optional (PATCH semantics).
202
+ const lines = [];
203
+ for (const f of fields) {
204
+ const mapping = FIELD_TYPE_MAP[f.type];
205
+ const zod = mapping ? mapping.zod : "z.unknown() /* TODO[ship-api]: unknown type */";
206
+ const nullable = f.nullable ? ".nullable()" : "";
207
+ lines.push(` ${f.name}: ${zod}${nullable}.optional(),`);
208
+ }
209
+ return lines.join("\n");
210
+ }
211
+
212
+ function snakeize(camel) {
213
+ return camel.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
214
+ }
215
+
216
+ async function rewriteSchemaForFields(schemaPath, fields) {
217
+ const original = await fs.readFile(schemaPath, "utf8");
218
+ // Replace the default `name: varchar(...)` line with the rendered
219
+ // field columns. The template has exactly one `name:` line between
220
+ // the `id:` line and `...baseFields`.
221
+ const columnsBlock = renderSchemaColumns(fields);
222
+ const replaced = original.replace(
223
+ / {2}name: varchar\("name", \{ length: 200 \}\)\.notNull\(\),/,
224
+ columnsBlock,
225
+ );
226
+ // Update the imports: the template imports varchar; now we import
227
+ // whatever the field types need.
228
+ const neededImports = renderImportsForFields(fields);
229
+ neededImports.add("pgTable");
230
+ neededImports.add("uuid"); // primary key
231
+ const importList = [...neededImports].sort().join(", ");
232
+ const withImports = replaced.replace(
233
+ /import \{ pgTable, uuid, varchar \} from "drizzle-orm\/pg-core";/,
234
+ `import { ${importList} } from "drizzle-orm/pg-core";`,
235
+ );
236
+ await fs.writeFile(schemaPath, withImports);
237
+ }
238
+
239
+ async function rewriteValidationForFields(validationPath, fields) {
240
+ const original = await fs.readFile(validationPath, "utf8");
241
+ const createBlock = renderCreateSchemaFields(fields);
242
+ const updateBlock = renderUpdateSchemaFields(fields);
243
+
244
+ // The template's create schema has a single `name: z.string()...`
245
+ // line; replace it with the field block. Same for update.
246
+ const createReplaced = original.replace(
247
+ / {4}name: z\.string\(\)\.min\(1\)\.max\(200\),\n/,
248
+ createBlock + "\n",
249
+ );
250
+ const updateReplaced = createReplaced.replace(
251
+ / {4}name: z\.string\(\)\.min\(1\)\.max\(200\)\.optional\(\),\n/,
252
+ updateBlock + "\n",
253
+ );
254
+
255
+ await fs.writeFile(validationPath, updateReplaced);
256
+ }
257
+
258
+ function printResourceReport(rawName, moduleResult, fields) {
259
+ console.log("");
260
+ console.log(
261
+ chalk.bold(`ship-api g resource ${rawName}`),
262
+ chalk.dim(`→ ${moduleResult.targetRoot}`),
263
+ );
264
+ console.log("");
265
+ for (const f of moduleResult.files) {
266
+ console.log(" ", chalk.green("✚"), path.relative(moduleResult.targetRoot, f));
267
+ }
268
+ console.log("");
269
+ console.log(chalk.dim("Fields:"));
270
+ for (const f of fields) {
271
+ const marker = f.nullable ? "?" : " ";
272
+ const known = f.known ? "" : chalk.yellow(" [unknown type — review]");
273
+ console.log(` ${marker} ${chalk.cyan(f.name)}: ${f.type}${known}`);
274
+ }
275
+ console.log("");
276
+ console.log(chalk.dim("Next steps:"));
277
+ console.log(
278
+ chalk.dim(" • Add the schema to your composed schema:"),
279
+ chalk.cyan(
280
+ `createApp({ schema: { ${moduleResult.names.namePlural}: ${moduleResult.names.namePlural} } })`,
281
+ ),
282
+ );
283
+ console.log(
284
+ chalk.dim(" • Mount the module sub-app on the root:"),
285
+ chalk.cyan(
286
+ `const routedApp = root.route("/", build${moduleResult.names.NamePlural}App({ service, provider }))`,
287
+ ),
288
+ );
289
+ console.log(chalk.dim(" • Run:"), chalk.cyan("npm run db:generate"));
290
+ console.log("");
291
+ console.log(
292
+ chalk.dim("Scaffold metadata recorded in module.json (start-point for delivery metrics)."),
293
+ );
294
+ console.log("");
295
+ }
296
+
297
+ export async function generateModule(rawName, opts = {}) {
298
+ const cwd = opts.cwd ?? process.cwd();
299
+ const targetRoot =
300
+ opts.targetRoot ?? path.join(cwd, "src", "modules", normalizeNamePlural(rawName));
301
+
302
+ const names = computeNames(rawName);
303
+ await ensureTargetEmpty(targetRoot, opts.force ?? false);
304
+ const written = await writeTemplate(TEMPLATE_ROOT, targetRoot, names);
305
+ await stampScaffoldMetadata(targetRoot, names, opts);
306
+
307
+ if (!opts.silent) {
308
+ printReport(rawName, names, targetRoot, written);
309
+ }
310
+ return { targetRoot, names, files: written };
311
+ }
312
+
313
+ // ─── In-module file generators ────────────────────────────────────────
314
+ //
315
+ // `g middleware <name>` — adds a Hono middleware skeleton at
316
+ // `src/middleware/<name>.ts` in the consumer's project.
317
+ //
318
+ // `g service <name> -m <module>` — adds a `<NameSingular>Service.ts`
319
+ // to an existing module's `services/` directory.
320
+ //
321
+ // `g schema <name> -m <module>` — adds an extra Drizzle pgTable file
322
+ // to a module's tree at `modules/<module>/schemas/<name>.ts`.
323
+ //
324
+ // `g job <name> -m <module>` — adds a BullMQ job stub at
325
+ // `modules/<module>/jobs/<name>.job.ts`.
326
+ //
327
+ // All four use the same name-substitution machinery as `g module`.
328
+
329
+ export async function generateMiddleware(rawName, opts = {}) {
330
+ const cwd = opts.cwd ?? process.cwd();
331
+ const names = computeMiddlewareNames(rawName);
332
+ const targetPath =
333
+ opts.targetPath ?? path.join(cwd, "src", "middleware", `${names.nameKebab}.ts`);
334
+ await ensureFileVacant(targetPath, opts.force ?? false);
335
+
336
+ const contents = renderMiddleware(names);
337
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
338
+ await fs.writeFile(targetPath, contents);
339
+
340
+ if (!opts.silent) printSingleFileReport("middleware", rawName, targetPath);
341
+ return { targetPath, names };
342
+ }
343
+
344
+ export async function generateService(rawName, opts = {}) {
345
+ if (!opts.module) {
346
+ throw new Error("g service: --module is required (which module to add the service to)");
347
+ }
348
+ const cwd = opts.cwd ?? process.cwd();
349
+ const moduleNames = computeNames(opts.module);
350
+ const moduleDir = path.join(cwd, "src", "modules", moduleNames.namePlural);
351
+ await ensureModuleDirExists(moduleDir, opts.module);
352
+
353
+ const names = computeServiceNames(rawName);
354
+ const fileName = `${names.NameSingular}Service.ts`;
355
+ const targetPath = opts.targetPath ?? path.join(moduleDir, "services", fileName);
356
+ await ensureFileVacant(targetPath, opts.force ?? false);
357
+
358
+ const contents = renderService(names, moduleNames);
359
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
360
+ await fs.writeFile(targetPath, contents);
361
+
362
+ if (!opts.silent) printSingleFileReport("service", rawName, targetPath);
363
+ return { targetPath, names, moduleNames };
364
+ }
365
+
366
+ export async function generateSchema(rawName, opts = {}) {
367
+ if (!opts.module) {
368
+ throw new Error("g schema: --module is required");
369
+ }
370
+ const cwd = opts.cwd ?? process.cwd();
371
+ const moduleNames = computeNames(opts.module);
372
+ const moduleDir = path.join(cwd, "src", "modules", moduleNames.namePlural);
373
+ await ensureModuleDirExists(moduleDir, opts.module);
374
+
375
+ const names = computeSchemaNames(rawName);
376
+ const targetPath = opts.targetPath ?? path.join(moduleDir, "schemas", `${names.nameKebab}.ts`);
377
+ await ensureFileVacant(targetPath, opts.force ?? false);
378
+
379
+ const contents = renderSchema(names);
380
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
381
+ await fs.writeFile(targetPath, contents);
382
+
383
+ if (!opts.silent) printSingleFileReport("schema", rawName, targetPath);
384
+ return { targetPath, names, moduleNames };
385
+ }
386
+
387
+ export async function generateJob(rawName, opts = {}) {
388
+ if (!opts.module) {
389
+ throw new Error("g job: --module is required");
390
+ }
391
+ const cwd = opts.cwd ?? process.cwd();
392
+ const moduleNames = computeNames(opts.module);
393
+ const moduleDir = path.join(cwd, "src", "modules", moduleNames.namePlural);
394
+ await ensureModuleDirExists(moduleDir, opts.module);
395
+
396
+ const names = computeJobNames(rawName);
397
+ const targetPath = opts.targetPath ?? path.join(moduleDir, "jobs", `${names.nameKebab}.job.ts`);
398
+ await ensureFileVacant(targetPath, opts.force ?? false);
399
+
400
+ const contents = renderJob(names, moduleNames);
401
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
402
+ await fs.writeFile(targetPath, contents);
403
+
404
+ if (!opts.silent) printSingleFileReport("job", rawName, targetPath);
405
+ return { targetPath, names, moduleNames };
406
+ }
407
+
408
+ // ─── Name helpers (each subcommand has a slightly different shape) ────
409
+
410
+ function computeMiddlewareNames(raw) {
411
+ const kebab = rawNameToKebab(raw, "g middleware");
412
+ return {
413
+ nameKebab: kebab,
414
+ nameCamel: kebabToCamel(kebab),
415
+ NamePascal: kebabToPascal(kebab),
416
+ };
417
+ }
418
+
419
+ function computeServiceNames(raw) {
420
+ const kebab = rawNameToKebab(raw, "g service");
421
+ // Service names are typically singular ("Account", not "Accounts").
422
+ const singular = singularizeKebab(kebab);
423
+ return {
424
+ nameKebab: singular,
425
+ NameSingular: kebabToPascal(singular),
426
+ nameCamelSingular: kebabToCamel(singular),
427
+ };
428
+ }
429
+
430
+ function computeSchemaNames(raw) {
431
+ const kebab = rawNameToKebab(raw, "g schema");
432
+ // Pluralized for table name (matches the v4 convention — tables are plural).
433
+ const plural = pluralizeKebab(singularizeKebab(kebab));
434
+ return {
435
+ nameKebab: kebab,
436
+ namePlural: plural,
437
+ nameSingular: singularizeKebab(plural),
438
+ nameCamelPlural: kebabToCamel(plural),
439
+ NamePascal: kebabToPascal(singularizeKebab(plural)),
440
+ };
441
+ }
442
+
443
+ function computeJobNames(raw) {
444
+ const kebab = rawNameToKebab(raw, "g job");
445
+ return {
446
+ nameKebab: kebab,
447
+ nameCamel: kebabToCamel(kebab),
448
+ NamePascal: kebabToPascal(kebab),
449
+ };
450
+ }
451
+
452
+ function rawNameToKebab(raw, contextLabel) {
453
+ const trimmed = raw.trim();
454
+ if (!trimmed) throw new Error(`${contextLabel}: name cannot be empty`);
455
+ const kebab = trimmed
456
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
457
+ .replace(/[_\s]+/g, "-")
458
+ .toLowerCase();
459
+ const cleaned = kebab.replace(/^-+|-+$/g, "");
460
+ if (!/^[a-z][a-z0-9-]*$/.test(cleaned)) {
461
+ throw new Error(
462
+ `${contextLabel}: invalid name "${raw}" — must be kebab-case starting with a letter`,
463
+ );
464
+ }
465
+ return cleaned;
466
+ }
467
+
468
+ // ─── File renderers ────────────────────────────────────────────────────
469
+
470
+ function renderMiddleware(names) {
471
+ return `import type { MiddlewareHandler } from "hono";
472
+ import type { AppEnv } from "@sixthsummit/ship-api/http/app/createApp";
473
+
474
+ /**
475
+ * \`${names.nameCamel}({ ... })\` — Hono middleware factory.
476
+ *
477
+ * Customize the options + the before/after-handler logic. Wire the
478
+ * middleware into the route chain (or globally via \`app.use("*", ...)\`).
479
+ */
480
+
481
+ export interface ${names.NamePascal}Options {
482
+ // TODO: declare middleware-specific options here.
483
+ }
484
+
485
+ export function ${names.nameCamel}(
486
+ opts: ${names.NamePascal}Options = {},
487
+ ): MiddlewareHandler<AppEnv> {
488
+ void opts;
489
+ return async (c, next) => {
490
+ // Before-handler logic (auth checks, request enrichment, etc.).
491
+
492
+ await next();
493
+
494
+ // After-handler logic (response shaping, headers, observability).
495
+ };
496
+ }
497
+ `;
498
+ }
499
+
500
+ function renderService(names, moduleNames) {
501
+ return `import { DrizzleEntityService } from "@sixthsummit/ship-api/app/services/DrizzleEntityService";
502
+ import { ${moduleNames.nameCamelPlural} } from "../schema.js";
503
+
504
+ /**
505
+ * \`${names.NameSingular}Service\` — extends \`DrizzleEntityService\` against
506
+ * the module's \`${moduleNames.nameCamelPlural}\` table. Inherits get / getAll /
507
+ * list / count / create / update / delete with soft-delete + audit-column
508
+ * auto-stamping; add domain-specific methods below.
509
+ */
510
+ export class ${names.NameSingular}Service extends DrizzleEntityService<
511
+ typeof ${moduleNames.nameCamelPlural},
512
+ { ${moduleNames.nameCamelPlural}: typeof ${moduleNames.nameCamelPlural} }
513
+ > {
514
+ protected getTable() {
515
+ return ${moduleNames.nameCamelPlural};
516
+ }
517
+
518
+ protected override getDefaultSearchFields(): readonly string[] {
519
+ return [];
520
+ }
521
+
522
+ // Domain-specific methods go here. Example:
523
+ //
524
+ // async findByExternalId(externalId: string) { ... }
525
+ }
526
+ `;
527
+ }
528
+
529
+ function renderSchema(names) {
530
+ return `import { pgTable, uuid, varchar } from "drizzle-orm/pg-core";
531
+ import { baseFields } from "@sixthsummit/ship-api/app/db/baseFields";
532
+
533
+ /**
534
+ * Replace this skeleton with the real columns + indexes for
535
+ * \`${names.namePlural}\`. Run \`npm run db:generate\` after editing.
536
+ */
537
+ export const ${names.nameCamelPlural} = pgTable("${names.namePlural}", {
538
+ id: uuid("id").primaryKey().defaultRandom(),
539
+ name: varchar("name", { length: 200 }).notNull(),
540
+ ...baseFields,
541
+ });
542
+
543
+ export type ${names.NamePascal} = typeof ${names.nameCamelPlural}.$inferSelect;
544
+ export type New${names.NamePascal} = typeof ${names.nameCamelPlural}.$inferInsert;
545
+ `;
546
+ }
547
+
548
+ function renderJob(names, moduleNames) {
549
+ return `import { z } from "zod";
550
+
551
+ /**
552
+ * \`${names.nameKebab}\` — BullMQ job for the ${moduleNames.namePlural} module.
553
+ *
554
+ * Replace the payload schema + processor body with the real
555
+ * implementation. The framework auto-discovers default exports
556
+ * named \`${names.nameKebab}.job.ts\` when \`InitJobs\` runs at boot
557
+ * (with \`WORKER_MODE=worker\` or \`all\`).
558
+ *
559
+ * NOTE: This is a stub that ships ahead of the framework's
560
+ * \`defineJob\` API export. Once \`defineJob\` lands in the public
561
+ * barrel, swap the placeholder export below for:
562
+ *
563
+ * import { defineJob } from "@sixthsummit/ship-api";
564
+ * export default defineJob({ ... });
565
+ */
566
+
567
+ export const ${names.nameCamel}PayloadSchema = z.object({
568
+ // TODO: declare the payload shape.
569
+ });
570
+
571
+ export type ${names.NamePascal}Payload = z.infer<
572
+ typeof ${names.nameCamel}PayloadSchema
573
+ >;
574
+
575
+ export const ${names.nameCamel}Job = {
576
+ queue: "${moduleNames.namePlural}",
577
+ name: "${names.nameKebab}",
578
+ payloadSchema: ${names.nameCamel}PayloadSchema,
579
+ async processor(payload: ${names.NamePascal}Payload): Promise<void> {
580
+ void payload;
581
+ // TODO: implement.
582
+ },
583
+ };
584
+
585
+ export default ${names.nameCamel}Job;
586
+ `;
587
+ }
588
+
589
+ // ─── Shared filesystem helpers ─────────────────────────────────────────
590
+
591
+ async function ensureFileVacant(targetPath, force) {
592
+ try {
593
+ await fs.access(targetPath);
594
+ if (!force) {
595
+ throw new Error(`Target ${targetPath} already exists (pass --force to overwrite)`);
596
+ }
597
+ } catch (err) {
598
+ if (err && err.code === "ENOENT") return;
599
+ if (err.message?.startsWith("Target")) throw err;
600
+ throw err;
601
+ }
602
+ }
603
+
604
+ async function ensureModuleDirExists(moduleDir, moduleName) {
605
+ try {
606
+ const stat = await fs.stat(moduleDir);
607
+ if (!stat.isDirectory()) {
608
+ throw new Error(`Module path ${moduleDir} is not a directory`);
609
+ }
610
+ } catch (err) {
611
+ if (err && err.code === "ENOENT") {
612
+ throw new Error(
613
+ `Module "${moduleName}" not found at ${moduleDir} — run \`ship-api g module ${moduleName}\` first`,
614
+ );
615
+ }
616
+ throw err;
617
+ }
618
+ }
619
+
620
+ function printSingleFileReport(kind, rawName, targetPath) {
621
+ console.log("");
622
+ console.log(chalk.bold(`ship-api g ${kind} ${rawName}`), chalk.dim(`→ ${targetPath}`));
623
+ console.log("");
624
+ console.log(" ", chalk.green("✚"), targetPath);
625
+ console.log("");
626
+ }
627
+
628
+ function normalizeNamePlural(raw) {
629
+ // Accept "accounts", "Account", "user-tokens", "userToken", etc.; emit
630
+ // canonical kebab-case-plural. Idempotent — feeding "accounts" back in
631
+ // returns "accounts" (not "accountses").
632
+ const trimmed = raw.trim();
633
+ if (!trimmed) throw new Error("g module: name cannot be empty");
634
+ // Convert camelCase / PascalCase boundaries to dashes, lowercase.
635
+ const kebab = trimmed
636
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
637
+ .replace(/[_\s]+/g, "-")
638
+ .toLowerCase();
639
+ const cleaned = kebab.replace(/^-+|-+$/g, "");
640
+ if (!/^[a-z][a-z0-9-]*$/.test(cleaned)) {
641
+ throw new Error(
642
+ `g module: invalid name "${raw}" — must be kebab-case starting with a letter (a-z, 0-9, -).`,
643
+ );
644
+ }
645
+ // Idempotent pluralization: re-pluralizing the singularized form
646
+ // recovers the same plural regardless of what the user passed.
647
+ return pluralizeKebab(singularizeKebab(cleaned));
648
+ }
649
+
650
+ export function computeNames(raw) {
651
+ const namePlural = normalizeNamePlural(raw);
652
+ const nameSingular = singularizeKebab(namePlural);
653
+ return {
654
+ namePlural,
655
+ nameSingular,
656
+ nameCamelPlural: kebabToCamel(namePlural),
657
+ nameCamelSingular: kebabToCamel(nameSingular),
658
+ NamePlural: kebabToPascal(namePlural),
659
+ NameSingular: kebabToPascal(nameSingular),
660
+ NAME_PLURAL: namePlural.replace(/-/g, "_").toUpperCase(),
661
+ NAME_SINGULAR: nameSingular.replace(/-/g, "_").toUpperCase(),
662
+ };
663
+ }
664
+
665
+ function kebabToPascal(kebab) {
666
+ return kebab
667
+ .split("-")
668
+ .filter(Boolean)
669
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
670
+ .join("");
671
+ }
672
+
673
+ function kebabToCamel(kebab) {
674
+ const parts = kebab.split("-").filter(Boolean);
675
+ if (parts.length === 0) return "";
676
+ return (
677
+ parts[0] +
678
+ parts
679
+ .slice(1)
680
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
681
+ .join("")
682
+ );
683
+ }
684
+
685
+ /**
686
+ * Pluralize the LAST segment of a kebab-case identifier — keeps multi-
687
+ * word module names readable (`user-token` → `user-tokens`, not
688
+ * `user-tokens` becoming the unholy `user-tokenses`).
689
+ */
690
+ function pluralizeKebab(kebab) {
691
+ const parts = kebab.split("-");
692
+ parts[parts.length - 1] = pluralizeWord(parts[parts.length - 1]);
693
+ return parts.join("-");
694
+ }
695
+
696
+ function singularizeKebab(kebab) {
697
+ const parts = kebab.split("-");
698
+ parts[parts.length - 1] = singularizeWord(parts[parts.length - 1]);
699
+ return parts.join("-");
700
+ }
701
+
702
+ /**
703
+ * Minimal English pluralization — handles the common cases that show up
704
+ * in module names. Engineers naming a module `mongoose-octopi` get
705
+ * what they deserve.
706
+ */
707
+ function pluralizeWord(word) {
708
+ if (word.endsWith("y") && !"aeiou".includes(word[word.length - 2]))
709
+ return word.slice(0, -1) + "ies";
710
+ if (
711
+ word.endsWith("s") ||
712
+ word.endsWith("x") ||
713
+ word.endsWith("z") ||
714
+ word.endsWith("ch") ||
715
+ word.endsWith("sh")
716
+ )
717
+ return word + "es";
718
+ if (word.endsWith("o") && !"aeiou".includes(word[word.length - 2])) return word + "es";
719
+ return word + "s";
720
+ }
721
+
722
+ function singularizeWord(word) {
723
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
724
+ if (word.endsWith("sses") || word.endsWith("shes") || word.endsWith("ches"))
725
+ return word.slice(0, -2);
726
+ if (word.endsWith("xes") || word.endsWith("zes") || word.endsWith("oes"))
727
+ return word.slice(0, -2);
728
+ if (word.endsWith("s") && !word.endsWith("ss")) return word.slice(0, -1);
729
+ return word;
730
+ }
731
+
732
+ // Backwards-compat exports for tests that already imported the per-segment
733
+ // helpers under the original names.
734
+ const pluralize = pluralizeWord;
735
+ const singularize = singularizeWord;
736
+
737
+ async function ensureTargetEmpty(target, force) {
738
+ try {
739
+ const stat = await fs.stat(target);
740
+ if (stat.isDirectory()) {
741
+ const entries = await fs.readdir(target);
742
+ if (entries.length > 0 && !force) {
743
+ throw new Error(`g module: target ${target} is not empty (pass --force to overwrite)`);
744
+ }
745
+ }
746
+ } catch (err) {
747
+ if (err && err.code === "ENOENT") return;
748
+ if (err.message?.startsWith("g module:")) throw err;
749
+ throw err;
750
+ }
751
+ }
752
+
753
+ async function writeTemplate(templateRoot, targetRoot, names) {
754
+ const files = [];
755
+ await walk(templateRoot, templateRoot, async (srcPath, relPath) => {
756
+ if (!srcPath.endsWith(".tpl")) return;
757
+ const targetRel = applyNameSubstitutions(relPath.slice(0, -".tpl".length), names);
758
+ const targetPath = path.join(targetRoot, targetRel);
759
+ const contents = await fs.readFile(srcPath, "utf8");
760
+ const substituted = applyNameSubstitutions(contents, names);
761
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
762
+ await fs.writeFile(targetPath, substituted);
763
+ files.push(targetPath);
764
+ });
765
+ return files;
766
+ }
767
+
768
+ /**
769
+ * Recursive directory walker. The `root` argument is the original walk
770
+ * root and stays constant through recursion so `relPath` is always the
771
+ * subpath under the original root, not just the filename.
772
+ */
773
+ async function walk(root, dir, visit) {
774
+ const entries = await fs.readdir(dir, { withFileTypes: true });
775
+ for (const entry of entries) {
776
+ const full = path.join(dir, entry.name);
777
+ if (entry.isDirectory()) {
778
+ await walk(root, full, visit);
779
+ } else if (entry.isFile()) {
780
+ const rel = path.relative(root, full);
781
+ await visit(full, rel);
782
+ }
783
+ }
784
+ }
785
+
786
+ function applyNameSubstitutions(text, names) {
787
+ // Order matters: replace the longer, more-specific tokens first so
788
+ // `{{NamePlural}}` doesn't get half-eaten by `{{Name`-prefix matches.
789
+ return text
790
+ .replaceAll("{{nameCamelPlural}}", names.nameCamelPlural)
791
+ .replaceAll("{{nameCamelSingular}}", names.nameCamelSingular)
792
+ .replaceAll("{{NamePlural}}", names.NamePlural)
793
+ .replaceAll("{{NameSingular}}", names.NameSingular)
794
+ .replaceAll("{{NAME_PLURAL}}", names.NAME_PLURAL)
795
+ .replaceAll("{{NAME_SINGULAR}}", names.NAME_SINGULAR)
796
+ .replaceAll("{{namePlural}}", names.namePlural)
797
+ .replaceAll("{{nameSingular}}", names.nameSingular);
798
+ }
799
+
800
+ /**
801
+ * Stamp un-backfillable scaffold provenance into the module's
802
+ * `module.json`. This is the start-point breadcrumb for the
803
+ * "time-to-module" delivery metric (see `docs/DELIVERY_METRICS.md`, M3):
804
+ * it records *when* a module was generated, by *which* generator, and
805
+ * under *which* framework version — independent of commit-message
806
+ * discipline, because the field rides into git as part of the module and
807
+ * cannot be reconstructed after the fact.
808
+ *
809
+ * The timestamp and framework version are injectable via `opts.now` and
810
+ * `opts.frameworkVersion` so the generator stays deterministic under test.
811
+ * If a (future) template ships no `module.json`, stamping is a no-op
812
+ * rather than an error.
813
+ */
814
+ async function stampScaffoldMetadata(targetRoot, names, opts) {
815
+ const manifestPath = path.join(targetRoot, "module.json");
816
+ let manifest;
817
+ try {
818
+ manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
819
+ } catch {
820
+ return;
821
+ }
822
+ manifest.scaffold = {
823
+ generator: opts.generator ?? "module",
824
+ name: names.namePlural,
825
+ at: normalizeTimestamp(opts.now),
826
+ frameworkVersion: opts.frameworkVersion ?? (await resolveFrameworkVersion()),
827
+ tool: detectScaffoldTool(opts),
828
+ };
829
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
830
+ }
831
+
832
+ /**
833
+ * Best-effort detection of the agent/editor that drove this scaffold, for the
834
+ * by-tool split of the time-to-module metric (DELIVERY_METRICS M3/M6 — agent
835
+ * output-parity). Precedence: explicit `opts.tool` (test injection) >
836
+ * `SHIP_API_SCAFFOLD_TOOL` (the reliable override) > env markers > "cli".
837
+ *
838
+ * Marker caveats — why the override is the canonical path:
839
+ * - Claude Code exports `CLAUDECODE=1` (its documented presence flag). We key
840
+ * on exactly that, not the broader `CLAUDE_CODE_*` family, because env is
841
+ * inherited by child processes — a Cursor/CLI run launched from a shell that
842
+ * once hosted Claude Code could otherwise be mislabelled. (The single flag
843
+ * narrows, though doesn't eliminate, that inheritance surface.)
844
+ * - Cursor has no canonical equivalent of `CLAUDECODE`; `CURSOR_TRACE_ID` is a
845
+ * best-effort signal and is often absent, so a Cursor run usually falls
846
+ * through to "cli". Cursor users should set `SHIP_API_SCAFFOLD_TOOL=cursor`
847
+ * so the by-tool metric attributes correctly.
848
+ *
849
+ * "cli" is a valid, honest answer, not a failure. `env` is injectable for
850
+ * tests. (Reading process.env here is fine — build tooling, not framework
851
+ * runtime, so it doesn't route through `defineEnv`.)
852
+ */
853
+ export function detectScaffoldTool(opts = {}, env = process.env) {
854
+ if (opts.tool) return opts.tool;
855
+ if (env.SHIP_API_SCAFFOLD_TOOL) return env.SHIP_API_SCAFFOLD_TOOL;
856
+ if (env.CLAUDECODE) return "claude-code";
857
+ if (env.CURSOR_TRACE_ID) return "cursor";
858
+ return "cli";
859
+ }
860
+
861
+ function normalizeTimestamp(now) {
862
+ if (now instanceof Date) return now.toISOString();
863
+ if (typeof now === "string" && now.length > 0) return now;
864
+ return new Date().toISOString();
865
+ }
866
+
867
+ let _frameworkVersionCache;
868
+
869
+ /**
870
+ * The version of `@sixthsummit/ship-api` running this generator, read from
871
+ * the package root (two levels up from `cli/commands/`) — used as the
872
+ * default `frameworkVersion` in scaffold provenance. Falls back to
873
+ * `"unknown"` if the package.json can't be read. Memoized.
874
+ */
875
+ async function resolveFrameworkVersion() {
876
+ if (_frameworkVersionCache !== undefined) return _frameworkVersionCache;
877
+ try {
878
+ const pkgPath = path.join(__dirname, "..", "..", "package.json");
879
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
880
+ _frameworkVersionCache = typeof pkg.version === "string" ? pkg.version : "unknown";
881
+ } catch {
882
+ _frameworkVersionCache = "unknown";
883
+ }
884
+ return _frameworkVersionCache;
885
+ }
886
+
887
+ function printReport(rawName, names, targetRoot, written) {
888
+ console.log("");
889
+ console.log(chalk.bold(`ship-api g module ${rawName}`), chalk.dim(`→ ${targetRoot}`));
890
+ console.log("");
891
+ for (const f of written) {
892
+ console.log(" ", chalk.green("✚"), path.relative(targetRoot, f));
893
+ }
894
+ console.log("");
895
+ console.log(chalk.dim("Next steps:"));
896
+ console.log(
897
+ chalk.dim(" • Add the schema to your composed schema:"),
898
+ chalk.cyan(`createApp({ schema: { ${names.namePlural}: ${names.namePlural} } })`),
899
+ );
900
+ console.log(
901
+ chalk.dim(
902
+ " • Mount the module sub-app on the root (capture the return — it carries the RPC types):",
903
+ ),
904
+ chalk.cyan(
905
+ `const routedApp = root.route("/", build${names.NamePlural}App({ service, provider }))`,
906
+ ),
907
+ );
908
+ console.log(
909
+ chalk.dim(" and point the typed-client export at it:"),
910
+ chalk.cyan(`export type ShipApiClient = typeof routedApp`),
911
+ );
912
+ console.log(chalk.dim(" • Run:"), chalk.cyan("npm run db:generate"));
913
+ console.log("");
914
+ console.log(
915
+ chalk.dim("Scaffold metadata recorded in module.json (start-point for delivery metrics)."),
916
+ );
917
+ console.log("");
918
+ }
919
+
920
+ // Exposed for tests.
921
+ export const __test = {
922
+ computeNames,
923
+ applyNameSubstitutions,
924
+ pluralize,
925
+ singularize,
926
+ kebabToPascal,
927
+ kebabToCamel,
928
+ computeMiddlewareNames,
929
+ computeServiceNames,
930
+ computeSchemaNames,
931
+ computeJobNames,
932
+ parseFieldSpec,
933
+ renderSchemaColumns,
934
+ renderCreateSchemaFields,
935
+ renderUpdateSchemaFields,
936
+ TEMPLATE_ROOT,
937
+ };