@podosoft/podokit 0.3.0 → 0.5.0

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 (245) hide show
  1. package/README.md +2 -2
  2. package/dist/add.d.ts +2 -0
  3. package/dist/add.js +5 -1
  4. package/dist/assemble.d.ts +14 -0
  5. package/dist/assemble.js +60 -0
  6. package/dist/create.d.ts +2 -0
  7. package/dist/create.js +7 -0
  8. package/dist/eject.d.ts +11 -0
  9. package/dist/eject.js +27 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +122 -1
  12. package/dist/inspect.d.ts +43 -0
  13. package/dist/inspect.js +82 -0
  14. package/dist/lockfile.d.ts +80 -0
  15. package/dist/lockfile.js +193 -0
  16. package/dist/templates/base/dot-env.example +11 -6
  17. package/dist/templates/fullstack-nest-svelte/README.md +2 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +21 -2
  19. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
  20. package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
  21. package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
  22. package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
  23. package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
  24. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +4 -3
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  30. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
  31. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +44 -5
  32. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
  33. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  34. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  35. package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
  36. package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
  37. package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
  38. package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
  39. package/dist/templates/fullstack-nest-svelte/package.json +11 -3
  40. package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
  41. package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
  42. package/dist/templates/fullstack-nest-svelte/tests/package.json +15 -0
  43. package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
  44. package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
  45. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +26 -0
  46. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
  47. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +65 -0
  48. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
  49. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
  50. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
  51. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +39 -0
  52. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
  53. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
  54. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
  55. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
  56. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
  57. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
  58. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
  59. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
  60. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
  61. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
  62. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
  63. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
  64. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
  65. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
  66. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
  67. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
  68. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
  69. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  70. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
  71. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  72. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  73. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  74. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
  75. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  76. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  77. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  78. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
  79. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
  80. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
  81. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
  82. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
  83. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
  84. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
  85. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
  86. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
  87. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
  88. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
  89. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
  90. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
  91. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
  92. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
  93. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
  94. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
  95. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
  96. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
  97. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
  98. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
  99. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
  100. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
  101. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
  102. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
  103. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
  104. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
  105. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
  106. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
  107. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
  108. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +575 -0
  109. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
  110. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +30 -0
  111. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
  112. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
  113. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +45 -0
  114. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
  115. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +685 -0
  116. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +6 -0
  117. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +123 -0
  118. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
  119. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
  120. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +7 -0
  121. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +156 -0
  122. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
  123. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
  124. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +7 -0
  125. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +552 -0
  126. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
  127. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +61 -0
  128. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
  129. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +181 -0
  130. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
  131. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +76 -0
  132. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
  133. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
  134. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
  135. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
  136. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
  137. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +97 -0
  138. package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
  139. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +306 -0
  140. package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
  141. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
  142. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
  143. package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
  144. package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
  145. package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
  146. package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
  147. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
  148. package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +40 -0
  149. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +126 -0
  150. package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
  151. package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
  152. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
  153. package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
  154. package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
  155. package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
  156. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +22 -0
  157. package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
  158. package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
  159. package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
  160. package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
  161. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +22 -0
  162. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
  163. package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
  164. package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
  165. package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +145 -0
  166. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +51 -0
  167. package/dist/templates/modules/admin-dashboard/module.manifest.json +47 -0
  168. package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
  169. package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
  170. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
  171. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
  172. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +74 -0
  173. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
  174. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
  175. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
  176. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +38 -21
  177. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
  178. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
  179. package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
  180. package/dist/templates/modules/audit-log/module.manifest.json +37 -8
  181. package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +85 -3
  182. package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
  183. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
  184. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +199 -31
  185. package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
  186. package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
  187. package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
  188. package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
  189. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
  190. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
  191. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
  192. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
  193. package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
  194. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
  195. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
  196. package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
  197. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
  198. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
  199. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
  200. package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
  201. package/dist/templates/modules/auth/module.manifest.json +54 -20
  202. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
  203. package/dist/templates/modules/bullmq/module.manifest.json +4 -4
  204. package/dist/templates/modules/file-upload/module.manifest.json +3 -3
  205. package/dist/templates/modules/job-progress/module.manifest.json +9 -9
  206. package/dist/templates/modules/logging/module.manifest.json +3 -3
  207. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
  208. package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
  209. package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
  210. package/dist/templates/modules/redis/module.manifest.json +4 -4
  211. package/dist/templates/modules/sse/module.manifest.json +4 -4
  212. package/dist/templates/todo/README.md +2 -2
  213. package/dist/templates/todo/apps/api/package.json +21 -2
  214. package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
  215. package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
  216. package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
  217. package/dist/templates/todo/apps/api/src/main.ts +1 -1
  218. package/dist/templates/todo/apps/api/tsconfig.json +14 -5
  219. package/dist/templates/todo/apps/web/package.json +4 -3
  220. package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
  221. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  222. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  223. package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
  224. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +44 -5
  225. package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
  226. package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  227. package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  228. package/dist/templates/todo/dot-env.example +10 -5
  229. package/dist/templates/todo/dot-gitignore +5 -0
  230. package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
  231. package/dist/templates/todo/package.json +11 -3
  232. package/dist/templates/todo/tests/README.md +15 -0
  233. package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
  234. package/dist/templates/todo/tests/package.json +15 -0
  235. package/dist/templates/todo/tests/playwright.config.ts +25 -0
  236. package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
  237. package/dist/update.d.ts +49 -0
  238. package/dist/update.js +158 -0
  239. package/package.json +2 -2
  240. package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
  241. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
  242. package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
  243. package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
  244. package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
  245. package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "CommonJS",
3
+ "module": "nodenext",
4
4
  "target": "ES2022",
5
- "moduleResolution": "Node",
5
+ "moduleResolution": "nodenext",
6
6
  "outDir": "dist",
7
7
  "rootDir": "src",
8
8
  "declaration": false,
@@ -14,8 +14,17 @@
14
14
  "noImplicitReturns": true,
15
15
  "esModuleInterop": true,
16
16
  "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true
17
+ "forceConsistentCasingInFileNames": true,
18
+ "types": [
19
+ "node"
20
+ ]
18
21
  },
19
- "include": ["src/**/*.ts"],
20
- "exclude": ["node_modules", "dist", "test"]
22
+ "include": [
23
+ "src/**/*.ts"
24
+ ],
25
+ "exclude": [
26
+ "node_modules",
27
+ "dist",
28
+ "test"
29
+ ]
21
30
  }
@@ -4,15 +4,16 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite dev --port 5173",
7
+ "dev": "vite dev --port 5001",
8
8
  "build": "vite build",
9
9
  "preview": "vite preview",
10
10
  "lint": "svelte-check --tsconfig ./tsconfig.json",
11
- "test": "echo \"add Playwright tests here\" && exit 0"
11
+ "test": "echo \"unit: add vitest; e2e: npm run test:e2e (see tests/)\" && exit 0"
12
12
  },
13
13
  "dependencies": {
14
14
  "@internationalized/date": "^3.12.2",
15
15
  "@lucide/svelte": "^1.23.0",
16
+ "@podosoft/podokit-api-client": "^0.5.0",
16
17
  "bits-ui": "^2.18.1",
17
18
  "clsx": "^2.1.1",
18
19
  "mode-watcher": "^1.1.0",
@@ -29,7 +30,7 @@
29
30
  "svelte-check": "^4.0.0",
30
31
  "tailwindcss": "^4.0.0",
31
32
  "typesafe-i18n": "^5.26.2",
32
- "typescript": "^5.6.3",
33
+ "typescript": "^6.0.3",
33
34
  "vite": "^5.4.0",
34
35
  "tw-animate-css": "^1.4.0"
35
36
  }
@@ -0,0 +1,5 @@
1
+ import { createApiClient } from "@podosoft/podokit-api-client";
2
+
3
+ // Browser client: same-origin. REST calls go to /api/* and auth to /api/auth/*,
4
+ // both proxied to the backend by the SvelteKit server (see routes/api/**).
5
+ export const api = createApiClient();
@@ -0,0 +1,7 @@
1
+ import Root from "./switch.svelte";
2
+
3
+ export {
4
+ Root,
5
+ //
6
+ Root as Switch,
7
+ };
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { Switch as SwitchPrimitive } from "bits-ui";
3
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ checked = $bindable(false),
8
+ class: className,
9
+ ...restProps
10
+ }: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
11
+ </script>
12
+
13
+ <SwitchPrimitive.Root
14
+ bind:ref
15
+ data-slot="switch"
16
+ class={cn(
17
+ "peer focus-visible:border-ring focus-visible:ring-ring/50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ bind:checked
21
+ {...restProps}
22
+ >
23
+ <SwitchPrimitive.Thumb
24
+ data-slot="switch-thumb"
25
+ class={cn(
26
+ "bg-background pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
@@ -0,0 +1,19 @@
1
+ import type { RequestEvent } from "@sveltejs/kit";
2
+ import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
3
+ import { backendBaseUrl } from "./backend-proxy";
4
+
5
+ // Server-side client for load functions and hooks: points at the internal
6
+ // backend URL and forwards the incoming request's cookies.
7
+ export function serverApiClient(event: RequestEvent): ApiClient {
8
+ const cookie = event.request.headers.get("cookie") ?? "";
9
+ const fetchWithCookies: typeof fetch = (input, init) => {
10
+ const headers = new Headers(init?.headers);
11
+ if (cookie) headers.set("cookie", cookie);
12
+ return event.fetch(input, { ...init, headers });
13
+ };
14
+ return createApiClient({
15
+ baseUrl: backendBaseUrl(),
16
+ apiBasePath: "",
17
+ fetch: fetchWithCookies,
18
+ });
19
+ }
@@ -1,16 +1,55 @@
1
1
  // Server-side proxy boundary. The browser never talks to the API directly:
2
- // only these allowlisted headers are forwarded to BACKEND_INTERNAL_URL.
3
- const FORWARDED_HEADERS = ["authorization", "cookie", "content-type"];
2
+ // requests go through SvelteKit server routes, which forward an allowlist of
3
+ // headers to BACKEND_INTERNAL_URL and relay the response (including Set-Cookie).
4
+ const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept", "origin", "referer"];
5
+ const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
4
6
 
5
7
  export function backendBaseUrl(): string {
6
- return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
8
+ return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:5002";
7
9
  }
8
10
 
9
- export function backendProxyHeaders(request: Request): Headers {
11
+ // Normalize the resolved client IP for the forwarded header. better-auth stores
12
+ // IPv4 as-is but anonymizes IPv6 (masks the host bits), so an IPv6 loopback would
13
+ // be recorded as "::" — map loopback to 127.0.0.1 for a clean local value, and
14
+ // unwrap IPv4-mapped addresses.
15
+ function normalizeClientIp(address: string | undefined): string | undefined {
16
+ if (!address) return undefined;
17
+ if (address === "::1" || address === "::") return "127.0.0.1";
18
+ if (address.startsWith("::ffff:")) return address.slice("::ffff:".length);
19
+ return address;
20
+ }
21
+
22
+ export async function proxyRequest(
23
+ request: Request,
24
+ targetUrl: string,
25
+ clientAddress?: string,
26
+ ): Promise<Response> {
10
27
  const headers = new Headers();
11
28
  for (const name of FORWARDED_HEADERS) {
12
29
  const value = request.headers.get(name);
13
30
  if (value) headers.set(name, value);
14
31
  }
15
- return headers;
32
+ // Forward the resolved client IP so the API (better-auth) can record it.
33
+ const forwardedIp = normalizeClientIp(clientAddress);
34
+ if (forwardedIp) headers.set("x-forwarded-for", forwardedIp);
35
+
36
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
37
+ const upstream = await fetch(targetUrl, {
38
+ method: request.method,
39
+ headers,
40
+ body: hasBody ? await request.text() : undefined,
41
+ redirect: "manual",
42
+ });
43
+
44
+ const responseHeaders = new Headers();
45
+ for (const name of RELAYED_RESPONSE_HEADERS) {
46
+ const value = upstream.headers.get(name);
47
+ if (value) responseHeaders.set(name, value);
48
+ }
49
+ // Relay Set-Cookie so auth session cookies reach the browser.
50
+ for (const cookie of upstream.headers.getSetCookie()) {
51
+ responseHeaders.append("set-cookie", cookie);
52
+ }
53
+
54
+ return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
16
55
  }
@@ -3,6 +3,8 @@
3
3
  import { Input } from "$lib/components/ui/input";
4
4
  import { Checkbox } from "$lib/components/ui/checkbox";
5
5
  import * as Card from "$lib/components/ui/card";
6
+ import { api } from "$lib/api";
7
+ import { ApiError } from "@podosoft/podokit-api-client";
6
8
 
7
9
  type Todo = { id: string; title: string; completed: boolean };
8
10
 
@@ -10,40 +12,39 @@
10
12
  let title = $state("");
11
13
  let error = $state<string | null>(null);
12
14
 
15
+ function fail(err: unknown, action: string): void {
16
+ error = err instanceof ApiError ? `${action}: ${err.message}` : `${action} failed`;
17
+ }
18
+
13
19
  async function load(): Promise<void> {
14
- const res = await fetch("/api/todos");
15
- if (res.ok) todos = await res.json();
16
- else error = `Failed to load (HTTP ${res.status})`;
20
+ try {
21
+ todos = await api.get<Todo[]>("/todos");
22
+ error = null;
23
+ } catch (err) {
24
+ fail(err, "Load");
25
+ }
17
26
  }
18
27
 
19
28
  async function add(event: SubmitEvent): Promise<void> {
20
29
  event.preventDefault();
21
30
  const value = title.trim();
22
31
  if (!value) return;
23
- const res = await fetch("/api/todos", {
24
- method: "POST",
25
- headers: { "content-type": "application/json" },
26
- body: JSON.stringify({ title: value }),
27
- });
28
- if (res.ok) {
32
+ try {
33
+ await api.post("/todos", { title: value });
29
34
  title = "";
30
35
  await load();
31
- } else {
32
- error = `Failed to add (HTTP ${res.status})`;
36
+ } catch (err) {
37
+ fail(err, "Add");
33
38
  }
34
39
  }
35
40
 
36
41
  async function toggle(todo: Todo): Promise<void> {
37
- await fetch(`/api/todos/${todo.id}`, {
38
- method: "PATCH",
39
- headers: { "content-type": "application/json" },
40
- body: JSON.stringify({ completed: !todo.completed }),
41
- });
42
+ await api.patch(`/todos/${todo.id}`, { completed: !todo.completed });
42
43
  await load();
43
44
  }
44
45
 
45
46
  async function remove(todo: Todo): Promise<void> {
46
- await fetch(`/api/todos/${todo.id}`, { method: "DELETE" });
47
+ await api.del(`/todos/${todo.id}`);
47
48
  await load();
48
49
  }
49
50
 
@@ -61,7 +62,7 @@
61
62
  <Card.Root>
62
63
  <Card.Header>
63
64
  <Card.Title>Todos</Card.Title>
64
- <Card.Description>A NestJS + SvelteKit CRUD example.</Card.Description>
65
+ <Card.Description>A NestJS + SvelteKit CRUD example via the typed ApiClient.</Card.Description>
65
66
  </Card.Header>
66
67
  <Card.Content class="flex flex-col gap-4">
67
68
  <form class="flex gap-2" onsubmit={add}>
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/${params.path}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the better-auth handler, keeping the /api/auth prefix.
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/api/auth/${params.all}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -1,6 +1,8 @@
1
1
  # Copy to .env and adjust. Never commit real secrets.
2
+
3
+ # ── Required (runtime) ───────────────────────────────────────────────
2
4
  NODE_ENV=development
3
- PORT=3000
5
+ PORT=5002
4
6
 
5
7
  POSTGRES_HOST=localhost
6
8
  POSTGRES_PORT=5432
@@ -8,9 +10,12 @@ POSTGRES_USER=podokit
8
10
  POSTGRES_PASSWORD=podokit
9
11
  POSTGRES_DB=podokit
10
12
 
13
+ # Web -> API boundary (server-side only; not exposed to the browser)
14
+ BACKEND_INTERNAL_URL=http://localhost:5002
15
+ CORS_ORIGIN=http://localhost:5001
16
+
17
+ # ── Optional / development ───────────────────────────────────────────
18
+ # Redis — only needed by cache/queue modules. Start it with:
19
+ # docker compose --profile cache up
11
20
  REDIS_HOST=localhost
12
21
  REDIS_PORT=6379
13
-
14
- # Web -> API boundary (server-side only; not exposed to the browser)
15
- BACKEND_INTERNAL_URL=http://localhost:3000
16
- CORS_ORIGIN=http://localhost:5173
@@ -7,3 +7,8 @@ build/
7
7
  !.env.example
8
8
  .DS_Store
9
9
  *.log
10
+
11
+ # e2e test artifacts
12
+ tests/playwright/
13
+ tests/playwright-report/
14
+ tests/test-results/
@@ -1,4 +1,5 @@
1
1
  services:
2
+ # --- Runtime (always started by `docker compose up`) ---
2
3
  postgres:
3
4
  image: postgres:16-alpine
4
5
  environment:
@@ -15,8 +16,11 @@ services:
15
16
  timeout: 5s
16
17
  retries: 5
17
18
 
19
+ # --- Runtime, but only needed by cache/queue modules. Start with:
20
+ # docker compose --profile cache up
18
21
  redis:
19
22
  image: redis:7-alpine
23
+ profiles: ["cache"]
20
24
  ports:
21
25
  - "${REDIS_PORT:-6379}:6379"
22
26
  healthcheck:
@@ -2,12 +2,20 @@
2
2
  "name": "{{projectName}}",
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
- "engines": { "node": ">=20" },
6
- "workspaces": ["apps/*"],
5
+ "engines": {
6
+ "node": ">=20"
7
+ },
8
+ "workspaces": [
9
+ "apps/*",
10
+ "tests"
11
+ ],
7
12
  "scripts": {
8
13
  "dev": "npm run dev --workspaces --if-present",
9
14
  "build": "npm run build --workspaces --if-present",
10
15
  "lint": "npm run lint --workspaces --if-present",
11
- "test": "npm run test --workspaces --if-present"
16
+ "test": "npm run test --workspaces --if-present",
17
+ "test:e2e": "npm run test:e2e -w tests",
18
+ "test:e2e:ui": "npm run test:e2e:ui -w tests",
19
+ "test:e2e:api": "npm run test:e2e:api -w tests"
12
20
  }
13
21
  }
@@ -0,0 +1,15 @@
1
+ # tests
2
+
3
+ End-to-end tests for this app, run with [`@playwright/test`](https://playwright.dev):
4
+
5
+ ```bash
6
+ npm run test:e2e # all specs (api + ui)
7
+ npm run test:e2e:api # backend endpoints (request-only)
8
+ npm run test:e2e:ui # pages (chromium)
9
+ npm run test:e2e:report # open the HTML report
10
+ ```
11
+
12
+ The suite runs against a live stack (web on `E2E_BASE_URL`, default
13
+ `http://localhost:5001`, proxying `/api/*` to the API). Start PostgreSQL, run the
14
+ auth migration, and start the API + web first. Files ending in `*.api.spec.ts` run
15
+ in the request-only `api` project; `*.ui.spec.ts` run in the chromium `ui` project.
@@ -0,0 +1,14 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ // Health endpoints are public and present in every generated app.
4
+ test("GET /health is public and ok @smoke", async ({ request }) => {
5
+ const res = await request.get("/api/health");
6
+ expect(res.ok()).toBeTruthy();
7
+ expect((await res.json()).status).toBe("ok");
8
+ });
9
+
10
+ test("GET /health/ready reports the database", async ({ request }) => {
11
+ const res = await request.get("/api/health/ready");
12
+ expect(res.status()).toBe(200);
13
+ expect((await res.json()).status).toBe("ready");
14
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{projectName}}-tests",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "test:e2e": "playwright test",
7
+ "test:e2e:ui": "playwright test --project=ui",
8
+ "test:e2e:api": "playwright test --project=api",
9
+ "test:e2e:report": "playwright show-report"
10
+ },
11
+ "devDependencies": {
12
+ "@playwright/test": "^1.61.1",
13
+ "otpauth": "^9.5.1"
14
+ }
15
+ }
@@ -0,0 +1,25 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ // The suite runs against a live stack: web on E2E_BASE_URL (proxying /api/* to
4
+ // the API). Start it first (see docs/testing.md) — locally via the dev harness,
5
+ // in CI via scripts/e2e-ci.mjs. Two projects: `api` (request-only, backend e2e)
6
+ // and `ui` (chromium, pages). Files are routed by suffix (*.api/*.ui.spec.ts).
7
+ const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5001";
8
+
9
+ export default defineConfig({
10
+ testDir: ".",
11
+ fullyParallel: true,
12
+ forbidOnly: !!process.env.CI,
13
+ retries: process.env.CI ? 2 : 0,
14
+ workers: process.env.CI ? 1 : undefined,
15
+ reporter: [["list"], ["html", { open: "never" }]],
16
+ use: {
17
+ baseURL,
18
+ trace: "on-first-retry",
19
+ screenshot: "only-on-failure",
20
+ },
21
+ projects: [
22
+ { name: "api", testMatch: /.*\.api\.spec\.ts/ },
23
+ { name: "ui", testMatch: /.*\.ui\.spec\.ts/, use: { ...devices["Desktop Chrome"] } },
24
+ ],
25
+ });
@@ -0,0 +1,8 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ // Module-agnostic: the app responds at "/" (a page or a redirect to it).
4
+ test("app is reachable @smoke", async ({ page }) => {
5
+ const res = await page.goto("/");
6
+ expect(res?.status()).toBeLessThan(400);
7
+ await expect(page.locator("body")).toBeVisible();
8
+ });
@@ -0,0 +1,49 @@
1
+ import { type Tier } from "./lockfile";
2
+ /**
3
+ * `podo update` planner. For now it produces a dry-run plan only: it assembles
4
+ * the new-version tree from the installed CLI's templates and compares it to the
5
+ * working copy, classifying each file. Applying the plan (writing files, 3-way
6
+ * merging) is a later step (see ADR-0009).
7
+ */
8
+ export type Action = "update" | "merge" | "conflict" | "add" | "remove" | "skip" | "up-to-date";
9
+ export interface FileChange {
10
+ path: string;
11
+ tier: Tier;
12
+ action: Action;
13
+ /** Why the action was chosen (for the report). */
14
+ note: string;
15
+ }
16
+ export interface UpdatePlan {
17
+ fromVersion: string;
18
+ toVersion: string;
19
+ template: string;
20
+ modules: string[];
21
+ changes: FileChange[];
22
+ }
23
+ /**
24
+ * Build the update plan. `templatesDir` is the installed CLI's template set (the
25
+ * new version); the lock records what PodoKit last wrote (to detect user edits).
26
+ */
27
+ export declare function planUpdate(projectRoot: string, templatesDir: string): UpdatePlan;
28
+ export interface ApplyOptions {
29
+ /** Templates for the version the project is currently on, for 3-way merges. */
30
+ oldTemplatesDir?: string;
31
+ }
32
+ export interface ApplyResult {
33
+ written: string[];
34
+ removed: string[];
35
+ /** Files 3-way merged cleanly. */
36
+ merged: string[];
37
+ /** Files written with conflict markers for manual resolution. */
38
+ conflicts: string[];
39
+ }
40
+ /**
41
+ * Apply an update to the working copy. Clean updates and additions are written,
42
+ * upstream removals deleted, and user-edited files 3-way merged against the old
43
+ * version (when `oldTemplatesDir` is given) or written with git-style conflict
44
+ * markers otherwise. Owned files are never touched. Refreshes the lockfile and
45
+ * stamps the new version.
46
+ */
47
+ export declare function applyUpdate(projectRoot: string, templatesDir: string, options?: ApplyOptions): ApplyResult;
48
+ /** Count actions for a one-line summary. */
49
+ export declare function summarize(plan: UpdatePlan): Record<Action, number>;
package/dist/update.js ADDED
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planUpdate = planUpdate;
4
+ exports.applyUpdate = applyUpdate;
5
+ exports.summarize = summarize;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
9
+ const assemble_1 = require("./assemble");
10
+ const lockfile_1 = require("./lockfile");
11
+ const inspect_1 = require("./inspect");
12
+ function treeText(tree, path) {
13
+ const file = tree.get(path);
14
+ if (!file)
15
+ return null;
16
+ return typeof file.content === "string" ? file.content : file.content.toString("utf8");
17
+ }
18
+ function diskContent(projectRoot, path) {
19
+ const abs = (0, node_path_1.join)(projectRoot, path);
20
+ return (0, node_fs_1.existsSync)(abs) ? (0, node_fs_1.readFileSync)(abs) : null;
21
+ }
22
+ /**
23
+ * Build the update plan. `templatesDir` is the installed CLI's template set (the
24
+ * new version); the lock records what PodoKit last wrote (to detect user edits).
25
+ */
26
+ function planUpdate(projectRoot, templatesDir) {
27
+ const manifest = (0, lockfile_1.readManifest)(projectRoot);
28
+ const lock = (0, lockfile_1.readFilesLock)(projectRoot);
29
+ if (!manifest || !lock)
30
+ throw new inspect_1.NotAProjectError();
31
+ const newTree = (0, assemble_1.assembleProject)({
32
+ templatesDir,
33
+ template: manifest.template,
34
+ answers: manifest.answers,
35
+ modules: manifest.modules.map((m) => m.name),
36
+ });
37
+ const changes = [];
38
+ const paths = new Set([...newTree.keys(), ...Object.keys(lock.files)]);
39
+ for (const path of [...paths].sort()) {
40
+ const locked = lock.files[path];
41
+ const newText = treeText(newTree, path);
42
+ const disk = diskContent(projectRoot, path);
43
+ // tier: prefer the lock; else classify the new file.
44
+ const tier = locked?.tier ??
45
+ (newText !== null ? (0, lockfile_1.classifyTier)(path, newText, manifest.ownedGlobs) : "managed");
46
+ if (tier === "owned") {
47
+ changes.push({ path, tier, action: "skip", note: "owned — never modified" });
48
+ continue;
49
+ }
50
+ // File removed upstream.
51
+ if (newText === null) {
52
+ if (disk === null)
53
+ continue; // already gone
54
+ const edited = locked && (0, podokit_template_engine_1.hashContent)(disk) !== locked.outHash;
55
+ changes.push({
56
+ path,
57
+ tier,
58
+ action: edited ? "conflict" : "remove",
59
+ note: edited ? "removed upstream but edited locally" : "removed upstream",
60
+ });
61
+ continue;
62
+ }
63
+ const newHash = (0, podokit_template_engine_1.hashContent)(newText);
64
+ // New file added upstream.
65
+ if (disk === null) {
66
+ changes.push({ path, tier, action: "add", note: "new in this version" });
67
+ continue;
68
+ }
69
+ const diskHash = (0, podokit_template_engine_1.hashContent)(disk);
70
+ if (diskHash === newHash) {
71
+ changes.push({ path, tier, action: "up-to-date", note: "already current" });
72
+ continue;
73
+ }
74
+ const edited = !locked || diskHash !== locked.outHash;
75
+ changes.push({
76
+ path,
77
+ tier,
78
+ action: edited ? "conflict" : "update",
79
+ note: edited ? "you edited this; update would need a merge" : "clean update",
80
+ });
81
+ }
82
+ return {
83
+ fromVersion: manifest.podokitVersion,
84
+ toVersion: (0, lockfile_1.podokitVersion)(),
85
+ template: manifest.template,
86
+ modules: manifest.modules.map((m) => m.name),
87
+ changes,
88
+ };
89
+ }
90
+ function writeFile(projectRoot, path, content) {
91
+ const abs = (0, node_path_1.join)(projectRoot, path);
92
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs), { recursive: true });
93
+ (0, node_fs_1.writeFileSync)(abs, content);
94
+ }
95
+ /**
96
+ * Apply an update to the working copy. Clean updates and additions are written,
97
+ * upstream removals deleted, and user-edited files 3-way merged against the old
98
+ * version (when `oldTemplatesDir` is given) or written with git-style conflict
99
+ * markers otherwise. Owned files are never touched. Refreshes the lockfile and
100
+ * stamps the new version.
101
+ */
102
+ function applyUpdate(projectRoot, templatesDir, options = {}) {
103
+ const manifest = (0, lockfile_1.readManifest)(projectRoot);
104
+ if (!manifest)
105
+ throw new inspect_1.NotAProjectError();
106
+ const plan = planUpdate(projectRoot, templatesDir);
107
+ const modules = manifest.modules.map((m) => m.name);
108
+ const newTree = (0, assemble_1.assembleProject)({ templatesDir, template: manifest.template, answers: manifest.answers, modules });
109
+ const oldTree = options.oldTemplatesDir
110
+ ? (0, assemble_1.assembleProject)({ templatesDir: options.oldTemplatesDir, template: manifest.template, answers: manifest.answers, modules })
111
+ : null;
112
+ const result = { written: [], removed: [], merged: [], conflicts: [] };
113
+ for (const change of plan.changes) {
114
+ const newText = treeText(newTree, change.path);
115
+ if (change.action === "update" || change.action === "add") {
116
+ writeFile(projectRoot, change.path, newText ?? "");
117
+ result.written.push(change.path);
118
+ }
119
+ else if (change.action === "remove") {
120
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(projectRoot, change.path), { force: true });
121
+ result.removed.push(change.path);
122
+ }
123
+ else if (change.action === "conflict" && newText !== null) {
124
+ const disk = (0, node_fs_1.readFileSync)((0, node_path_1.join)(projectRoot, change.path), "utf8");
125
+ const base = oldTree ? treeText(oldTree, change.path) : null;
126
+ if (base === null) {
127
+ // No old version to merge against — never clobber the user's edits.
128
+ result.conflicts.push(change.path);
129
+ continue;
130
+ }
131
+ const merge = (0, podokit_template_engine_1.threeWayMerge)(base, disk, newText, { current: "current", next: "podokit" });
132
+ writeFile(projectRoot, change.path, merge.merged);
133
+ if (merge.conflicts > 0)
134
+ result.conflicts.push(change.path);
135
+ else
136
+ result.merged.push(change.path);
137
+ }
138
+ }
139
+ // Refresh the lock to match what we just wrote and stamp the new version.
140
+ (0, lockfile_1.writeFilesLock)(projectRoot, (0, lockfile_1.computeFilesLock)(projectRoot, manifest.ownedGlobs));
141
+ (0, lockfile_1.writeManifest)(projectRoot, { ...manifest, podokitVersion: (0, lockfile_1.podokitVersion)() });
142
+ return result;
143
+ }
144
+ /** Count actions for a one-line summary. */
145
+ function summarize(plan) {
146
+ const counts = {
147
+ update: 0,
148
+ merge: 0,
149
+ conflict: 0,
150
+ add: 0,
151
+ remove: 0,
152
+ skip: 0,
153
+ "up-to-date": 0,
154
+ };
155
+ for (const change of plan.changes)
156
+ counts[change.action] += 1;
157
+ return counts;
158
+ }