ui-syncup 0.3.13 → 0.4.0-beta.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 (1047) hide show
  1. package/.agents/skills/ai-spec-workflow/SKILL.md +58 -0
  2. package/.agents/skills/ai-spec-workflow/references/AI_SPECIFICATION_WORKFLOW.md +1434 -0
  3. package/.agents/skills/ai-spec-workflow/references/templates/design-template.md +729 -0
  4. package/.agents/skills/ai-spec-workflow/references/templates/requirements-template.md +179 -0
  5. package/.agents/skills/ai-spec-workflow/references/templates/tasks-template.md +501 -0
  6. package/.agents/skills/animation-designer/SKILL.md +688 -0
  7. package/.agents/skills/animation-designer/manifest.yaml +44 -0
  8. package/.agents/skills/brainstorming/SKILL.md +54 -0
  9. package/.agents/skills/contract-driven-ui/SKILL.md +270 -0
  10. package/.agents/skills/dispatching-parallel-agents/SKILL.md +180 -0
  11. package/.agents/skills/executing-plans/SKILL.md +76 -0
  12. package/.agents/skills/executing-specs/SKILL.md +53 -0
  13. package/.agents/skills/finishing-a-development-branch/SKILL.md +200 -0
  14. package/.agents/skills/github-workflow-automation/SKILL.md +846 -0
  15. package/.agents/skills/react-best-practices/AGENTS.md +2249 -0
  16. package/.agents/skills/react-best-practices/README.md +123 -0
  17. package/.agents/skills/react-best-practices/SKILL.md +121 -0
  18. package/.agents/skills/react-best-practices/metadata.json +15 -0
  19. package/.agents/skills/react-best-practices/rules/_sections.md +46 -0
  20. package/.agents/skills/react-best-practices/rules/_template.md +28 -0
  21. package/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  22. package/.agents/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  23. package/.agents/skills/react-best-practices/rules/async-api-routes.md +38 -0
  24. package/.agents/skills/react-best-practices/rules/async-defer-await.md +80 -0
  25. package/.agents/skills/react-best-practices/rules/async-dependencies.md +36 -0
  26. package/.agents/skills/react-best-practices/rules/async-parallel.md +28 -0
  27. package/.agents/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  28. package/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  29. package/.agents/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  30. package/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  31. package/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  32. package/.agents/skills/react-best-practices/rules/bundle-preload.md +50 -0
  33. package/.agents/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  34. package/.agents/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  35. package/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  36. package/.agents/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  37. package/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  38. package/.agents/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  39. package/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  40. package/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
  41. package/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  42. package/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
  43. package/.agents/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  44. package/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  45. package/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  46. package/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  47. package/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
  48. package/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  49. package/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  50. package/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  51. package/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  52. package/.agents/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  53. package/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  54. package/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  55. package/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  56. package/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  57. package/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  58. package/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  59. package/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
  60. package/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  61. package/.agents/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  62. package/.agents/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  63. package/.agents/skills/react-best-practices/rules/server-cache-react.md +26 -0
  64. package/.agents/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  65. package/.agents/skills/react-best-practices/rules/server-serialization.md +38 -0
  66. package/.agents/skills/react-ui-patterns/SKILL.md +289 -0
  67. package/.agents/skills/receiving-code-review/SKILL.md +213 -0
  68. package/.agents/skills/requesting-code-review/SKILL.md +105 -0
  69. package/.agents/skills/requesting-code-review/code-reviewer.md +146 -0
  70. package/.agents/skills/reviewing-code/SKILL.md +28 -0
  71. package/.agents/skills/shadcn/SKILL.md +240 -0
  72. package/.agents/skills/shadcn/agents/openai.yml +5 -0
  73. package/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  74. package/.agents/skills/shadcn/assets/shadcn.png +0 -0
  75. package/.agents/skills/shadcn/cli.md +255 -0
  76. package/.agents/skills/shadcn/customization.md +202 -0
  77. package/.agents/skills/shadcn/evals/evals.json +47 -0
  78. package/.agents/skills/shadcn/mcp.md +94 -0
  79. package/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  80. package/.agents/skills/shadcn/rules/composition.md +195 -0
  81. package/.agents/skills/shadcn/rules/forms.md +192 -0
  82. package/.agents/skills/shadcn/rules/icons.md +101 -0
  83. package/.agents/skills/shadcn/rules/styling.md +162 -0
  84. package/.agents/skills/steering-creation/SKILL.md +221 -0
  85. package/.agents/skills/steering-creation/references/STEERING_CREATION_INSTRUCTION.md +850 -0
  86. package/.agents/skills/subagent-driven-development/SKILL.md +240 -0
  87. package/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  88. package/.agents/skills/subagent-driven-development/implementer-prompt.md +78 -0
  89. package/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  90. package/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +296 -0
  92. package/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  93. package/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
  94. package/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
  95. package/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
  96. package/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
  97. package/.agents/skills/systematic-debugging/test-academic.md +14 -0
  98. package/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
  99. package/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
  100. package/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
  101. package/.agents/skills/test-driven-development/SKILL.md +371 -0
  102. package/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
  103. package/.agents/skills/using-git-worktrees/SKILL.md +217 -0
  104. package/.agents/skills/using-superpowers/SKILL.md +87 -0
  105. package/.agents/skills/verification-before-completion/SKILL.md +139 -0
  106. package/.agents/skills/web-design-guidelines/SKILL.md +36 -0
  107. package/.agents/skills/writing-plans/SKILL.md +116 -0
  108. package/.agents/skills/writing-skills/SKILL.md +655 -0
  109. package/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
  110. package/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  111. package/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
  112. package/.agents/skills/writing-skills/persuasion-principles.md +187 -0
  113. package/.agents/skills/writing-skills/render-graphs.js +168 -0
  114. package/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  115. package/.ai/steering/product.md +51 -0
  116. package/.ai/steering/structure.md +275 -0
  117. package/.ai/steering/tech.md +188 -0
  118. package/.claude/agents/database-architect.md +96 -0
  119. package/.claude/agents/deployment-pipeline-architect.md +122 -0
  120. package/.claude/agents/nextjs-expert.md +69 -0
  121. package/.claude/agents/ui-design-expert.md +106 -0
  122. package/.claudeignore +69 -0
  123. package/.dockerignore +8 -0
  124. package/.env.development +86 -0
  125. package/.env.example +171 -0
  126. package/.env.production +139 -0
  127. package/.env.test +58 -0
  128. package/.gitattributes +2 -0
  129. package/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
  130. package/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
  131. package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
  132. package/.github/workflows/ci.yml +64 -0
  133. package/.github/workflows/release.yml +131 -0
  134. package/.nvmrc +1 -0
  135. package/.releaserc.json +18 -0
  136. package/.vercelignore +73 -0
  137. package/AGENTS.md +544 -0
  138. package/CHANGELOG.md +37 -0
  139. package/CODE_OF_CONDUCT.md +21 -0
  140. package/CONTRIBUTING.md +32 -0
  141. package/Dockerfile +84 -0
  142. package/LICENSE +21 -0
  143. package/README.md +328 -59
  144. package/SECURITY.md +16 -0
  145. package/bun.lock +3853 -0
  146. package/cli/README.md +94 -0
  147. package/cli/bun.lock +306 -0
  148. package/cli/index.ts +96 -0
  149. package/cli/package-lock.json +2157 -0
  150. package/cli/package.json +30 -0
  151. package/cli/src/commands/backup.ts +78 -0
  152. package/cli/src/commands/doctor.ts +82 -0
  153. package/cli/src/commands/init.ts +234 -0
  154. package/cli/src/commands/logs.ts +26 -0
  155. package/cli/src/commands/open.ts +23 -0
  156. package/cli/src/commands/remove.ts +44 -0
  157. package/cli/src/commands/restart.ts +21 -0
  158. package/cli/src/commands/restore.ts +90 -0
  159. package/cli/src/commands/start.ts +26 -0
  160. package/cli/src/commands/status.ts +25 -0
  161. package/cli/src/commands/stop.ts +20 -0
  162. package/cli/src/commands/upgrade.ts +28 -0
  163. package/cli/src/lib/docker.ts +40 -0
  164. package/cli/src/lib/env.ts +42 -0
  165. package/cli/src/lib/ui.ts +43 -0
  166. package/cli/tsconfig.json +13 -0
  167. package/cli/tsup.config.ts +12 -0
  168. package/components.json +24 -0
  169. package/docker/README.md +430 -0
  170. package/docker/compose.dev-minio.yml +30 -0
  171. package/docker/compose.dev.yml +39 -0
  172. package/docker/compose.local.yml +84 -0
  173. package/docker/compose.yml +153 -0
  174. package/docs/VERSIONING.md +117 -0
  175. package/docs/database/DRIZZLE_COMMANDS_EXPLAINED.md +1779 -0
  176. package/docs/database/DRIZZLE_ZOD_POSTGRESQL_INSTRUCTION.md +646 -0
  177. package/docs/database/MIGRATION_BEST_PRACTICES.md +601 -0
  178. package/docs/database/MIGRATION_ROLLBACK.md +1080 -0
  179. package/docs/database/MIGRATION_SYSTEM.md +165 -0
  180. package/docs/database/MIGRATION_TROUBLESHOOTING.md +881 -0
  181. package/docs/development/ENVIRONMENT_CONFIG.md +896 -0
  182. package/docs/development/LOCAL_DEVELOPMENT.md +456 -0
  183. package/docs/development/REMOTE_DATABASE_SETUP.md +786 -0
  184. package/docs/development/STORAGE_SETUP.md +207 -0
  185. package/docs/development/SUPABASE_LOCAL_SETUP.md +178 -0
  186. package/docs/development/TESTING.md +714 -0
  187. package/docs/feature-architectures/LOADING_ARCHITECTURE.md +343 -0
  188. package/docs/feature-architectures/NOTIFICATION_ARCHITECTURE.md +858 -0
  189. package/docs/feature-architectures/RATE_LIMIT_RESET.md +147 -0
  190. package/docs/feature-architectures/RBAC.md +1132 -0
  191. package/docs/feature-architectures/RESOURCE_LIMITS.md +69 -0
  192. package/docs/feature-architectures/SECURITY.md +284 -0
  193. package/docs/feature-architectures/WORKSPACES.md +278 -0
  194. package/docs/plans/admin-setup-wizard-routing-plan.md +623 -0
  195. package/drizzle/0000_purple_wilson_fisk.sql +360 -0
  196. package/drizzle/0001_drop_instance_public_url.sql +1 -0
  197. package/drizzle/meta/0000_snapshot.json +3118 -0
  198. package/drizzle/meta/_journal.json +20 -0
  199. package/drizzle.config.ts +13 -0
  200. package/eslint.config.mjs +44 -0
  201. package/install.sh +180 -0
  202. package/next.config.ts +91 -0
  203. package/package.json +128 -22
  204. package/playwright.config.ts +70 -0
  205. package/postcss.config.mjs +7 -0
  206. package/public/file.svg +1 -0
  207. package/public/globe.svg +1 -0
  208. package/public/logo.svg +11 -0
  209. package/public/next.svg +1 -0
  210. package/public/playground/CPM-101/as-is-image.jpg +0 -0
  211. package/public/playground/CPM-101/to-be-image.jpg +0 -0
  212. package/public/playground/TEST-1/LinkedIn-skeleton-screen.png +0 -0
  213. package/public/playground/TEST-1/https___dev-to-uploads.s3.amazonaws.com_uploads_articles_vuahe90ka1mkx9aepmea.webp +0 -0
  214. package/public/playground/TEST-1/linkedin_skeletonscreen.jpg +0 -0
  215. package/public/vercel.svg +1 -0
  216. package/public/window.svg +1 -0
  217. package/scripts/__tests__/migrate.integration.test.ts +642 -0
  218. package/scripts/__tests__/migrate.property.test.ts +1714 -0
  219. package/scripts/__tests__/migrate.test.ts +536 -0
  220. package/scripts/admin-reset-password.ts +114 -0
  221. package/scripts/check-email-queue.ts +99 -0
  222. package/scripts/check-sessions.ts +50 -0
  223. package/scripts/db-pull-data.sh +73 -0
  224. package/scripts/force-verify-email.sh +13 -0
  225. package/scripts/migrate.ts +693 -0
  226. package/scripts/process-email-queue.ts +26 -0
  227. package/scripts/reset-db.ts +47 -0
  228. package/scripts/reset-rate-limit.sh +26 -0
  229. package/scripts/reset-remote-db.sql +31 -0
  230. package/scripts/retry-failed-emails.ts +67 -0
  231. package/scripts/seed.ts +605 -0
  232. package/scripts/setup-monitoring.sh +440 -0
  233. package/scripts/sync-migration-tracking.ts +113 -0
  234. package/scripts/test-ci-error-handling.sh +237 -0
  235. package/scripts/test-ci-workflow.sh +200 -0
  236. package/scripts/test-migration.sh +151 -0
  237. package/scripts/validate-env.ts +25 -0
  238. package/scripts/validate-migration-system.ts +566 -0
  239. package/scripts/verify-ci-status-reporting.sh +206 -0
  240. package/scripts/verify-user-email.sql +22 -0
  241. package/scripts/verify-vercel-integration.ts +292 -0
  242. package/seed_data.md +54 -0
  243. package/src/app/(protected)/(team)/(routes)/[projectSlug]/error.tsx +89 -0
  244. package/src/app/(protected)/(team)/(routes)/[projectSlug]/loading.tsx +101 -0
  245. package/src/app/(protected)/(team)/(routes)/[projectSlug]/page.tsx +91 -0
  246. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/README.md +192 -0
  247. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/error.tsx +58 -0
  248. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/loading.tsx +14 -0
  249. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/not-found.tsx +47 -0
  250. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/page.tsx +91 -0
  251. package/src/app/(protected)/(team)/projects/page.tsx +16 -0
  252. package/src/app/(protected)/(team)/team/settings/(section)/instance/page.tsx +52 -0
  253. package/src/app/(protected)/(team)/team/settings/(section)/integrations/loading.tsx +5 -0
  254. package/src/app/(protected)/(team)/team/settings/(section)/integrations/page.tsx +23 -0
  255. package/src/app/(protected)/(team)/team/settings/(section)/members/loading.tsx +5 -0
  256. package/src/app/(protected)/(team)/team/settings/(section)/members/page.tsx +35 -0
  257. package/src/app/(protected)/(team)/team/settings/layout.tsx +72 -0
  258. package/src/app/(protected)/(team)/team/settings/loading.tsx +5 -0
  259. package/src/app/(protected)/(team)/team/settings/page.tsx +71 -0
  260. package/src/app/(protected)/dev/auth/README.md +151 -0
  261. package/src/app/(protected)/dev/auth/page.tsx +590 -0
  262. package/src/app/(protected)/layout.test.tsx +209 -0
  263. package/src/app/(protected)/layout.tsx +28 -0
  264. package/src/app/(protected)/onboarding/page.tsx +27 -0
  265. package/src/app/(protected)/settings/integrations/page.tsx +23 -0
  266. package/src/app/(protected)/settings/layout.tsx +26 -0
  267. package/src/app/(protected)/settings/notifications/page.tsx +26 -0
  268. package/src/app/(protected)/settings/other/page.tsx +23 -0
  269. package/src/app/(protected)/settings/page.tsx +23 -0
  270. package/src/app/(protected)/settings/preferences/page.tsx +23 -0
  271. package/src/app/(protected)/settings/security/page.tsx +37 -0
  272. package/src/app/(public)/forgot-password/page.tsx +20 -0
  273. package/src/app/(public)/invite/project/[token]/error.tsx +50 -0
  274. package/src/app/(public)/invite/project/[token]/loading.tsx +39 -0
  275. package/src/app/(public)/invite/project/[token]/page.tsx +156 -0
  276. package/src/app/(public)/layout.tsx +9 -0
  277. package/src/app/(public)/privacy-policy/page.tsx +12 -0
  278. package/src/app/(public)/reset-password/page.tsx +37 -0
  279. package/src/app/(public)/setup/__tests__/page.test.tsx +30 -0
  280. package/src/app/(public)/setup/page.tsx +17 -0
  281. package/src/app/(public)/share/issue/[token]/page.tsx +51 -0
  282. package/src/app/(public)/sign-in/page.tsx +55 -0
  283. package/src/app/(public)/sign-up/page.tsx +23 -0
  284. package/src/app/(public)/verify-email/page.tsx +22 -0
  285. package/src/app/(public)/verify-email-confirm/page.tsx +40 -0
  286. package/src/app/api/auth/[...all]/route.ts +6 -0
  287. package/src/app/api/auth/delete-account/route.ts +134 -0
  288. package/src/app/api/auth/dev/force-verify/route.ts +180 -0
  289. package/src/app/api/auth/dev/reset-rate-limit/route.ts +144 -0
  290. package/src/app/api/auth/dev/sessions/route.ts +172 -0
  291. package/src/app/api/auth/forgot-password/__tests__/forgot-password.property.test.ts +397 -0
  292. package/src/app/api/auth/forgot-password/route.ts +277 -0
  293. package/src/app/api/auth/logout/route.ts +115 -0
  294. package/src/app/api/auth/me/route.ts +123 -0
  295. package/src/app/api/auth/providers/__tests__/route.test.ts +236 -0
  296. package/src/app/api/auth/providers/route.ts +119 -0
  297. package/src/app/api/auth/resend-verification/route.ts +262 -0
  298. package/src/app/api/auth/reset-password/__tests__/reset-password.property.test.ts +493 -0
  299. package/src/app/api/auth/reset-password/__tests__/route.test.ts +284 -0
  300. package/src/app/api/auth/reset-password/route.ts +251 -0
  301. package/src/app/api/auth/verify-email/route.ts +232 -0
  302. package/src/app/api/example-cors/route.ts +61 -0
  303. package/src/app/api/health/route.ts +14 -0
  304. package/src/app/api/invite/project/[token]/__tests__/accept-invitation.integration.test.ts +348 -0
  305. package/src/app/api/invite/project/[token]/decline/route.ts +99 -0
  306. package/src/app/api/invite/project/[token]/route.ts +269 -0
  307. package/src/app/api/issues/[issueId]/activities/route.ts +213 -0
  308. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/[commentId]/route.ts +486 -0
  309. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/route.ts +283 -0
  310. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/read/route.ts +242 -0
  311. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/route.ts +534 -0
  312. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/route.ts +514 -0
  313. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/route.ts +161 -0
  314. package/src/app/api/issues/[issueId]/attachments/route.ts +376 -0
  315. package/src/app/api/issues/[issueId]/route.ts +516 -0
  316. package/src/app/api/notifications/[id]/read/route.ts +131 -0
  317. package/src/app/api/notifications/__tests__/notifications.integration.test.ts +350 -0
  318. package/src/app/api/notifications/read-all/route.ts +72 -0
  319. package/src/app/api/notifications/route.ts +148 -0
  320. package/src/app/api/notifications/unread-count/route.ts +77 -0
  321. package/src/app/api/projects/[id]/activities/route.ts +174 -0
  322. package/src/app/api/projects/[id]/invitations/[invitationId]/resend/route.ts +99 -0
  323. package/src/app/api/projects/[id]/invitations/[invitationId]/route.ts +96 -0
  324. package/src/app/api/projects/[id]/invitations/route.ts +254 -0
  325. package/src/app/api/projects/[id]/issues/route.ts +452 -0
  326. package/src/app/api/projects/[id]/join/route.ts +207 -0
  327. package/src/app/api/projects/[id]/members/[memberId]/route.ts +364 -0
  328. package/src/app/api/projects/[id]/members/me/route.ts +121 -0
  329. package/src/app/api/projects/[id]/members/route.ts +129 -0
  330. package/src/app/api/projects/[id]/route.ts +476 -0
  331. package/src/app/api/projects/route.ts +394 -0
  332. package/src/app/api/setup/admin/route.ts +255 -0
  333. package/src/app/api/setup/complete/__tests__/route.test.ts +60 -0
  334. package/src/app/api/setup/complete/route.ts +244 -0
  335. package/src/app/api/setup/config/route.ts +195 -0
  336. package/src/app/api/setup/export/route.ts +111 -0
  337. package/src/app/api/setup/health/route.ts +74 -0
  338. package/src/app/api/setup/import/route.ts +154 -0
  339. package/src/app/api/setup/status/route.ts +82 -0
  340. package/src/app/api/setup/workspace/route.ts +252 -0
  341. package/src/app/api/teams/[teamId]/export/route.ts +115 -0
  342. package/src/app/api/teams/[teamId]/invitations/[invitationId]/resend/route.ts +132 -0
  343. package/src/app/api/teams/[teamId]/invitations/[invitationId]/route.ts +117 -0
  344. package/src/app/api/teams/[teamId]/invitations/route.ts +363 -0
  345. package/src/app/api/teams/[teamId]/members/[userId]/route.ts +335 -0
  346. package/src/app/api/teams/[teamId]/members/route.ts +184 -0
  347. package/src/app/api/teams/[teamId]/members/search/route.ts +202 -0
  348. package/src/app/api/teams/[teamId]/route.ts +423 -0
  349. package/src/app/api/teams/[teamId]/switch/route.ts +140 -0
  350. package/src/app/api/teams/[teamId]/transfer-ownership/route.ts +212 -0
  351. package/src/app/api/teams/invitations/[token]/accept/route.ts +140 -0
  352. package/src/app/api/teams/invitations/by-id/[id]/accept/route.ts +98 -0
  353. package/src/app/api/teams/invitations/by-id/[id]/decline/route.ts +90 -0
  354. package/src/app/api/teams/route.ts +278 -0
  355. package/src/app/api/uploads/media/route.ts +118 -0
  356. package/src/app/api/uploads/presigned/route.ts +49 -0
  357. package/src/app/api/user/linked-accounts/route.ts +35 -0
  358. package/src/app/email-preview/page.tsx +11 -0
  359. package/src/app/favicon.ico +0 -0
  360. package/src/app/global-error.tsx +21 -0
  361. package/src/app/layout.tsx +50 -0
  362. package/src/app/page.tsx +5 -0
  363. package/src/components/icons/atlassian-icon.tsx +22 -0
  364. package/src/components/icons/index.ts +1 -0
  365. package/src/components/layout/SIDEBAR_LAYOUT_BEST_PRACTICES.md +240 -0
  366. package/src/components/layout/app-shell-header-store.tsx +20 -0
  367. package/src/components/layout/app-shell-skeleton.tsx +89 -0
  368. package/src/components/layout/app-shell-wrapper.tsx +32 -0
  369. package/src/components/layout/app-shell.test.tsx +155 -0
  370. package/src/components/layout/app-shell.tsx +100 -0
  371. package/src/components/shared/headers/app-header-configurator.tsx +42 -0
  372. package/src/components/shared/headers/app-header.tsx +103 -0
  373. package/src/components/shared/headers/header-user-menu.tsx +247 -0
  374. package/src/components/shared/headers/index.ts +44 -0
  375. package/src/components/shared/headers/page-header.tsx +25 -0
  376. package/src/components/shared/notifications/__tests__/notification-bell.test.tsx +159 -0
  377. package/src/components/shared/notifications/__tests__/notification-dropdown.test.tsx +296 -0
  378. package/src/components/shared/notifications/__tests__/notification-item.test.tsx +328 -0
  379. package/src/components/shared/notifications/index.ts +45 -0
  380. package/src/components/shared/notifications/notification-actions.tsx +295 -0
  381. package/src/components/shared/notifications/notification-bell-button.tsx +77 -0
  382. package/src/components/shared/notifications/notification-dropdown.tsx +160 -0
  383. package/src/components/shared/notifications/notification-group-item.tsx +268 -0
  384. package/src/components/shared/notifications/notification-item.tsx +193 -0
  385. package/src/components/shared/notifications/notification-load-more.tsx +50 -0
  386. package/src/components/shared/notifications/notification-panel.tsx +49 -0
  387. package/src/components/shared/notifications/utils.tsx +127 -0
  388. package/src/components/shared/permission-guard/index.ts +1 -0
  389. package/src/components/shared/permission-guard/permission-tooltip.tsx +45 -0
  390. package/src/components/shared/relative-time.tsx +53 -0
  391. package/src/components/shared/section-container.tsx +32 -0
  392. package/src/components/shared/service-status-banner.tsx +121 -0
  393. package/src/components/shared/settings-sidebar/index.ts +2 -0
  394. package/src/components/shared/settings-sidebar/team-setting-aside.tsx +97 -0
  395. package/src/components/shared/settings-sidebar/user-settings-aside.tsx +66 -0
  396. package/src/components/shared/sidebar/app-sidebar.tsx +146 -0
  397. package/src/components/shared/sidebar/index.ts +36 -0
  398. package/src/components/shared/sidebar/sidebar-main.tsx +81 -0
  399. package/src/components/shared/sidebar/sidebar-project.tsx +61 -0
  400. package/src/components/shared/sidebar/sidebar-team-avatar.tsx +126 -0
  401. package/src/components/shared/sidebar/sidebar-team-switcher.tsx +185 -0
  402. package/src/components/shared/sidebar/type.ts +97 -0
  403. package/src/components/ui/alert-dialog.tsx +157 -0
  404. package/src/components/ui/alert.tsx +66 -0
  405. package/src/components/ui/avatar-upload.tsx +147 -0
  406. package/src/components/ui/avatar.tsx +53 -0
  407. package/src/components/ui/badge.tsx +46 -0
  408. package/src/components/ui/breadcrumb.tsx +109 -0
  409. package/src/components/ui/button.tsx +60 -0
  410. package/src/components/ui/card.tsx +92 -0
  411. package/src/components/ui/checkbox.tsx +32 -0
  412. package/src/components/ui/collapsible.tsx +33 -0
  413. package/src/components/ui/command.tsx +184 -0
  414. package/src/components/ui/dialog.tsx +143 -0
  415. package/src/components/ui/dropdown-menu.tsx +257 -0
  416. package/src/components/ui/empty.tsx +104 -0
  417. package/src/components/ui/field.tsx +244 -0
  418. package/src/components/ui/image-cropper-dialog.tsx +167 -0
  419. package/src/components/ui/input.tsx +21 -0
  420. package/src/components/ui/label.tsx +24 -0
  421. package/src/components/ui/optimized-image.tsx +220 -0
  422. package/src/components/ui/pagination.tsx +127 -0
  423. package/src/components/ui/popover.tsx +48 -0
  424. package/src/components/ui/progress.tsx +31 -0
  425. package/src/components/ui/radio-group.tsx +45 -0
  426. package/src/components/ui/scroll-area.tsx +58 -0
  427. package/src/components/ui/select.tsx +187 -0
  428. package/src/components/ui/separator.tsx +28 -0
  429. package/src/components/ui/sheet.tsx +139 -0
  430. package/src/components/ui/sidebar.tsx +733 -0
  431. package/src/components/ui/skeleton.tsx +13 -0
  432. package/src/components/ui/sonner.tsx +40 -0
  433. package/src/components/ui/spinner.tsx +16 -0
  434. package/src/components/ui/switch.tsx +31 -0
  435. package/src/components/ui/table.tsx +116 -0
  436. package/src/components/ui/tabs.tsx +66 -0
  437. package/src/components/ui/textarea.tsx +23 -0
  438. package/src/components/ui/tooltip.tsx +61 -0
  439. package/src/config/__tests__/workspace.property.test.ts +40 -0
  440. package/src/config/auth.ts +62 -0
  441. package/src/config/integrations.ts +126 -0
  442. package/src/config/quotas.ts +20 -0
  443. package/src/config/roles.ts +463 -0
  444. package/src/config/settings-nav.ts +39 -0
  445. package/src/config/team-settings-nav.ts +37 -0
  446. package/src/config/user-settings-nav.ts +42 -0
  447. package/src/config/version.ts +1 -0
  448. package/src/config/workspace.ts +64 -0
  449. package/src/features/annotations/README.md +283 -0
  450. package/src/features/annotations/api/annotations-api.ts +194 -0
  451. package/src/features/annotations/api/comments-api.ts +147 -0
  452. package/src/features/annotations/api/index.ts +71 -0
  453. package/src/features/annotations/api/save-annotation.ts +150 -0
  454. package/src/features/annotations/api/schemas.ts +142 -0
  455. package/src/features/annotations/components/annotated-attachment-view.tsx +576 -0
  456. package/src/features/annotations/components/annotation-action-sheet.tsx +140 -0
  457. package/src/features/annotations/components/annotation-annotations-panel.tsx +213 -0
  458. package/src/features/annotations/components/annotation-box.tsx +539 -0
  459. package/src/features/annotations/components/annotation-canvas.tsx +534 -0
  460. package/src/features/annotations/components/annotation-comment-input.tsx +145 -0
  461. package/src/features/annotations/components/annotation-context-menu.tsx +164 -0
  462. package/src/features/annotations/components/annotation-drawer.tsx +231 -0
  463. package/src/features/annotations/components/annotation-layer.tsx +271 -0
  464. package/src/features/annotations/components/annotation-pin.tsx +318 -0
  465. package/src/features/annotations/components/annotation-popover.tsx +562 -0
  466. package/src/features/annotations/components/annotation-thread-panel.tsx +485 -0
  467. package/src/features/annotations/components/annotation-thread-preview.tsx +195 -0
  468. package/src/features/annotations/components/annotation-toolbar.tsx +244 -0
  469. package/src/features/annotations/components/keyboard-shortcuts-modal.tsx +79 -0
  470. package/src/features/annotations/docs/ANNOTATIONS_ARCHITECTURE.md +67 -0
  471. package/src/features/annotations/docs/ANNOTATION_SAVE_ARCHITECTURE.md +422 -0
  472. package/src/features/annotations/docs/ANNOTATION_SAVE_FEATURE.md +408 -0
  473. package/src/features/annotations/docs/BOX_ANNOTATION_GUIDE.md +542 -0
  474. package/src/features/annotations/docs/NEXTSTEP.md +28 -0
  475. package/src/features/annotations/docs/STALE_CLOSURE_FIX.md +344 -0
  476. package/src/features/annotations/docs/UNDO_REDO_QUICK_START.md +545 -0
  477. package/src/features/annotations/docs/local_first_canvas_autosave_architecture.md +674 -0
  478. package/src/features/annotations/examples/complete-example.tsx +266 -0
  479. package/src/features/annotations/examples/save-annotation-example.tsx +309 -0
  480. package/src/features/annotations/hooks/__tests__/use-annotation-permissions.property.test.tsx +493 -0
  481. package/src/features/annotations/hooks/index.ts +36 -0
  482. package/src/features/annotations/hooks/use-annotation-batch-save.ts +109 -0
  483. package/src/features/annotations/hooks/use-annotation-comments.ts +353 -0
  484. package/src/features/annotations/hooks/use-annotation-drafts.ts +137 -0
  485. package/src/features/annotations/hooks/use-annotation-edit-state.ts +99 -0
  486. package/src/features/annotations/hooks/use-annotation-history-tracker.ts +159 -0
  487. package/src/features/annotations/hooks/use-annotation-integration.ts +916 -0
  488. package/src/features/annotations/hooks/use-annotation-permissions.ts +210 -0
  489. package/src/features/annotations/hooks/use-annotation-popover.ts +175 -0
  490. package/src/features/annotations/hooks/use-annotation-save.ts +208 -0
  491. package/src/features/annotations/hooks/use-annotation-tools.ts +237 -0
  492. package/src/features/annotations/hooks/use-annotations-with-history.ts +332 -0
  493. package/src/features/annotations/hooks/use-auto-save.ts +94 -0
  494. package/src/features/annotations/index.ts +111 -0
  495. package/src/features/annotations/types/annotation.ts +201 -0
  496. package/src/features/annotations/types/index.ts +28 -0
  497. package/src/features/annotations/utils/history-manager.ts +73 -0
  498. package/src/features/annotations/utils/index.ts +2 -0
  499. package/src/features/annotations/utils/map-attachments-to-threads.ts +28 -0
  500. package/src/features/annotations/utils/position-comment-input.ts +136 -0
  501. package/src/features/annotations/utils/re-sequence-labels.ts +92 -0
  502. package/src/features/annotations/utils/validate-annotation-label.ts +120 -0
  503. package/src/features/auth/api/types.ts +101 -0
  504. package/src/features/auth/components/__tests__/role-gate.test.tsx +448 -0
  505. package/src/features/auth/components/__tests__/social-login-buttons.test.tsx +313 -0
  506. package/src/features/auth/components/auth-card.tsx +36 -0
  507. package/src/features/auth/components/forgot-password-form.tsx +115 -0
  508. package/src/features/auth/components/index.ts +14 -0
  509. package/src/features/auth/components/invite-code-input.tsx +155 -0
  510. package/src/features/auth/components/invited-user-form.tsx +309 -0
  511. package/src/features/auth/components/onboarding-form.tsx +195 -0
  512. package/src/features/auth/components/password-strength-indicator.tsx +113 -0
  513. package/src/features/auth/components/reset-password-form.tsx +138 -0
  514. package/src/features/auth/components/role-gate.tsx +124 -0
  515. package/src/features/auth/components/self-registration-choice.tsx +153 -0
  516. package/src/features/auth/components/sign-in-form.tsx +159 -0
  517. package/src/features/auth/components/sign-up-form.tsx +158 -0
  518. package/src/features/auth/components/social-login-buttons.tsx +219 -0
  519. package/src/features/auth/hooks/__tests__/use-onboarding.test.tsx +109 -0
  520. package/src/features/auth/hooks/__tests__/use-session.test.tsx +160 -0
  521. package/src/features/auth/hooks/index.ts +15 -0
  522. package/src/features/auth/hooks/use-accept-invitation.ts +194 -0
  523. package/src/features/auth/hooks/use-delete-account.ts +86 -0
  524. package/src/features/auth/hooks/use-force-verify.ts +89 -0
  525. package/src/features/auth/hooks/use-forgot-password.ts +144 -0
  526. package/src/features/auth/hooks/use-link-account.ts +78 -0
  527. package/src/features/auth/hooks/use-linked-accounts.ts +88 -0
  528. package/src/features/auth/hooks/use-onboarding.ts +159 -0
  529. package/src/features/auth/hooks/use-resend-verification.ts +139 -0
  530. package/src/features/auth/hooks/use-reset-password.ts +151 -0
  531. package/src/features/auth/hooks/use-reset-rate-limit.ts +56 -0
  532. package/src/features/auth/hooks/use-self-registration.ts +202 -0
  533. package/src/features/auth/hooks/use-session.ts +81 -0
  534. package/src/features/auth/hooks/use-sessions.ts +59 -0
  535. package/src/features/auth/hooks/use-sign-in.ts +234 -0
  536. package/src/features/auth/hooks/use-sign-out.ts +88 -0
  537. package/src/features/auth/hooks/use-sign-up.ts +194 -0
  538. package/src/features/auth/hooks/use-unlink-account.ts +100 -0
  539. package/src/features/auth/hooks/use-verify-email-token.ts +125 -0
  540. package/src/features/auth/index.ts +75 -0
  541. package/src/features/auth/screens/forgot-password-screen.tsx +33 -0
  542. package/src/features/auth/screens/index.ts +7 -0
  543. package/src/features/auth/screens/onboarding-screen.tsx +49 -0
  544. package/src/features/auth/screens/reset-password-screen.tsx +33 -0
  545. package/src/features/auth/screens/sign-in-screen.tsx +61 -0
  546. package/src/features/auth/screens/sign-up-screen.tsx +37 -0
  547. package/src/features/auth/screens/verify-email-confirm-screen.tsx +286 -0
  548. package/src/features/auth/screens/verify-email-screen.tsx +146 -0
  549. package/src/features/auth/types/index.ts +14 -0
  550. package/src/features/auth/utils/__tests__/validators.test.ts +331 -0
  551. package/src/features/auth/utils/password-strength.ts +129 -0
  552. package/src/features/auth/utils/validators.ts +124 -0
  553. package/src/features/email-preview/actions/render-email.ts +21 -0
  554. package/src/features/email-preview/screens/email-preview-screen.tsx +81 -0
  555. package/src/features/folder-scaffold-template/index.ts +0 -0
  556. package/src/features/instance-settings/components/index.ts +6 -0
  557. package/src/features/instance-settings/components/instance-settings-form.tsx +180 -0
  558. package/src/features/instance-settings/components/instance-status-display.tsx +158 -0
  559. package/src/features/instance-settings/index.ts +7 -0
  560. package/src/features/instance-settings/screens/index.ts +5 -0
  561. package/src/features/instance-settings/screens/instance-settings-screen.tsx +59 -0
  562. package/src/features/issues/README.md +330 -0
  563. package/src/features/issues/api/create-issue.ts +19 -0
  564. package/src/features/issues/api/delete-issue.ts +27 -0
  565. package/src/features/issues/api/get-issue-activities.ts +58 -0
  566. package/src/features/issues/api/get-issue-details.ts +25 -0
  567. package/src/features/issues/api/get-project-issues-server.ts +44 -0
  568. package/src/features/issues/api/get-project-issues.ts +21 -0
  569. package/src/features/issues/api/index.ts +44 -0
  570. package/src/features/issues/api/update-issue.ts +31 -0
  571. package/src/features/issues/api/upload-attachment.ts +81 -0
  572. package/src/features/issues/components/activity-timeline.tsx +440 -0
  573. package/src/features/issues/components/canvas-state-indicator.tsx +90 -0
  574. package/src/features/issues/components/centered-canvas-view.tsx +739 -0
  575. package/src/features/issues/components/image-selector.tsx +123 -0
  576. package/src/features/issues/components/image-upload-zone.tsx +262 -0
  577. package/src/features/issues/components/infinite-canvas-background.tsx +163 -0
  578. package/src/features/issues/components/inline-editable-select.tsx +173 -0
  579. package/src/features/issues/components/inline-editable-text.tsx +225 -0
  580. package/src/features/issues/components/inline-editable-textarea.tsx +219 -0
  581. package/src/features/issues/components/inline-editable-user-select.tsx +202 -0
  582. package/src/features/issues/components/issue-deletion-dialog.tsx +142 -0
  583. package/src/features/issues/components/issue-details-panel.tsx +101 -0
  584. package/src/features/issues/components/issues-create-dialog.tsx +578 -0
  585. package/src/features/issues/components/issues-list-filter.tsx +312 -0
  586. package/src/features/issues/components/issues-list.tsx +151 -0
  587. package/src/features/issues/components/issues-priority-badge.tsx +77 -0
  588. package/src/features/issues/components/issues-status-badge.tsx +100 -0
  589. package/src/features/issues/components/metadata-section.tsx +389 -0
  590. package/src/features/issues/components/optimized-attachment-view.tsx +528 -0
  591. package/src/features/issues/components/optimized-image.tsx +257 -0
  592. package/src/features/issues/components/panel-header.tsx +186 -0
  593. package/src/features/issues/components/preload.ts +31 -0
  594. package/src/features/issues/components/priority-selector.tsx +101 -0
  595. package/src/features/issues/components/responsive-issue-layout-skeleton.tsx +139 -0
  596. package/src/features/issues/components/responsive-issue-layout.tsx +617 -0
  597. package/src/features/issues/components/status-selector.tsx +320 -0
  598. package/src/features/issues/components/type-selector.tsx +102 -0
  599. package/src/features/issues/components/upload-progress-overlay.tsx +35 -0
  600. package/src/features/issues/components/uploaded-image-preview.tsx +173 -0
  601. package/src/features/issues/components/workflow-control.tsx +318 -0
  602. package/src/features/issues/components/zoom-controls.tsx +150 -0
  603. package/src/features/issues/config/index.ts +47 -0
  604. package/src/features/issues/config/options.ts +323 -0
  605. package/src/features/issues/config/workflow.ts +102 -0
  606. package/src/features/issues/docs/ARCHITECTURE_DIAGRAM.md +321 -0
  607. package/src/features/issues/docs/BACKEND_ARCHITECTURE.md +194 -0
  608. package/src/features/issues/docs/IMAGE_COMPONENTS_ARCHITECTURE.md +363 -0
  609. package/src/features/issues/docs/IMAGE_COMPONENTS_IMPORTS.md +412 -0
  610. package/src/features/issues/docs/IMPLEMENTATION_CHECKLIST.md +210 -0
  611. package/src/features/issues/docs/ROUTE_SETUP_COMPLETE.md +242 -0
  612. package/src/features/issues/hooks/index.ts +78 -0
  613. package/src/features/issues/hooks/use-canvas-transform.ts +255 -0
  614. package/src/features/issues/hooks/use-create-issue.ts +28 -0
  615. package/src/features/issues/hooks/use-elastic-scroll.ts +296 -0
  616. package/src/features/issues/hooks/use-issue-activities.ts +71 -0
  617. package/src/features/issues/hooks/use-issue-delete.ts +84 -0
  618. package/src/features/issues/hooks/use-issue-details.ts +70 -0
  619. package/src/features/issues/hooks/use-issue-filters.ts +50 -0
  620. package/src/features/issues/hooks/use-issue-update.ts +93 -0
  621. package/src/features/issues/hooks/use-keyboard-shortcuts.ts +104 -0
  622. package/src/features/issues/hooks/use-optimized-image.ts +228 -0
  623. package/src/features/issues/hooks/use-project-issues.ts +14 -0
  624. package/src/features/issues/index.ts +65 -0
  625. package/src/features/issues/screens/issue-details-screen.tsx +207 -0
  626. package/src/features/issues/screens/issue-details-skeletons.tsx +295 -0
  627. package/src/features/issues/screens/issue-share-screen.tsx +56 -0
  628. package/src/features/issues/types/index.ts +48 -0
  629. package/src/features/issues/types/issue.ts +291 -0
  630. package/src/features/issues/utils/filter-issues.ts +141 -0
  631. package/src/features/issues/utils/index.ts +14 -0
  632. package/src/features/legal/index.ts +1 -0
  633. package/src/features/legal/screens/privacy-policy-screen.tsx +307 -0
  634. package/src/features/notifications/api/get-notifications.ts +58 -0
  635. package/src/features/notifications/api/get-unread-count.ts +37 -0
  636. package/src/features/notifications/api/index.ts +35 -0
  637. package/src/features/notifications/api/mark-all-as-read.ts +37 -0
  638. package/src/features/notifications/api/mark-as-read.ts +41 -0
  639. package/src/features/notifications/api/types.ts +109 -0
  640. package/src/features/notifications/hooks/__tests__/use-notification-subscription.test.ts +206 -0
  641. package/src/features/notifications/hooks/index.ts +28 -0
  642. package/src/features/notifications/hooks/use-mark-all-as-read.ts +106 -0
  643. package/src/features/notifications/hooks/use-mark-as-read.ts +106 -0
  644. package/src/features/notifications/hooks/use-notification-subscription.ts +244 -0
  645. package/src/features/notifications/hooks/use-notification-toast.ts +161 -0
  646. package/src/features/notifications/hooks/use-notifications.ts +80 -0
  647. package/src/features/notifications/hooks/use-unread-count.ts +60 -0
  648. package/src/features/notifications/index.ts +48 -0
  649. package/src/features/notifications/utils/group-notifications.ts +152 -0
  650. package/src/features/notifications/utils/index.ts +9 -0
  651. package/src/features/projects/api/create-invitation.ts +45 -0
  652. package/src/features/projects/api/create-project.ts +64 -0
  653. package/src/features/projects/api/delete-project.ts +50 -0
  654. package/src/features/projects/api/get-project-activities.ts +43 -0
  655. package/src/features/projects/api/get-project-members.ts +53 -0
  656. package/src/features/projects/api/get-project.ts +49 -0
  657. package/src/features/projects/api/get-projects.ts +61 -0
  658. package/src/features/projects/api/index.ts +27 -0
  659. package/src/features/projects/api/join-project.ts +52 -0
  660. package/src/features/projects/api/leave-project.ts +51 -0
  661. package/src/features/projects/api/list-invitations.ts +36 -0
  662. package/src/features/projects/api/remove-member.ts +60 -0
  663. package/src/features/projects/api/resend-invitation.ts +36 -0
  664. package/src/features/projects/api/revoke-invitation.ts +36 -0
  665. package/src/features/projects/api/types.ts +286 -0
  666. package/src/features/projects/api/update-member-role.ts +70 -0
  667. package/src/features/projects/api/update-project.ts +69 -0
  668. package/src/features/projects/components/__tests__/project-detail-activity-feed.test.tsx +106 -0
  669. package/src/features/projects/components/__tests__/project-invitation-dialog.test.tsx +211 -0
  670. package/src/features/projects/components/__tests__/project-member-manager-dialog.test.tsx +254 -0
  671. package/src/features/projects/components/index.ts +21 -0
  672. package/src/features/projects/components/project-actions.tsx +248 -0
  673. package/src/features/projects/components/project-create-dialog.tsx +410 -0
  674. package/src/features/projects/components/project-detail-activity-feed.tsx +206 -0
  675. package/src/features/projects/components/project-detail-header.tsx +103 -0
  676. package/src/features/projects/components/project-detail-overview.tsx +128 -0
  677. package/src/features/projects/components/project-icon-selector.test.tsx +49 -0
  678. package/src/features/projects/components/project-icon-selector.tsx +76 -0
  679. package/src/features/projects/components/project-invitation-dialog.tsx +368 -0
  680. package/src/features/projects/components/project-issues.tsx +128 -0
  681. package/src/features/projects/components/project-leave-button.tsx +69 -0
  682. package/src/features/projects/components/project-list-card.tsx +246 -0
  683. package/src/features/projects/components/project-list-filters.tsx +320 -0
  684. package/src/features/projects/components/project-member-manager-dialog.tsx +419 -0
  685. package/src/features/projects/components/project-settings-dialog.tsx +204 -0
  686. package/src/features/projects/components/project-stats.tsx +46 -0
  687. package/src/features/projects/components/project-title-section.tsx +78 -0
  688. package/src/features/projects/config/icons.ts +91 -0
  689. package/src/features/projects/hooks/index.ts +28 -0
  690. package/src/features/projects/hooks/use-create-invitation.ts +83 -0
  691. package/src/features/projects/hooks/use-create-project.ts +77 -0
  692. package/src/features/projects/hooks/use-delete-project.ts +84 -0
  693. package/src/features/projects/hooks/use-join-project.ts +43 -0
  694. package/src/features/projects/hooks/use-leave-project.ts +84 -0
  695. package/src/features/projects/hooks/use-project-activities.ts +39 -0
  696. package/src/features/projects/hooks/use-project-filters.ts +86 -0
  697. package/src/features/projects/hooks/use-project-invitations.ts +66 -0
  698. package/src/features/projects/hooks/use-project-members.ts +57 -0
  699. package/src/features/projects/hooks/use-project.ts +67 -0
  700. package/src/features/projects/hooks/use-projects.ts +49 -0
  701. package/src/features/projects/hooks/use-recent-projects.ts +58 -0
  702. package/src/features/projects/hooks/use-remove-member.ts +89 -0
  703. package/src/features/projects/hooks/use-resend-invitation.ts +68 -0
  704. package/src/features/projects/hooks/use-revoke-invitation.ts +71 -0
  705. package/src/features/projects/hooks/use-team-member-suggestions.ts +133 -0
  706. package/src/features/projects/hooks/use-update-member-role.ts +92 -0
  707. package/src/features/projects/hooks/use-update-project.ts +88 -0
  708. package/src/features/projects/index.ts +91 -0
  709. package/src/features/projects/screens/index.ts +3 -0
  710. package/src/features/projects/screens/invitation-acceptance-screen.tsx +320 -0
  711. package/src/features/projects/screens/project-detail-screen-wrapper.tsx +47 -0
  712. package/src/features/projects/screens/project-detail-screen.tsx +661 -0
  713. package/src/features/projects/screens/projects-list-screen.tsx +161 -0
  714. package/src/features/projects/types/index.ts +59 -0
  715. package/src/features/projects/utils/format-helpers.ts +16 -0
  716. package/src/features/projects/utils/index.ts +2 -0
  717. package/src/features/projects/utils/role-helpers.ts +25 -0
  718. package/src/features/setup/api/complete-setup.ts +21 -0
  719. package/src/features/setup/api/create-admin.ts +21 -0
  720. package/src/features/setup/api/create-first-workspace.ts +21 -0
  721. package/src/features/setup/api/get-instance-status.ts +12 -0
  722. package/src/features/setup/api/get-service-health.ts +12 -0
  723. package/src/features/setup/api/index.ts +44 -0
  724. package/src/features/setup/api/save-instance-config.ts +21 -0
  725. package/src/features/setup/api/types.ts +122 -0
  726. package/src/features/setup/components/__tests__/setup-wizard-ui.test.tsx +362 -0
  727. package/src/features/setup/components/admin-account-step.tsx +205 -0
  728. package/src/features/setup/components/first-workspace-step.tsx +120 -0
  729. package/src/features/setup/components/index.ts +9 -0
  730. package/src/features/setup/components/instance-config-step.tsx +107 -0
  731. package/src/features/setup/components/mail-config-step.tsx +205 -0
  732. package/src/features/setup/components/sample-data-step.tsx +131 -0
  733. package/src/features/setup/components/service-health-step.tsx +180 -0
  734. package/src/features/setup/components/service-status-badge.tsx +50 -0
  735. package/src/features/setup/components/setup-progress.tsx +103 -0
  736. package/src/features/setup/components/setup-wizard.tsx +169 -0
  737. package/src/features/setup/hooks/index.ts +12 -0
  738. package/src/features/setup/hooks/use-complete-setup.ts +23 -0
  739. package/src/features/setup/hooks/use-create-admin.ts +25 -0
  740. package/src/features/setup/hooks/use-create-first-workspace.ts +24 -0
  741. package/src/features/setup/hooks/use-instance-status.ts +21 -0
  742. package/src/features/setup/hooks/use-save-instance-config.ts +23 -0
  743. package/src/features/setup/hooks/use-service-health.ts +21 -0
  744. package/src/features/setup/hooks/use-setup-wizard.ts +152 -0
  745. package/src/features/setup/hooks/use-workspace-mode.ts +19 -0
  746. package/src/features/setup/index.ts +30 -0
  747. package/src/features/setup/screens/index.ts +1 -0
  748. package/src/features/setup/screens/setup-screen.tsx +40 -0
  749. package/src/features/setup/types/index.ts +69 -0
  750. package/src/features/setup/utils/index.ts +78 -0
  751. package/src/features/team-settings/components/index.ts +39 -0
  752. package/src/features/team-settings/components/loading-states.tsx +296 -0
  753. package/src/features/team-settings/components/permission-guard.tsx +23 -0
  754. package/src/features/team-settings/components/settings-card.tsx +22 -0
  755. package/src/features/team-settings/components/settings-context-provider.tsx +51 -0
  756. package/src/features/team-settings/components/settings-error-boundary.tsx +366 -0
  757. package/src/features/team-settings/components/settings-navigation.tsx +87 -0
  758. package/src/features/team-settings/components/settings-section.tsx +23 -0
  759. package/src/features/team-settings/components/team-danger-zone.tsx +275 -0
  760. package/src/features/team-settings/components/team-information-form.tsx +116 -0
  761. package/src/features/team-settings/components/team-invitations-list.tsx +463 -0
  762. package/src/features/team-settings/components/team-members-list.tsx +342 -0
  763. package/src/features/team-settings/components/team-permission-guard.tsx +56 -0
  764. package/src/features/team-settings/components/team-setting-integrations.tsx +27 -0
  765. package/src/features/team-settings/components/team-setting-member.tsx +28 -0
  766. package/src/features/team-settings/components/team-settings-general.tsx +131 -0
  767. package/src/features/team-settings/components/transfer-ownership-modal.tsx +164 -0
  768. package/src/features/team-settings/components/unauthorized-access.tsx +52 -0
  769. package/src/features/team-settings/hooks/__tests__/use-team-settings.test.tsx +139 -0
  770. package/src/features/team-settings/hooks/index.ts +1 -0
  771. package/src/features/team-settings/hooks/use-team-settings.ts +148 -0
  772. package/src/features/team-settings/hooks/use-transfer-ownership.ts +45 -0
  773. package/src/features/team-settings/index.ts +25 -0
  774. package/src/features/team-settings/screens/index.ts +1 -0
  775. package/src/features/team-settings/screens/team-settings-screen.tsx +33 -0
  776. package/src/features/team-settings/types/index.ts +78 -0
  777. package/src/features/team-settings/utils/index.ts +6 -0
  778. package/src/features/team-settings/utils/mock-data.ts +40 -0
  779. package/src/features/teams/api/cancel-invitation.ts +25 -0
  780. package/src/features/teams/api/create-invitation.ts +28 -0
  781. package/src/features/teams/api/create-team.ts +14 -0
  782. package/src/features/teams/api/delete-team.ts +19 -0
  783. package/src/features/teams/api/get-invitations.ts +25 -0
  784. package/src/features/teams/api/get-team-members.ts +25 -0
  785. package/src/features/teams/api/get-team.ts +13 -0
  786. package/src/features/teams/api/get-teams.ts +19 -0
  787. package/src/features/teams/api/index.ts +49 -0
  788. package/src/features/teams/api/leave-team.ts +22 -0
  789. package/src/features/teams/api/remove-member.ts +25 -0
  790. package/src/features/teams/api/resend-invitation.ts +26 -0
  791. package/src/features/teams/api/switch-team.ts +13 -0
  792. package/src/features/teams/api/types.ts +122 -0
  793. package/src/features/teams/api/update-member-roles.ts +28 -0
  794. package/src/features/teams/api/update-team.ts +17 -0
  795. package/src/features/teams/components/create-team-dialog.tsx +105 -0
  796. package/src/features/teams/hooks/__tests__/cache-invalidation.property.test.tsx +268 -0
  797. package/src/features/teams/hooks/index.ts +35 -0
  798. package/src/features/teams/hooks/use-can-manage-members.ts +21 -0
  799. package/src/features/teams/hooks/use-can-manage-team.ts +21 -0
  800. package/src/features/teams/hooks/use-cancel-invitation.ts +52 -0
  801. package/src/features/teams/hooks/use-create-invitation.ts +59 -0
  802. package/src/features/teams/hooks/use-create-team.ts +38 -0
  803. package/src/features/teams/hooks/use-delete-team.ts +43 -0
  804. package/src/features/teams/hooks/use-invitations.ts +31 -0
  805. package/src/features/teams/hooks/use-leave-team.ts +43 -0
  806. package/src/features/teams/hooks/use-remove-member.ts +58 -0
  807. package/src/features/teams/hooks/use-resend-invitation.ts +52 -0
  808. package/src/features/teams/hooks/use-switch-team.ts +41 -0
  809. package/src/features/teams/hooks/use-team-members.ts +31 -0
  810. package/src/features/teams/hooks/use-team-permissions.ts +102 -0
  811. package/src/features/teams/hooks/use-team.ts +30 -0
  812. package/src/features/teams/hooks/use-teams.ts +26 -0
  813. package/src/features/teams/hooks/use-update-member-roles.ts +64 -0
  814. package/src/features/teams/hooks/use-update-team.ts +47 -0
  815. package/src/features/teams/index.ts +111 -0
  816. package/src/features/user-settings/actions/set-password.ts +63 -0
  817. package/src/features/user-settings/api/index.ts +16 -0
  818. package/src/features/user-settings/components/__tests__/security-settings.test.tsx +125 -0
  819. package/src/features/user-settings/components/delete-account-dialog.tsx +185 -0
  820. package/src/features/user-settings/components/index.ts +13 -0
  821. package/src/features/user-settings/components/integrations-list.tsx +152 -0
  822. package/src/features/user-settings/components/notification-preferences.tsx +112 -0
  823. package/src/features/user-settings/components/other-settings.tsx +126 -0
  824. package/src/features/user-settings/components/password-section.tsx +297 -0
  825. package/src/features/user-settings/components/security-settings.tsx +184 -0
  826. package/src/features/user-settings/components/user-preferences.tsx +146 -0
  827. package/src/features/user-settings/hooks/index.ts +8 -0
  828. package/src/features/user-settings/hooks/use-notification-preferences.ts +65 -0
  829. package/src/features/user-settings/hooks/use-user-preferences.ts +52 -0
  830. package/src/features/user-settings/index.ts +22 -0
  831. package/src/features/user-settings/screens/index.ts +11 -0
  832. package/src/features/user-settings/screens/integrations-screen.tsx +24 -0
  833. package/src/features/user-settings/screens/notifications-screen.tsx +29 -0
  834. package/src/features/user-settings/screens/other-settings-screen.tsx +24 -0
  835. package/src/features/user-settings/screens/setting-preferences-screen.tsx +24 -0
  836. package/src/features/user-settings/screens/user-settings-screen.tsx +23 -0
  837. package/src/features/user-settings/types/index.ts +42 -0
  838. package/src/features/user-settings/utils/index.ts +8 -0
  839. package/src/hooks/use-long-press.ts +196 -0
  840. package/src/hooks/use-media-upload.ts +95 -0
  841. package/src/hooks/use-mobile.ts +19 -0
  842. package/src/hooks/use-team.ts +32 -0
  843. package/src/instrumentation.ts +14 -0
  844. package/src/lib/__tests__/auth-config.test.ts +166 -0
  845. package/src/lib/__tests__/config.test.ts +42 -0
  846. package/src/lib/__tests__/db.test.ts +101 -0
  847. package/src/lib/__tests__/env.property.test.ts +197 -0
  848. package/src/lib/__tests__/env.test.ts +177 -0
  849. package/src/lib/__tests__/health-check.test.ts +87 -0
  850. package/src/lib/__tests__/oauth-errors.test.ts +184 -0
  851. package/src/lib/__tests__/oauth-redirect.test.ts +224 -0
  852. package/src/lib/__tests__/oauth-scopes.test.ts +268 -0
  853. package/src/lib/__tests__/storage.test.ts +58 -0
  854. package/src/lib/__tests__/url-validator.test.ts +232 -0
  855. package/src/lib/api-client.ts +93 -0
  856. package/src/lib/auth-client.ts +5 -0
  857. package/src/lib/auth-config.ts +146 -0
  858. package/src/lib/auth.ts +209 -0
  859. package/src/lib/config.ts +228 -0
  860. package/src/lib/cors.ts +96 -0
  861. package/src/lib/date.ts +16 -0
  862. package/src/lib/db.ts +88 -0
  863. package/src/lib/env.ts +489 -0
  864. package/src/lib/feedback.ts +29 -0
  865. package/src/lib/health-check.ts +229 -0
  866. package/src/lib/logger.ts +204 -0
  867. package/src/lib/oauth-errors.ts +138 -0
  868. package/src/lib/performance.ts +367 -0
  869. package/src/lib/query-server.ts +35 -0
  870. package/src/lib/query.tsx +43 -0
  871. package/src/lib/resend.ts +36 -0
  872. package/src/lib/security-headers.ts +165 -0
  873. package/src/lib/setup-status.ts +34 -0
  874. package/src/lib/storage.ts +150 -0
  875. package/src/lib/supabase-client.ts +58 -0
  876. package/src/lib/testing/test-db.ts +75 -0
  877. package/src/lib/url-validator.ts +168 -0
  878. package/src/lib/utils.ts +6 -0
  879. package/src/mocks/README.md +42 -0
  880. package/src/mocks/activity.fixtures.ts +281 -0
  881. package/src/mocks/annotation.fixtures.ts +325 -0
  882. package/src/mocks/attachment.fixtures.ts +80 -0
  883. package/src/mocks/email.fixtures.ts +61 -0
  884. package/src/mocks/index.ts +33 -0
  885. package/src/mocks/issue.fixtures.ts +364 -0
  886. package/src/mocks/landing.fixtures.ts +118 -0
  887. package/src/mocks/notification.fixtures.ts +111 -0
  888. package/src/mocks/project-activity.fixtures.ts +69 -0
  889. package/src/mocks/project-invitation.fixtures.ts +95 -0
  890. package/src/mocks/project-member.fixtures.ts +93 -0
  891. package/src/mocks/project.fixtures.ts +249 -0
  892. package/src/mocks/settings.fixtures.ts +56 -0
  893. package/src/mocks/share.fixtures.ts +48 -0
  894. package/src/mocks/team-member.fixtures.ts +147 -0
  895. package/src/mocks/team.fixtures.ts +35 -0
  896. package/src/mocks/user-settings.fixtures.ts +77 -0
  897. package/src/mocks/user.fixtures.ts +21 -0
  898. package/src/providers/theme-provider.tsx +11 -0
  899. package/src/proxy/__tests__/proxy-setup-gate.test.ts +43 -0
  900. package/src/proxy.ts +169 -0
  901. package/src/server/annotations/__tests__/annotation-limit.property.test.ts +291 -0
  902. package/src/server/annotations/__tests__/attachment-deletion-cascade.property.test.ts +385 -0
  903. package/src/server/annotations/__tests__/comment-delete-authorization.property.test.ts +419 -0
  904. package/src/server/annotations/__tests__/sanitize.property.test.ts +302 -0
  905. package/src/server/annotations/annotation-service.ts +305 -0
  906. package/src/server/annotations/comment-service.ts +288 -0
  907. package/src/server/annotations/index.ts +61 -0
  908. package/src/server/annotations/permission-utils.ts +125 -0
  909. package/src/server/annotations/sanitize.ts +134 -0
  910. package/src/server/annotations/types.ts +161 -0
  911. package/src/server/audit/audit-service.ts +85 -0
  912. package/src/server/audit/index.ts +11 -0
  913. package/src/server/audit/types.ts +75 -0
  914. package/src/server/auth/__tests__/README.md +368 -0
  915. package/src/server/auth/__tests__/account-linking.integration.test.ts +410 -0
  916. package/src/server/auth/__tests__/auth-integration.test.ts +811 -0
  917. package/src/server/auth/__tests__/cookies.test.ts +337 -0
  918. package/src/server/auth/__tests__/login.property.test.ts +428 -0
  919. package/src/server/auth/__tests__/oauth-integration.test.ts +555 -0
  920. package/src/server/auth/__tests__/password.test.ts +194 -0
  921. package/src/server/auth/__tests__/rate-limiter.test.ts +450 -0
  922. package/src/server/auth/__tests__/rbac.test.ts +474 -0
  923. package/src/server/auth/__tests__/session.test.ts +599 -0
  924. package/src/server/auth/__tests__/signup.property.test.ts +224 -0
  925. package/src/server/auth/__tests__/token-encryption.test.ts +171 -0
  926. package/src/server/auth/__tests__/tokens.test.ts +476 -0
  927. package/src/server/auth/__tests__/verify-email.property.test.ts +372 -0
  928. package/src/server/auth/cookies.ts +184 -0
  929. package/src/server/auth/password.ts +94 -0
  930. package/src/server/auth/rate-limiter.ts +257 -0
  931. package/src/server/auth/rbac.ts +1168 -0
  932. package/src/server/auth/session.ts +392 -0
  933. package/src/server/auth/token-encryption.ts +201 -0
  934. package/src/server/auth/tokens.ts +397 -0
  935. package/src/server/db/schema/account.ts +21 -0
  936. package/src/server/db/schema/annotation-read-status.ts +57 -0
  937. package/src/server/db/schema/better-auth-verifications.ts +27 -0
  938. package/src/server/db/schema/email-jobs.ts +24 -0
  939. package/src/server/db/schema/index.ts +20 -0
  940. package/src/server/db/schema/instance-settings.ts +42 -0
  941. package/src/server/db/schema/issue-activities.ts +97 -0
  942. package/src/server/db/schema/issue-attachments.ts +101 -0
  943. package/src/server/db/schema/issues.ts +139 -0
  944. package/src/server/db/schema/notifications.ts +119 -0
  945. package/src/server/db/schema/project-activities.ts +116 -0
  946. package/src/server/db/schema/project-invitations.ts +34 -0
  947. package/src/server/db/schema/project-members.ts +29 -0
  948. package/src/server/db/schema/projects.ts +46 -0
  949. package/src/server/db/schema/sessions.ts +17 -0
  950. package/src/server/db/schema/team-invitations.ts +22 -0
  951. package/src/server/db/schema/team-members.ts +18 -0
  952. package/src/server/db/schema/teams.ts +15 -0
  953. package/src/server/db/schema/user-roles.ts +14 -0
  954. package/src/server/db/schema/users.ts +17 -0
  955. package/src/server/db/schema/verification-tokens.ts +15 -0
  956. package/src/server/email/README.md +258 -0
  957. package/src/server/email/__tests__/client.property.test.ts +218 -0
  958. package/src/server/email/__tests__/payload.property.test.ts +205 -0
  959. package/src/server/email/__tests__/queue.test.ts +407 -0
  960. package/src/server/email/__tests__/render-template.test.tsx +172 -0
  961. package/src/server/email/client.ts +70 -0
  962. package/src/server/email/index.ts +20 -0
  963. package/src/server/email/providers/console-provider.ts +43 -0
  964. package/src/server/email/providers/index.ts +5 -0
  965. package/src/server/email/providers/resend-provider.ts +59 -0
  966. package/src/server/email/providers/smtp-provider.ts +65 -0
  967. package/src/server/email/queue.ts +365 -0
  968. package/src/server/email/render-template.tsx +117 -0
  969. package/src/server/email/templates/layout.tsx +66 -0
  970. package/src/server/email/templates/ownership-transfer-email.tsx +75 -0
  971. package/src/server/email/templates/password-reset-email.tsx +72 -0
  972. package/src/server/email/templates/project-invitation-email.tsx +70 -0
  973. package/src/server/email/templates/security-alert-email.tsx +161 -0
  974. package/src/server/email/templates/team-invitation-email.tsx +68 -0
  975. package/src/server/email/templates/verification-email.tsx +61 -0
  976. package/src/server/email/templates/welcome-email.tsx +60 -0
  977. package/src/server/email/worker.ts +182 -0
  978. package/src/server/issues/activity-service.ts +422 -0
  979. package/src/server/issues/attachment-service.ts +379 -0
  980. package/src/server/issues/index.ts +68 -0
  981. package/src/server/issues/issue-service.ts +569 -0
  982. package/src/server/issues/types.ts +233 -0
  983. package/src/server/notifications/__tests__/notification-service.integration.test.ts +405 -0
  984. package/src/server/notifications/index.ts +13 -0
  985. package/src/server/notifications/notification-service.ts +526 -0
  986. package/src/server/notifications/types.ts +234 -0
  987. package/src/server/projects/__tests__/activity-logging.integration.test.ts +319 -0
  988. package/src/server/projects/__tests__/invitation-email.integration.test.ts +258 -0
  989. package/src/server/projects/__tests__/invitation-service.integration.test.ts +651 -0
  990. package/src/server/projects/__tests__/member-service.property.test.ts +397 -0
  991. package/src/server/projects/__tests__/project-service.property.test.ts +116 -0
  992. package/src/server/projects/activity-service.ts +548 -0
  993. package/src/server/projects/index.ts +11 -0
  994. package/src/server/projects/invitation-service.ts +773 -0
  995. package/src/server/projects/member-service.ts +458 -0
  996. package/src/server/projects/project-service.ts +491 -0
  997. package/src/server/projects/schemas.ts +310 -0
  998. package/src/server/projects/types.ts +166 -0
  999. package/src/server/projects/utils.ts +128 -0
  1000. package/src/server/setup/__tests__/health-check.property.test.ts +70 -0
  1001. package/src/server/setup/__tests__/sample-data.property.test.ts +82 -0
  1002. package/src/server/setup/__tests__/setup.property.test.ts +95 -0
  1003. package/src/server/setup/health-check-service.ts +294 -0
  1004. package/src/server/setup/index.ts +35 -0
  1005. package/src/server/setup/sample-data-service.ts +233 -0
  1006. package/src/server/setup/setup-service.ts +229 -0
  1007. package/src/server/setup/types.ts +96 -0
  1008. package/src/server/teams/__tests__/export.property.test.ts +542 -0
  1009. package/src/server/teams/__tests__/get-members.integration.test.ts +105 -0
  1010. package/src/server/teams/__tests__/invitation-flow.integration.test.ts +402 -0
  1011. package/src/server/teams/__tests__/invitations.property.test.ts +235 -0
  1012. package/src/server/teams/__tests__/member-management-flow.integration.test.ts +306 -0
  1013. package/src/server/teams/__tests__/members.property.test.ts +180 -0
  1014. package/src/server/teams/__tests__/ownership-transfer.property.test.ts +173 -0
  1015. package/src/server/teams/__tests__/resource-limits.property.test.ts +382 -0
  1016. package/src/server/teams/__tests__/team-context-management.integration.test.ts +396 -0
  1017. package/src/server/teams/__tests__/team-context.property.test.ts +854 -0
  1018. package/src/server/teams/__tests__/team-creation-flow.integration.test.ts +310 -0
  1019. package/src/server/teams/__tests__/team-creation.property.test.ts +280 -0
  1020. package/src/server/teams/errors.ts +396 -0
  1021. package/src/server/teams/export-service.ts +383 -0
  1022. package/src/server/teams/index.ts +10 -0
  1023. package/src/server/teams/invitation-service.ts +708 -0
  1024. package/src/server/teams/member-service.ts +334 -0
  1025. package/src/server/teams/resource-limits.ts +211 -0
  1026. package/src/server/teams/slug.ts +58 -0
  1027. package/src/server/teams/team-context.ts +648 -0
  1028. package/src/server/teams/team-service.ts +660 -0
  1029. package/src/server/teams/types.ts +81 -0
  1030. package/src/server/teams/validation.ts +209 -0
  1031. package/src/styles/globals.css +160 -0
  1032. package/src/types/deployment.ts +39 -0
  1033. package/supabase/config.toml +357 -0
  1034. package/supabase/seed.sql +480 -0
  1035. package/tests/e2e/.gitkeep +0 -0
  1036. package/tests/e2e/QUICK_START.md +98 -0
  1037. package/tests/e2e/README.md +301 -0
  1038. package/tests/e2e/auth.spec.ts +583 -0
  1039. package/tests/e2e/global-setup.ts +23 -0
  1040. package/tests/e2e/global-teardown.ts +23 -0
  1041. package/tests/e2e/helpers/auth-helpers.ts +310 -0
  1042. package/tests/e2e/helpers/test-fixtures.ts +286 -0
  1043. package/tests/e2e/smoke-test.spec.ts +330 -0
  1044. package/tsconfig.json +48 -0
  1045. package/vitest.config.ts +50 -0
  1046. package/vitest.setup.ts +56 -0
  1047. package/dist/index.js +0 -778
@@ -0,0 +1,1714 @@
1
+ /**
2
+ * Property-based tests for database migration runner
3
+ *
4
+ * Tests migration system correctness properties using property-based testing with fast-check.
5
+ *
6
+ * Feature: automated-drizzle-migrations
7
+ */
8
+
9
+ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
10
+ import fc from 'fast-check';
11
+
12
+ // Property test configuration
13
+ const PROPERTY_CONFIG = {
14
+ numRuns: 100,
15
+ verbose: false,
16
+ };
17
+
18
+ // Mock console methods to capture log output
19
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
20
+ let consoleInfoSpy: ReturnType<typeof vi.spyOn>;
21
+ let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
22
+ let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
23
+
24
+ const getAllLogMessages = (spy: ReturnType<typeof vi.spyOn>): string[] =>
25
+ spy.mock.calls.map((call: unknown[]) => String(call[0] ?? ''));
26
+
27
+ beforeEach(() => {
28
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
29
+ consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
30
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
31
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
32
+ });
33
+
34
+ afterEach(() => {
35
+ consoleLogSpy.mockRestore();
36
+ consoleInfoSpy.mockRestore();
37
+ consoleWarnSpy.mockRestore();
38
+ consoleErrorSpy.mockRestore();
39
+ });
40
+
41
+ // ============================================================================
42
+ // Helper Functions (extracted from migrate.ts for testing)
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Validates the DIRECT_URL environment variable
47
+ */
48
+ function validateEnvironment(directUrl?: string): { valid: boolean; url?: string; error?: string } {
49
+ const DIRECT_URL = directUrl;
50
+
51
+ // Check if DIRECT_URL exists
52
+ if (!DIRECT_URL) {
53
+ return {
54
+ valid: false,
55
+ error: "DIRECT_URL environment variable is not set. Please configure it in your environment or .env.local file.",
56
+ };
57
+ }
58
+
59
+ // Check if DIRECT_URL is not just whitespace
60
+ if (DIRECT_URL.trim().length === 0) {
61
+ return {
62
+ valid: false,
63
+ error: "DIRECT_URL environment variable is empty. Please provide a valid PostgreSQL connection string.",
64
+ };
65
+ }
66
+
67
+ // Validate PostgreSQL URL format
68
+ try {
69
+ const url = new URL(DIRECT_URL);
70
+
71
+ // Check if it's a PostgreSQL URL
72
+ if (!url.protocol.startsWith("postgres")) {
73
+ return {
74
+ valid: false,
75
+ error: `Invalid database URL protocol: ${url.protocol}. Expected 'postgres:' or 'postgresql:'.`,
76
+ };
77
+ }
78
+
79
+ // Check if hostname exists
80
+ if (!url.hostname) {
81
+ return {
82
+ valid: false,
83
+ error: "Invalid database URL: missing hostname.",
84
+ };
85
+ }
86
+
87
+ return { valid: true, url: DIRECT_URL };
88
+ } catch (error) {
89
+ return {
90
+ valid: false,
91
+ error: `Invalid database URL format: ${error instanceof Error ? error.message : String(error)}`,
92
+ };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Formats errors with GitHub Actions annotations
98
+ */
99
+ function formatError(error: unknown, context?: string): string {
100
+ const errorObj = error as any;
101
+ let message = "";
102
+
103
+ if (context) {
104
+ message += `Context: ${context}\n`;
105
+ }
106
+
107
+ // Extract error message
108
+ if (errorObj?.message) {
109
+ message += `Error: ${errorObj.message}\n`;
110
+ } else {
111
+ message += `Error: ${String(error)}\n`;
112
+ }
113
+
114
+ // Extract SQL state if available (PostgreSQL error codes)
115
+ if (errorObj?.code) {
116
+ message += `SQL State: ${errorObj.code}\n`;
117
+ }
118
+
119
+ // Extract position/line number if available
120
+ if (errorObj?.position) {
121
+ message += `Position: ${errorObj.position}\n`;
122
+ }
123
+
124
+ // Extract constraint name for constraint violations
125
+ if (errorObj?.constraint) {
126
+ message += `Constraint: ${errorObj.constraint}\n`;
127
+ }
128
+
129
+ // Extract table name if available
130
+ if (errorObj?.table) {
131
+ message += `Table: ${errorObj.table}\n`;
132
+ }
133
+
134
+ // Extract column name if available
135
+ if (errorObj?.column) {
136
+ message += `Column: ${errorObj.column}\n`;
137
+ }
138
+
139
+ // Add troubleshooting guidance
140
+ message += "\nTroubleshooting:\n";
141
+
142
+ if (errorObj?.code === "42P01") {
143
+ message += "- Table does not exist. Ensure migrations are applied in order.\n";
144
+ } else if (errorObj?.code === "23505") {
145
+ message += "- Unique constraint violation. Check for duplicate data.\n";
146
+ } else if (errorObj?.code === "23503") {
147
+ message += "- Foreign key constraint violation. Ensure referenced records exist.\n";
148
+ } else if (errorObj?.code === "42601") {
149
+ message += "- SQL syntax error. Review the migration SQL for syntax issues.\n";
150
+ } else if (errorObj?.message?.includes("timeout")) {
151
+ message += "- Connection timeout. Check network connectivity and database availability.\n";
152
+ } else if (errorObj?.message?.includes("authentication")) {
153
+ message += "- Authentication failed. Verify database credentials in DIRECT_URL.\n";
154
+ } else {
155
+ message += "- Review the error details above and check the migration SQL.\n";
156
+ message += "- Ensure the database is accessible and has sufficient resources.\n";
157
+ }
158
+
159
+ return message;
160
+ }
161
+
162
+ /**
163
+ * Simulates logging migration context
164
+ */
165
+ function logMigrationContext(branch: string, commit: string, environment: string): void {
166
+ console.log(`📋 Migration Context:`);
167
+ console.log(` Branch: ${branch}`);
168
+ console.log(` Commit: ${commit.substring(0, 7)}`);
169
+ console.log(` Environment: ${environment}`);
170
+ }
171
+
172
+ // ============================================================================
173
+ // Arbitraries for generating test data
174
+ // ============================================================================
175
+
176
+ // Valid PostgreSQL URL arbitrary
177
+ const validPostgresUrlArb = fc.record({
178
+ protocol: fc.constantFrom('postgres:', 'postgresql:'),
179
+ username: fc.stringMatching(/^[a-zA-Z0-9_]{1,20}$/),
180
+ password: fc.stringMatching(/^[a-zA-Z0-9_]{1,20}$/),
181
+ hostname: fc.domain(),
182
+ port: fc.integer({ min: 1024, max: 65535 }),
183
+ database: fc.stringMatching(/^[a-zA-Z0-9_]{1,20}$/),
184
+ }).map(({ protocol, username, password, hostname, port, database }) =>
185
+ `${protocol}//${username}:${password}@${hostname}:${port}/${database}`
186
+ );
187
+
188
+ // Invalid URL arbitrary
189
+ const invalidUrlArb = fc.oneof(
190
+ fc.constant(''),
191
+ fc.constant(' '),
192
+ fc.constant('not-a-url'),
193
+ fc.constant('http://example.com'),
194
+ fc.constant('mysql://localhost:3306/db'),
195
+ fc.string({ minLength: 1, maxLength: 50 }).filter(s => !s.includes('://')),
196
+ );
197
+
198
+ // PostgreSQL error code arbitrary
199
+ const postgresErrorCodeArb = fc.constantFrom(
200
+ '42P01', // undefined_table
201
+ '23505', // unique_violation
202
+ '23503', // foreign_key_violation
203
+ '42601', // syntax_error
204
+ '08006', // connection_failure
205
+ '28P01', // invalid_password
206
+ );
207
+
208
+ // Hex string arbitrary (for migration hashes)
209
+ const hexStringArb = (length: number) =>
210
+ fc.array(fc.constantFrom('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'), {
211
+ minLength: length,
212
+ maxLength: length
213
+ }).map(arr => arr.join(''));
214
+
215
+ // Branch name arbitrary
216
+ const branchNameArb = fc.constantFrom('main', 'develop', 'feature/test', 'hotfix/bug');
217
+
218
+ // Commit SHA arbitrary (40 character hex string)
219
+ const commitShaArb = fc.array(fc.constantFrom('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'), { minLength: 40, maxLength: 40 }).map(arr => arr.join(''));
220
+
221
+ // Environment arbitrary
222
+ const environmentArb = fc.constantFrom('Production', 'Preview', 'Development');
223
+
224
+ // ============================================================================
225
+ // Property Tests
226
+ // ============================================================================
227
+
228
+ describe('Migration Runner - Property-Based Tests', () => {
229
+ /**
230
+ * Feature: automated-drizzle-migrations, Property 9: Configuration validation completeness
231
+ * Validates: Requirements 2.1, 2.2
232
+ *
233
+ * For any workflow execution, if the DIRECT_URL environment variable is missing or invalid,
234
+ * the migration system should fail before attempting any database operations.
235
+ */
236
+ test('Property 9: Configuration validation completeness - missing or invalid DIRECT_URL fails', async () => {
237
+ await fc.assert(
238
+ fc.asyncProperty(
239
+ invalidUrlArb,
240
+ async (invalidUrl) => {
241
+ // Validate environment with invalid URL
242
+ const result = validateEnvironment(invalidUrl);
243
+
244
+ // Verify validation fails
245
+ expect(result.valid).toBe(false);
246
+ expect(result.error).toBeDefined();
247
+ expect(result.error).toBeTruthy();
248
+ expect(typeof result.error).toBe('string');
249
+ expect(result.error!.length).toBeGreaterThan(0);
250
+
251
+ // Verify error message is descriptive
252
+ if (invalidUrl === '' || invalidUrl.trim() === '') {
253
+ expect(result.error).toMatch(/not set|empty/i);
254
+ } else if (!invalidUrl.includes('://')) {
255
+ // Some strings without :// are valid URLs (e.g. "scheme:path"), so they might fail on protocol instead of format
256
+ expect(result.error).toMatch(/invalid.*(url.*format|protocol)/i);
257
+ } else if (!invalidUrl.startsWith('postgres')) {
258
+ expect(result.error).toMatch(/invalid.*protocol|protocol/i);
259
+ }
260
+ }
261
+ ),
262
+ PROPERTY_CONFIG
263
+ );
264
+ });
265
+
266
+ /**
267
+ * Property 9 (valid URLs): Valid PostgreSQL URLs pass validation
268
+ */
269
+ test('Property 9: Configuration validation completeness - valid DIRECT_URL passes', async () => {
270
+ await fc.assert(
271
+ fc.asyncProperty(
272
+ validPostgresUrlArb,
273
+ async (validUrl) => {
274
+ // Validate environment with valid URL
275
+ const result = validateEnvironment(validUrl);
276
+
277
+ // Verify validation succeeds
278
+ expect(result.valid).toBe(true);
279
+ expect(result.url).toBe(validUrl);
280
+ expect(result.error).toBeUndefined();
281
+ }
282
+ ),
283
+ PROPERTY_CONFIG
284
+ );
285
+ });
286
+
287
+ /**
288
+ * Feature: automated-drizzle-migrations, Property 7: Error message completeness
289
+ * Validates: Requirements 7.1, 7.2, 7.3, 7.4
290
+ *
291
+ * For any migration failure, the error output should contain sufficient information
292
+ * (SQL error, line number, migration file name) to diagnose the issue without
293
+ * requiring additional database queries.
294
+ */
295
+ test('Property 7: Error message completeness - SQL errors include diagnostic information', async () => {
296
+ await fc.assert(
297
+ fc.asyncProperty(
298
+ postgresErrorCodeArb,
299
+ fc.string({ minLength: 10, maxLength: 200 }),
300
+ fc.option(fc.integer({ min: 1, max: 1000 }), { nil: undefined }),
301
+ fc.option(fc.string({ minLength: 1, maxLength: 50 }), { nil: undefined }),
302
+ fc.option(fc.string({ minLength: 1, maxLength: 50 }), { nil: undefined }),
303
+ fc.option(fc.string({ minLength: 1, maxLength: 50 }), { nil: undefined }),
304
+ async (errorCode, errorMessage, position, constraint, table, column) => {
305
+ // Create a PostgreSQL-like error object
306
+ const error = {
307
+ message: errorMessage,
308
+ code: errorCode,
309
+ position,
310
+ constraint,
311
+ table,
312
+ column,
313
+ };
314
+
315
+ // Format the error
316
+ const formattedError = formatError(error, 'Migration execution');
317
+
318
+ // Verify error message contains context
319
+ expect(formattedError).toContain('Context: Migration execution');
320
+
321
+ // Verify error message contains the error text
322
+ expect(formattedError).toContain(`Error: ${errorMessage}`);
323
+
324
+ // Verify error message contains SQL state
325
+ expect(formattedError).toContain(`SQL State: ${errorCode}`);
326
+
327
+ // Verify optional fields are included when present
328
+ if (position) {
329
+ expect(formattedError).toContain(`Position: ${position}`);
330
+ }
331
+
332
+ if (constraint) {
333
+ expect(formattedError).toContain(`Constraint: ${constraint}`);
334
+ }
335
+
336
+ if (table) {
337
+ expect(formattedError).toContain(`Table: ${table}`);
338
+ }
339
+
340
+ if (column) {
341
+ expect(formattedError).toContain(`Column: ${column}`);
342
+ }
343
+
344
+ // Verify troubleshooting guidance is included
345
+ expect(formattedError).toContain('Troubleshooting:');
346
+
347
+ // Verify specific troubleshooting guidance based on error code
348
+ if (errorCode === '42P01') {
349
+ expect(formattedError).toContain('Table does not exist');
350
+ } else if (errorCode === '23505') {
351
+ expect(formattedError).toContain('Unique constraint violation');
352
+ } else if (errorCode === '23503') {
353
+ expect(formattedError).toContain('Foreign key constraint violation');
354
+ } else if (errorCode === '42601') {
355
+ expect(formattedError).toContain('SQL syntax error');
356
+ }
357
+ }
358
+ ),
359
+ PROPERTY_CONFIG
360
+ );
361
+ });
362
+
363
+ /**
364
+ * Property 7 (error types): Different error types have appropriate troubleshooting
365
+ */
366
+ test('Property 7: Error message completeness - error types have specific troubleshooting', async () => {
367
+ await fc.assert(
368
+ fc.asyncProperty(
369
+ fc.oneof(
370
+ fc.constant({ message: 'Connection timeout after 10s', code: '08006' }),
371
+ fc.constant({ message: 'authentication failed for user', code: '28P01' }),
372
+ fc.constant({ message: 'syntax error at or near "CREAT"', code: '42601' }),
373
+ fc.constant({ message: 'relation "users" does not exist', code: '42P01' }),
374
+ ),
375
+ async (error) => {
376
+ // Format the error
377
+ const formattedError = formatError(error);
378
+
379
+ // Verify appropriate troubleshooting guidance
380
+ if (error.message.includes('timeout')) {
381
+ expect(formattedError).toMatch(/timeout.*network.*connectivity/i);
382
+ } else if (error.message.includes('authentication')) {
383
+ expect(formattedError).toMatch(/authentication.*credentials/i);
384
+ } else if (error.code === '42601') {
385
+ expect(formattedError).toMatch(/syntax.*review.*SQL/i);
386
+ } else if (error.code === '42P01') {
387
+ expect(formattedError).toMatch(/table.*not exist.*order/i);
388
+ }
389
+ }
390
+ ),
391
+ PROPERTY_CONFIG
392
+ );
393
+ });
394
+
395
+ /**
396
+ * Feature: automated-drizzle-migrations, Property 10: Log output completeness
397
+ * Validates: Requirements 3.1, 3.2, 3.3, 3.5
398
+ *
399
+ * For any migration execution, the log output should contain the branch name,
400
+ * commit SHA, count of applied migrations, and execution status for each
401
+ * migration file processed.
402
+ */
403
+ test('Property 10: Log output completeness - migration context is logged', async () => {
404
+ await fc.assert(
405
+ fc.asyncProperty(
406
+ branchNameArb,
407
+ commitShaArb,
408
+ environmentArb,
409
+ async (branch, commit, environment) => {
410
+ // Clear previous logs
411
+ consoleLogSpy.mockClear();
412
+
413
+ // Log migration context
414
+ logMigrationContext(branch, commit, environment);
415
+
416
+ // Get all log messages
417
+ const logMessages = getAllLogMessages(consoleLogSpy);
418
+ const combinedLog = logMessages.join('\n');
419
+
420
+ // Verify branch name is logged
421
+ expect(combinedLog).toContain(`Branch: ${branch}`);
422
+
423
+ // Verify commit SHA is logged (at least first 7 characters)
424
+ expect(combinedLog).toContain(`Commit: ${commit.substring(0, 7)}`);
425
+
426
+ // Verify environment is logged
427
+ expect(combinedLog).toContain(`Environment: ${environment}`);
428
+
429
+ // Verify context header is present
430
+ expect(combinedLog).toContain('Migration Context');
431
+ }
432
+ ),
433
+ PROPERTY_CONFIG
434
+ );
435
+ });
436
+
437
+ /**
438
+ * Property 10 (commit truncation): Commit SHA is consistently truncated to 7 characters
439
+ */
440
+ test('Property 10: Log output completeness - commit SHA truncation is consistent', async () => {
441
+ await fc.assert(
442
+ fc.asyncProperty(
443
+ branchNameArb,
444
+ commitShaArb,
445
+ environmentArb,
446
+ async (branch, commit, environment) => {
447
+ // Clear previous logs
448
+ consoleLogSpy.mockClear();
449
+
450
+ // Log migration context
451
+ logMigrationContext(branch, commit, environment);
452
+
453
+ // Get all log messages
454
+ const logMessages = getAllLogMessages(consoleLogSpy);
455
+ const combinedLog = logMessages.join('\n');
456
+
457
+ // Extract the logged commit
458
+ const commitMatch = combinedLog.match(/Commit: ([a-f0-9]+)/);
459
+ expect(commitMatch).toBeTruthy();
460
+
461
+ if (commitMatch) {
462
+ const loggedCommit = commitMatch[1];
463
+
464
+ // Verify it's exactly 7 characters
465
+ expect(loggedCommit.length).toBe(7);
466
+
467
+ // Verify it matches the first 7 characters of the original
468
+ expect(loggedCommit).toBe(commit.substring(0, 7));
469
+ }
470
+ }
471
+ ),
472
+ PROPERTY_CONFIG
473
+ );
474
+ });
475
+
476
+ /**
477
+ * Property 10 (environment mapping): Branch names map to correct environments
478
+ */
479
+ test('Property 10: Log output completeness - branch to environment mapping', async () => {
480
+ await fc.assert(
481
+ fc.asyncProperty(
482
+ branchNameArb,
483
+ commitShaArb,
484
+ async (branch, commit) => {
485
+ // Determine expected environment based on branch
486
+ const expectedEnvironment =
487
+ branch === 'main' ? 'Production' :
488
+ branch === 'develop' ? 'Preview' :
489
+ 'Development';
490
+
491
+ // Clear previous logs
492
+ consoleLogSpy.mockClear();
493
+
494
+ // Log migration context
495
+ logMigrationContext(branch, commit, expectedEnvironment);
496
+
497
+ // Get all log messages
498
+ const logMessages = getAllLogMessages(consoleLogSpy);
499
+ const combinedLog = logMessages.join('\n');
500
+
501
+ // Verify correct environment is logged
502
+ expect(combinedLog).toContain(`Environment: ${expectedEnvironment}`);
503
+ }
504
+ ),
505
+ PROPERTY_CONFIG
506
+ );
507
+ });
508
+
509
+ /**
510
+ * Feature: automated-drizzle-migrations, Property 4: Environment isolation
511
+ * Validates: Requirements 6.1, 6.2, 6.3, 6.4
512
+ *
513
+ * For any push to the develop branch, migrations should only affect the dev database,
514
+ * and for any push to the main branch, migrations should only affect the prod database,
515
+ * with no cross-contamination.
516
+ */
517
+ test('Property 4: Environment isolation - branch determines database target', async () => {
518
+ await fc.assert(
519
+ fc.asyncProperty(
520
+ fc.constantFrom('main', 'develop'),
521
+ validPostgresUrlArb,
522
+ validPostgresUrlArb,
523
+ async (branch, devUrl, prodUrl) => {
524
+ // Ensure dev and prod URLs are different
525
+ fc.pre(devUrl !== prodUrl);
526
+
527
+ // Determine which URL should be used based on branch
528
+ const expectedUrl = branch === 'main' ? prodUrl : devUrl;
529
+ const unexpectedUrl = branch === 'main' ? devUrl : prodUrl;
530
+
531
+ // Simulate environment variable selection based on branch
532
+ const selectedUrl = branch === 'main' ? prodUrl : devUrl;
533
+
534
+ // Verify correct URL is selected
535
+ expect(selectedUrl).toBe(expectedUrl);
536
+ expect(selectedUrl).not.toBe(unexpectedUrl);
537
+
538
+ // Verify URL validation passes for selected URL
539
+ const result = validateEnvironment(selectedUrl);
540
+ expect(result.valid).toBe(true);
541
+ expect(result.url).toBe(expectedUrl);
542
+
543
+ // Verify the URL contains expected database identifier
544
+ if (branch === 'main') {
545
+ // Production should use PROD_DIRECT_URL
546
+ expect(selectedUrl).toBe(prodUrl);
547
+ } else {
548
+ // Develop/feature branches should use DEV_DIRECT_URL
549
+ expect(selectedUrl).toBe(devUrl);
550
+ }
551
+ }
552
+ ),
553
+ PROPERTY_CONFIG
554
+ );
555
+ });
556
+
557
+ /**
558
+ * Property 4 (feature branches): Feature branches use dev database
559
+ */
560
+ test('Property 4: Environment isolation - feature branches use dev database', async () => {
561
+ await fc.assert(
562
+ fc.asyncProperty(
563
+ fc.stringMatching(/^feature\/[a-z0-9-]{1,30}$/),
564
+ validPostgresUrlArb,
565
+ validPostgresUrlArb,
566
+ async (featureBranch, devUrl, prodUrl) => {
567
+ // Ensure dev and prod URLs are different
568
+ fc.pre(devUrl !== prodUrl);
569
+
570
+ // Feature branches should always use dev database
571
+ const selectedUrl = devUrl; // In workflow, non-main branches use DEV_DIRECT_URL
572
+
573
+ // Verify dev URL is selected
574
+ expect(selectedUrl).toBe(devUrl);
575
+ expect(selectedUrl).not.toBe(prodUrl);
576
+
577
+ // Verify URL validation passes
578
+ const result = validateEnvironment(selectedUrl);
579
+ expect(result.valid).toBe(true);
580
+ }
581
+ ),
582
+ PROPERTY_CONFIG
583
+ );
584
+ });
585
+
586
+ /**
587
+ * Property 4 (environment secrets): Different environments use different secrets
588
+ */
589
+ test('Property 4: Environment isolation - environments use distinct secrets', async () => {
590
+ await fc.assert(
591
+ fc.asyncProperty(
592
+ validPostgresUrlArb,
593
+ validPostgresUrlArb,
594
+ async (devUrl, prodUrl) => {
595
+ // Ensure URLs are different (representing different databases)
596
+ fc.pre(devUrl !== prodUrl);
597
+
598
+ // Simulate GitHub environment secrets
599
+ const previewSecret = devUrl;
600
+ const productionSecret = prodUrl;
601
+
602
+ // Verify secrets are distinct
603
+ expect(previewSecret).not.toBe(productionSecret);
604
+
605
+ // Verify both secrets are valid
606
+ const devResult = validateEnvironment(previewSecret);
607
+ const prodResult = validateEnvironment(productionSecret);
608
+
609
+ expect(devResult.valid).toBe(true);
610
+ expect(prodResult.valid).toBe(true);
611
+
612
+ // Verify they point to different databases
613
+ expect(devResult.url).not.toBe(prodResult.url);
614
+ }
615
+ ),
616
+ PROPERTY_CONFIG
617
+ );
618
+ });
619
+
620
+ /**
621
+ * Feature: automated-drizzle-migrations, Property 5: Deployment blocking on failure
622
+ * Validates: Requirements 1.5, 5.3
623
+ *
624
+ * For any migration execution that fails, the subsequent Vercel deployment step
625
+ * should not execute, ensuring that application code is never deployed with an
626
+ * incompatible database schema.
627
+ */
628
+ test('Property 5: Deployment blocking on failure - failed migrations prevent deployment', async () => {
629
+ await fc.assert(
630
+ fc.asyncProperty(
631
+ fc.integer({ min: 1, max: 10 }),
632
+ fc.boolean(),
633
+ async (exitCode, migrationSuccess) => {
634
+ // Simulate migration execution result
635
+ const migrationExitCode = migrationSuccess ? 0 : exitCode;
636
+
637
+ // Verify exit code indicates success or failure
638
+ const shouldDeploy = migrationExitCode === 0;
639
+
640
+ if (migrationSuccess) {
641
+ // Successful migration should allow deployment
642
+ expect(shouldDeploy).toBe(true);
643
+ expect(migrationExitCode).toBe(0);
644
+ } else {
645
+ // Failed migration should block deployment
646
+ expect(shouldDeploy).toBe(false);
647
+ expect(migrationExitCode).not.toBe(0);
648
+ expect(migrationExitCode).toBeGreaterThan(0);
649
+ }
650
+ }
651
+ ),
652
+ PROPERTY_CONFIG
653
+ );
654
+ });
655
+
656
+ /**
657
+ * Property 5 (workflow dependency): Deployment job depends on migration success
658
+ */
659
+ test('Property 5: Deployment blocking on failure - deployment requires migration success', async () => {
660
+ await fc.assert(
661
+ fc.asyncProperty(
662
+ fc.constantFrom('success', 'failure', 'cancelled'),
663
+ async (migrationStatus) => {
664
+ // Simulate workflow job status
665
+ const migrationJobStatus = migrationStatus;
666
+
667
+ // Determine if deployment should proceed
668
+ const shouldProceedToDeployment = migrationJobStatus === 'success';
669
+
670
+ // Verify deployment only proceeds on success
671
+ if (migrationStatus === 'success') {
672
+ expect(shouldProceedToDeployment).toBe(true);
673
+ } else {
674
+ expect(shouldProceedToDeployment).toBe(false);
675
+ }
676
+ }
677
+ ),
678
+ PROPERTY_CONFIG
679
+ );
680
+ });
681
+
682
+ /**
683
+ * Property 5 (error propagation): Migration errors propagate to workflow
684
+ */
685
+ test('Property 5: Deployment blocking on failure - errors propagate correctly', async () => {
686
+ await fc.assert(
687
+ fc.asyncProperty(
688
+ postgresErrorCodeArb,
689
+ fc.string({ minLength: 10, maxLength: 100 }),
690
+ async (errorCode, errorMessage) => {
691
+ // Simulate a migration error
692
+ const error = {
693
+ message: errorMessage,
694
+ code: errorCode,
695
+ };
696
+
697
+ // Format the error
698
+ const formattedError = formatError(error, 'Migration execution');
699
+
700
+ // Verify error is properly formatted for GitHub Actions
701
+ expect(formattedError).toBeTruthy();
702
+ expect(formattedError.length).toBeGreaterThan(0);
703
+
704
+ // Verify error contains diagnostic information
705
+ expect(formattedError).toContain('Error:');
706
+ expect(formattedError).toContain('SQL State:');
707
+ expect(formattedError).toContain('Troubleshooting:');
708
+
709
+ // Simulate exit code for error
710
+ const exitCode = 1;
711
+
712
+ // Verify non-zero exit code blocks deployment
713
+ expect(exitCode).not.toBe(0);
714
+ expect(exitCode).toBeGreaterThan(0);
715
+ }
716
+ ),
717
+ PROPERTY_CONFIG
718
+ );
719
+ });
720
+
721
+ /**
722
+ * Feature: automated-drizzle-migrations, Property 1: Migration idempotency
723
+ * Validates: Requirements 4.2, 4.3, 4.4
724
+ *
725
+ * For any set of migration files and any database state, running the migration system
726
+ * multiple times should result in the same final database schema, with previously
727
+ * applied migrations being skipped.
728
+ */
729
+ test('Property 1: Migration idempotency - running migrations multiple times produces same result', async () => {
730
+ await fc.assert(
731
+ fc.asyncProperty(
732
+ fc.array(
733
+ fc.record({
734
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
735
+ hash: hexStringArb(32),
736
+ appliedAt: fc.date({ min: new Date('2020-01-01'), max: new Date() }),
737
+ }),
738
+ { minLength: 0, maxLength: 10 }
739
+ ),
740
+ fc.array(
741
+ fc.record({
742
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
743
+ hash: hexStringArb(32),
744
+ }),
745
+ { minLength: 0, maxLength: 10 }
746
+ ),
747
+ async (appliedMigrations, pendingMigrations) => {
748
+ // Simulate first migration run
749
+ const firstRunApplied = new Set(appliedMigrations.map(m => m.hash));
750
+ const firstRunPending = pendingMigrations.filter(m => !firstRunApplied.has(m.hash));
751
+
752
+ // After first run, these migrations are now applied
753
+ const afterFirstRun = new Set([...firstRunApplied, ...firstRunPending.map(m => m.hash)]);
754
+
755
+ // Simulate second migration run with same migration files
756
+ const secondRunApplied = afterFirstRun;
757
+ const secondRunPending = pendingMigrations.filter(m => !secondRunApplied.has(m.hash));
758
+
759
+ // After second run, no new migrations should be applied
760
+ const afterSecondRun = new Set([...secondRunApplied, ...secondRunPending.map(m => m.hash)]);
761
+
762
+ // Verify idempotency: second run applies no new migrations
763
+ expect(secondRunPending.length).toBe(0);
764
+ expect(afterSecondRun.size).toBe(afterFirstRun.size);
765
+
766
+ // Verify all migrations from first run are still applied
767
+ firstRunPending.forEach(m => {
768
+ expect(afterSecondRun.has(m.hash)).toBe(true);
769
+ });
770
+
771
+ // Verify the final state is identical
772
+ expect(Array.from(afterSecondRun).sort()).toEqual(Array.from(afterFirstRun).sort());
773
+ }
774
+ ),
775
+ PROPERTY_CONFIG
776
+ );
777
+ });
778
+
779
+ /**
780
+ * Property 1 (skip logic): Already-applied migrations are skipped
781
+ */
782
+ test('Property 1: Migration idempotency - already-applied migrations are skipped', async () => {
783
+ await fc.assert(
784
+ fc.asyncProperty(
785
+ fc.array(
786
+ fc.record({
787
+ hash: hexStringArb(32),
788
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
789
+ }),
790
+ { minLength: 1, maxLength: 5 }
791
+ ),
792
+ async (migrations) => {
793
+ // Simulate all migrations already applied
794
+ const appliedHashes = new Set(migrations.map(m => m.hash));
795
+
796
+ // Attempt to run migrations again
797
+ const pendingMigrations = migrations.filter(m => !appliedHashes.has(m.hash));
798
+
799
+ // Verify no migrations are pending
800
+ expect(pendingMigrations.length).toBe(0);
801
+
802
+ // Verify all migrations are marked as applied
803
+ migrations.forEach(m => {
804
+ expect(appliedHashes.has(m.hash)).toBe(true);
805
+ });
806
+ }
807
+ ),
808
+ PROPERTY_CONFIG
809
+ );
810
+ });
811
+
812
+ /**
813
+ * Property 1 (tracking consistency): Migration tracking table accurately reflects applied migrations
814
+ */
815
+ test('Property 1: Migration idempotency - tracking table reflects applied state', async () => {
816
+ await fc.assert(
817
+ fc.asyncProperty(
818
+ fc.array(
819
+ fc.record({
820
+ hash: hexStringArb(32),
821
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
822
+ appliedAt: fc.integer({ min: 1000000000000, max: 9999999999999 }), // Unix timestamp in ms
823
+ }),
824
+ { minLength: 0, maxLength: 10 }
825
+ ),
826
+ fc.array(
827
+ fc.record({
828
+ hash: hexStringArb(32),
829
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
830
+ }),
831
+ { minLength: 0, maxLength: 5 }
832
+ ),
833
+ async (trackingTableEntries, newMigrations) => {
834
+ // Ensure new migrations don't overlap with tracking table
835
+ const appliedHashes = new Set(trackingTableEntries.map(m => m.hash));
836
+ const uniqueNewMigrations = newMigrations.filter(m => !appliedHashes.has(m.hash));
837
+
838
+ // Simulate applying new migrations
839
+ const afterApply = [
840
+ ...trackingTableEntries,
841
+ ...uniqueNewMigrations.map(m => ({
842
+ hash: m.hash,
843
+ filename: m.filename,
844
+ appliedAt: Date.now(),
845
+ })),
846
+ ];
847
+
848
+ // Verify tracking table contains all applied migrations
849
+ expect(afterApply.length).toBe(trackingTableEntries.length + uniqueNewMigrations.length);
850
+
851
+ // Verify each applied migration has a tracking entry
852
+ uniqueNewMigrations.forEach(m => {
853
+ const tracked = afterApply.find(t => t.hash === m.hash);
854
+ expect(tracked).toBeDefined();
855
+ expect(tracked?.hash).toBe(m.hash);
856
+ });
857
+
858
+ // Verify original tracking entries are preserved
859
+ trackingTableEntries.forEach(original => {
860
+ const preserved = afterApply.find(t => t.hash === original.hash);
861
+ expect(preserved).toBeDefined();
862
+ expect(preserved?.appliedAt).toBe(original.appliedAt);
863
+ });
864
+ }
865
+ ),
866
+ PROPERTY_CONFIG
867
+ );
868
+ });
869
+
870
+ /**
871
+ * Feature: automated-drizzle-migrations, Property 2: Migration ordering consistency
872
+ * Validates: Requirements 1.3
873
+ *
874
+ * For any set of migration files, the migration system should always execute them
875
+ * in the same chronological order based on their timestamp prefixes, regardless
876
+ * of filesystem ordering.
877
+ */
878
+ test('Property 2: Migration ordering consistency - migrations execute in timestamp order', async () => {
879
+ await fc.assert(
880
+ fc.asyncProperty(
881
+ fc.array(
882
+ fc.record({
883
+ filename: fc.nat({ max: 9999 }).map(n => `${String(n).padStart(4, '0')}_migration.sql`),
884
+ timestamp: fc.nat({ max: 9999 }),
885
+ }),
886
+ { minLength: 2, maxLength: 10 }
887
+ ),
888
+ async (migrations) => {
889
+ // Extract timestamps from filenames
890
+ const migrationsWithTimestamps = migrations.map(m => {
891
+ const match = m.filename.match(/^(\d{4})_/);
892
+ const timestamp = match ? parseInt(match[1], 10) : 0;
893
+ return { ...m, extractedTimestamp: timestamp };
894
+ });
895
+
896
+ // Sort by timestamp (simulating migration system behavior)
897
+ const sorted = [...migrationsWithTimestamps].sort((a, b) =>
898
+ a.extractedTimestamp - b.extractedTimestamp
899
+ );
900
+
901
+ // Verify sorted order is chronological
902
+ for (let i = 1; i < sorted.length; i++) {
903
+ expect(sorted[i].extractedTimestamp).toBeGreaterThanOrEqual(sorted[i - 1].extractedTimestamp);
904
+ }
905
+
906
+ // Verify the order is deterministic (same input produces same output)
907
+ const sortedAgain = [...migrationsWithTimestamps].sort((a, b) =>
908
+ a.extractedTimestamp - b.extractedTimestamp
909
+ );
910
+
911
+ expect(sortedAgain.map(m => m.filename)).toEqual(sorted.map(m => m.filename));
912
+ }
913
+ ),
914
+ PROPERTY_CONFIG
915
+ );
916
+ });
917
+
918
+ /**
919
+ * Property 2 (filesystem independence): Order is independent of filesystem listing
920
+ */
921
+ test('Property 2: Migration ordering consistency - order independent of filesystem', async () => {
922
+ await fc.assert(
923
+ fc.asyncProperty(
924
+ fc.array(
925
+ fc.nat({ max: 9999 }).map(n => ({
926
+ filename: `${String(n).padStart(4, '0')}_migration.sql`,
927
+ timestamp: n,
928
+ })),
929
+ { minLength: 2, maxLength: 10 }
930
+ ),
931
+ async (migrations) => {
932
+ // Shuffle to simulate different filesystem orderings
933
+ const shuffled1 = [...migrations].sort(() => Math.random() - 0.5);
934
+ const shuffled2 = [...migrations].sort(() => Math.random() - 0.5);
935
+
936
+ // Sort both by timestamp
937
+ const sorted1 = [...shuffled1].sort((a, b) => a.timestamp - b.timestamp);
938
+ const sorted2 = [...shuffled2].sort((a, b) => a.timestamp - b.timestamp);
939
+
940
+ // Verify both produce the same order
941
+ expect(sorted1.map(m => m.filename)).toEqual(sorted2.map(m => m.filename));
942
+
943
+ // Verify order matches timestamp order
944
+ for (let i = 1; i < sorted1.length; i++) {
945
+ expect(sorted1[i].timestamp).toBeGreaterThanOrEqual(sorted1[i - 1].timestamp);
946
+ }
947
+ }
948
+ ),
949
+ PROPERTY_CONFIG
950
+ );
951
+ });
952
+
953
+ /**
954
+ * Property 2 (timestamp extraction): Timestamps are correctly extracted from filenames
955
+ */
956
+ test('Property 2: Migration ordering consistency - timestamp extraction is correct', async () => {
957
+ await fc.assert(
958
+ fc.asyncProperty(
959
+ fc.nat({ max: 9999 }),
960
+ fc.stringMatching(/^[a-z_]{1,30}$/),
961
+ async (timestamp, description) => {
962
+ // Create filename with timestamp
963
+ const filename = `${String(timestamp).padStart(4, '0')}_${description}.sql`;
964
+
965
+ // Extract timestamp (simulating migration system)
966
+ const match = filename.match(/^(\d{4})_/);
967
+ expect(match).toBeTruthy();
968
+
969
+ if (match) {
970
+ const extractedTimestamp = parseInt(match[1], 10);
971
+
972
+ // Verify extracted timestamp matches original
973
+ expect(extractedTimestamp).toBe(timestamp);
974
+
975
+ // Verify it's a valid number
976
+ expect(Number.isInteger(extractedTimestamp)).toBe(true);
977
+ expect(extractedTimestamp).toBeGreaterThanOrEqual(0);
978
+ expect(extractedTimestamp).toBeLessThanOrEqual(9999);
979
+ }
980
+ }
981
+ ),
982
+ PROPERTY_CONFIG
983
+ );
984
+ });
985
+
986
+ /**
987
+ * Feature: automated-drizzle-migrations, Property 6: Migration tracking consistency
988
+ * Validates: Requirements 4.5, 9.2, 9.5
989
+ *
990
+ * For any successfully applied migration, the migration tracking table should contain
991
+ * exactly one entry for that migration, and for any failed migration, the tracking
992
+ * table should not contain an entry.
993
+ */
994
+ test('Property 6: Migration tracking consistency - successful migrations recorded once', async () => {
995
+ await fc.assert(
996
+ fc.asyncProperty(
997
+ fc.array(
998
+ fc.record({
999
+ hash: hexStringArb(32),
1000
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1001
+ success: fc.constant(true),
1002
+ }),
1003
+ { minLength: 1, maxLength: 10 }
1004
+ ),
1005
+ async (successfulMigrations) => {
1006
+ // Simulate tracking table after successful migrations
1007
+ const trackingTable = successfulMigrations.map(m => ({
1008
+ hash: m.hash,
1009
+ created_at: Date.now(),
1010
+ }));
1011
+
1012
+ // Verify each successful migration has exactly one entry
1013
+ successfulMigrations.forEach(migration => {
1014
+ const entries = trackingTable.filter(t => t.hash === migration.hash);
1015
+ expect(entries.length).toBe(1);
1016
+ expect(entries[0].hash).toBe(migration.hash);
1017
+ });
1018
+
1019
+ // Verify tracking table size matches number of successful migrations
1020
+ expect(trackingTable.length).toBe(successfulMigrations.length);
1021
+
1022
+ // Verify no duplicate entries
1023
+ const uniqueHashes = new Set(trackingTable.map(t => t.hash));
1024
+ expect(uniqueHashes.size).toBe(trackingTable.length);
1025
+ }
1026
+ ),
1027
+ PROPERTY_CONFIG
1028
+ );
1029
+ });
1030
+
1031
+ /**
1032
+ * Property 6 (failed migrations): Failed migrations are not recorded
1033
+ */
1034
+ test('Property 6: Migration tracking consistency - failed migrations not recorded', async () => {
1035
+ await fc.assert(
1036
+ fc.asyncProperty(
1037
+ fc.array(
1038
+ fc.record({
1039
+ hash: hexStringArb(32),
1040
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1041
+ success: fc.boolean(),
1042
+ }),
1043
+ { minLength: 1, maxLength: 10 }
1044
+ ),
1045
+ async (migrations) => {
1046
+ // Simulate tracking table with only successful migrations
1047
+ const trackingTable = migrations
1048
+ .filter(m => m.success)
1049
+ .map(m => ({
1050
+ hash: m.hash,
1051
+ created_at: Date.now(),
1052
+ }));
1053
+
1054
+ // Verify successful migrations are recorded
1055
+ const successfulMigrations = migrations.filter(m => m.success);
1056
+ successfulMigrations.forEach(migration => {
1057
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1058
+ expect(entry).toBeDefined();
1059
+ expect(entry?.hash).toBe(migration.hash);
1060
+ });
1061
+
1062
+ // Verify failed migrations are NOT recorded
1063
+ const failedMigrations = migrations.filter(m => !m.success);
1064
+ failedMigrations.forEach(migration => {
1065
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1066
+ expect(entry).toBeUndefined();
1067
+ });
1068
+
1069
+ // Verify tracking table only contains successful migrations
1070
+ expect(trackingTable.length).toBe(successfulMigrations.length);
1071
+ }
1072
+ ),
1073
+ PROPERTY_CONFIG
1074
+ );
1075
+ });
1076
+
1077
+ /**
1078
+ * Property 6 (rollback consistency): Rolled back migrations don't appear in tracking
1079
+ */
1080
+ test('Property 6: Migration tracking consistency - rollback prevents tracking entry', async () => {
1081
+ await fc.assert(
1082
+ fc.asyncProperty(
1083
+ fc.array(
1084
+ fc.record({
1085
+ hash: hexStringArb(32),
1086
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1087
+ }),
1088
+ { minLength: 1, maxLength: 5 }
1089
+ ),
1090
+ fc.integer({ min: 0, max: 4 }),
1091
+ async (migrations, failureIndex) => {
1092
+ // Ensure failure index is valid
1093
+ fc.pre(failureIndex < migrations.length);
1094
+
1095
+ // Simulate migrations up to failure point
1096
+ const appliedBeforeFailure = migrations.slice(0, failureIndex);
1097
+ const failedMigration = migrations[failureIndex];
1098
+ const notAttempted = migrations.slice(failureIndex + 1);
1099
+
1100
+ // Simulate tracking table (only contains migrations before failure)
1101
+ const trackingTable = appliedBeforeFailure.map(m => ({
1102
+ hash: m.hash,
1103
+ created_at: Date.now(),
1104
+ }));
1105
+
1106
+ // Verify migrations before failure are tracked
1107
+ appliedBeforeFailure.forEach(migration => {
1108
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1109
+ expect(entry).toBeDefined();
1110
+ });
1111
+
1112
+ // Verify failed migration is NOT tracked (rolled back)
1113
+ const failedEntry = trackingTable.find(t => t.hash === failedMigration.hash);
1114
+ expect(failedEntry).toBeUndefined();
1115
+
1116
+ // Verify migrations after failure are NOT tracked (not attempted)
1117
+ notAttempted.forEach(migration => {
1118
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1119
+ expect(entry).toBeUndefined();
1120
+ });
1121
+
1122
+ // Verify tracking table size
1123
+ expect(trackingTable.length).toBe(appliedBeforeFailure.length);
1124
+ }
1125
+ ),
1126
+ PROPERTY_CONFIG
1127
+ );
1128
+ });
1129
+
1130
+ /**
1131
+ * Property 6 (tracking atomicity): Tracking entry is atomic with migration success
1132
+ */
1133
+ test('Property 6: Migration tracking consistency - tracking is atomic with migration', async () => {
1134
+ await fc.assert(
1135
+ fc.asyncProperty(
1136
+ fc.record({
1137
+ hash: hexStringArb(32),
1138
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1139
+ migrationSuccess: fc.boolean(),
1140
+ trackingSuccess: fc.boolean(),
1141
+ }),
1142
+ async ({ hash, filename, migrationSuccess, trackingSuccess }) => {
1143
+ // Simulate transaction behavior: both must succeed or both must fail
1144
+ const transactionSuccess = migrationSuccess && trackingSuccess;
1145
+
1146
+ // Simulate tracking table state after transaction
1147
+ const trackingTable: Array<{ hash: string; created_at: number }> = [];
1148
+
1149
+ if (transactionSuccess) {
1150
+ // Both migration and tracking succeeded
1151
+ trackingTable.push({ hash, created_at: Date.now() });
1152
+ }
1153
+ // If either failed, tracking table remains empty (rollback)
1154
+
1155
+ // Verify atomicity
1156
+ if (migrationSuccess && trackingSuccess) {
1157
+ // Both succeeded: tracking entry exists
1158
+ expect(trackingTable.length).toBe(1);
1159
+ expect(trackingTable[0].hash).toBe(hash);
1160
+ } else {
1161
+ // Either failed: no tracking entry (rollback)
1162
+ expect(trackingTable.length).toBe(0);
1163
+ }
1164
+ }
1165
+ ),
1166
+ PROPERTY_CONFIG
1167
+ );
1168
+ });
1169
+
1170
+ /**
1171
+ * Feature: automated-drizzle-migrations, Property 3: Transaction atomicity per migration
1172
+ * Validates: Requirements 9.1, 9.3, 9.4
1173
+ *
1174
+ * For any individual migration file, if the migration fails at any point during execution,
1175
+ * the database should return to its exact pre-migration state with no partial changes applied.
1176
+ */
1177
+ test('Property 3: Transaction atomicity per migration - failed migrations rollback completely', async () => {
1178
+ await fc.assert(
1179
+ fc.asyncProperty(
1180
+ fc.record({
1181
+ migrationHash: hexStringArb(32),
1182
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1183
+ preMigrationState: fc.record({
1184
+ tableCount: fc.integer({ min: 0, max: 50 }),
1185
+ rowCounts: fc.array(fc.integer({ min: 0, max: 1000 }), { maxLength: 10 }),
1186
+ constraints: fc.array(fc.string({ minLength: 5, maxLength: 20 }), { maxLength: 10 }),
1187
+ }),
1188
+ migrationOperations: fc.array(
1189
+ fc.record({
1190
+ type: fc.constantFrom('CREATE_TABLE', 'INSERT', 'ALTER_TABLE', 'CREATE_INDEX'),
1191
+ success: fc.boolean(),
1192
+ }),
1193
+ { minLength: 1, maxLength: 10 }
1194
+ ),
1195
+ }),
1196
+ async ({ migrationHash, filename, preMigrationState, migrationOperations }) => {
1197
+ // Determine if migration succeeds (all operations must succeed)
1198
+ const migrationSucceeds = migrationOperations.every(op => op.success);
1199
+
1200
+ // Simulate database state after migration attempt
1201
+ let postMigrationState;
1202
+
1203
+ if (migrationSucceeds) {
1204
+ // All operations succeeded: state changes are committed
1205
+ postMigrationState = {
1206
+ tableCount: preMigrationState.tableCount + migrationOperations.filter(op => op.type === 'CREATE_TABLE').length,
1207
+ rowCounts: [...preMigrationState.rowCounts],
1208
+ constraints: [...preMigrationState.constraints],
1209
+ };
1210
+
1211
+ // Add rows from INSERT operations
1212
+ const insertCount = migrationOperations.filter(op => op.type === 'INSERT').length;
1213
+ if (insertCount > 0 && postMigrationState.rowCounts.length > 0) {
1214
+ postMigrationState.rowCounts[0] += insertCount;
1215
+ }
1216
+ } else {
1217
+ // At least one operation failed: transaction rolled back
1218
+ // Database state should be identical to pre-migration state
1219
+ postMigrationState = {
1220
+ tableCount: preMigrationState.tableCount,
1221
+ rowCounts: [...preMigrationState.rowCounts],
1222
+ constraints: [...preMigrationState.constraints],
1223
+ };
1224
+ }
1225
+
1226
+ // Verify atomicity property
1227
+ if (migrationSucceeds) {
1228
+ // Success: state should have changed
1229
+ const stateChanged =
1230
+ postMigrationState.tableCount !== preMigrationState.tableCount ||
1231
+ postMigrationState.rowCounts.some((count, idx) => count !== preMigrationState.rowCounts[idx]);
1232
+
1233
+ // If there were operations that modify state, state should change
1234
+ const hasCreateTable = migrationOperations.some(op => op.type === 'CREATE_TABLE');
1235
+ const hasInsert = migrationOperations.some(op => op.type === 'INSERT') && preMigrationState.rowCounts.length > 0;
1236
+ const hasStateModifyingOps = hasCreateTable || hasInsert;
1237
+
1238
+ if (hasStateModifyingOps) {
1239
+ expect(stateChanged).toBe(true);
1240
+ }
1241
+ } else {
1242
+ // Failure: state must be identical to pre-migration (rollback)
1243
+ expect(postMigrationState.tableCount).toBe(preMigrationState.tableCount);
1244
+ expect(postMigrationState.rowCounts).toEqual(preMigrationState.rowCounts);
1245
+ expect(postMigrationState.constraints).toEqual(preMigrationState.constraints);
1246
+
1247
+ // Verify no partial changes
1248
+ expect(JSON.stringify(postMigrationState)).toBe(JSON.stringify(preMigrationState));
1249
+ }
1250
+ }
1251
+ ),
1252
+ PROPERTY_CONFIG
1253
+ );
1254
+ });
1255
+
1256
+ /**
1257
+ * Property 3 (partial failure): Partial failures trigger complete rollback
1258
+ */
1259
+ test('Property 3: Transaction atomicity - partial failures rollback all changes', async () => {
1260
+ await fc.assert(
1261
+ fc.asyncProperty(
1262
+ fc.array(
1263
+ fc.record({
1264
+ operation: fc.constantFrom('CREATE', 'INSERT', 'UPDATE', 'DELETE'),
1265
+ success: fc.boolean(),
1266
+ }),
1267
+ { minLength: 2, maxLength: 10 }
1268
+ ),
1269
+ fc.integer({ min: 0, max: 9 }),
1270
+ async (operations, failureIndex) => {
1271
+ // Ensure failure index is valid
1272
+ fc.pre(failureIndex < operations.length);
1273
+
1274
+ // Mark the operation at failureIndex as failed
1275
+ const operationsWithFailure = operations.map((op, idx) => ({
1276
+ ...op,
1277
+ success: idx < failureIndex ? true : idx === failureIndex ? false : op.success,
1278
+ }));
1279
+
1280
+ // Simulate transaction execution
1281
+ const migrationSucceeds = operationsWithFailure.every(op => op.success);
1282
+
1283
+ // Count successful operations before failure
1284
+ const successfulOpsBeforeFailure = operationsWithFailure.slice(0, failureIndex).length;
1285
+
1286
+ // Simulate database changes
1287
+ let appliedChanges = 0;
1288
+
1289
+ if (migrationSucceeds) {
1290
+ // All operations succeeded
1291
+ appliedChanges = operationsWithFailure.length;
1292
+ } else {
1293
+ // Transaction failed and rolled back
1294
+ appliedChanges = 0; // All changes rolled back, even successful ones
1295
+ }
1296
+
1297
+ // Verify rollback behavior
1298
+ if (!migrationSucceeds) {
1299
+ // Even though some operations succeeded before failure,
1300
+ // all changes should be rolled back
1301
+ expect(appliedChanges).toBe(0);
1302
+
1303
+ // If there were successful operations before failure, verify they were rolled back
1304
+ if (successfulOpsBeforeFailure > 0) {
1305
+ // Verify no partial state - successful ops before failure were also rolled back
1306
+ expect(appliedChanges).not.toBe(successfulOpsBeforeFailure);
1307
+ }
1308
+ } else {
1309
+ // All operations succeeded
1310
+ expect(appliedChanges).toBe(operations.length);
1311
+ }
1312
+ }
1313
+ ),
1314
+ PROPERTY_CONFIG
1315
+ );
1316
+ });
1317
+
1318
+ /**
1319
+ * Property 3 (tracking table consistency): Failed migrations don't update tracking table
1320
+ */
1321
+ test('Property 3: Transaction atomicity - tracking table not updated on failure', async () => {
1322
+ await fc.assert(
1323
+ fc.asyncProperty(
1324
+ fc.record({
1325
+ migrationHash: hexStringArb(32),
1326
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1327
+ migrationSuccess: fc.boolean(),
1328
+ sqlOperations: fc.array(
1329
+ fc.record({
1330
+ sql: fc.string({ minLength: 10, maxLength: 100 }),
1331
+ success: fc.boolean(),
1332
+ }),
1333
+ { minLength: 1, maxLength: 5 }
1334
+ ),
1335
+ }),
1336
+ async ({ migrationHash, filename, migrationSuccess, sqlOperations }) => {
1337
+ // Migration succeeds only if all SQL operations succeed
1338
+ const allOperationsSucceed = sqlOperations.every(op => op.success);
1339
+ const actualMigrationSuccess = migrationSuccess && allOperationsSucceed;
1340
+
1341
+ // Simulate tracking table state
1342
+ const trackingTableBefore: Array<{ hash: string; created_at: number }> = [];
1343
+ const trackingTableAfter: Array<{ hash: string; created_at: number }> = [];
1344
+
1345
+ // Only add to tracking table if migration succeeded
1346
+ if (actualMigrationSuccess) {
1347
+ trackingTableAfter.push({
1348
+ hash: migrationHash,
1349
+ created_at: Date.now(),
1350
+ });
1351
+ }
1352
+
1353
+ // Verify tracking table behavior
1354
+ if (actualMigrationSuccess) {
1355
+ // Success: tracking table should have new entry
1356
+ expect(trackingTableAfter.length).toBe(trackingTableBefore.length + 1);
1357
+ expect(trackingTableAfter.find(t => t.hash === migrationHash)).toBeDefined();
1358
+ } else {
1359
+ // Failure: tracking table should be unchanged (rollback)
1360
+ expect(trackingTableAfter.length).toBe(trackingTableBefore.length);
1361
+ expect(trackingTableAfter.find(t => t.hash === migrationHash)).toBeUndefined();
1362
+
1363
+ // Verify tracking table is identical to before
1364
+ expect(trackingTableAfter).toEqual(trackingTableBefore);
1365
+ }
1366
+ }
1367
+ ),
1368
+ PROPERTY_CONFIG
1369
+ );
1370
+ });
1371
+
1372
+ /**
1373
+ * Property 3 (transaction boundary): Each migration has its own transaction
1374
+ */
1375
+ test('Property 3: Transaction atomicity - each migration has independent transaction', async () => {
1376
+ await fc.assert(
1377
+ fc.asyncProperty(
1378
+ fc.array(
1379
+ fc.record({
1380
+ hash: hexStringArb(32),
1381
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1382
+ success: fc.boolean(),
1383
+ }),
1384
+ { minLength: 2, maxLength: 5 }
1385
+ ),
1386
+ async (migrations) => {
1387
+ // Simulate tracking table with independent transactions
1388
+ const trackingTable: Array<{ hash: string; created_at: number }> = [];
1389
+
1390
+ // Process each migration in its own transaction
1391
+ for (const migration of migrations) {
1392
+ if (migration.success) {
1393
+ // Transaction succeeded: add to tracking
1394
+ trackingTable.push({
1395
+ hash: migration.hash,
1396
+ created_at: Date.now(),
1397
+ });
1398
+ }
1399
+ // Transaction failed: don't add to tracking (rollback)
1400
+ // But continue with next migration (independent transaction)
1401
+ }
1402
+
1403
+ // Verify each successful migration is tracked
1404
+ const successfulMigrations = migrations.filter(m => m.success);
1405
+ expect(trackingTable.length).toBe(successfulMigrations.length);
1406
+
1407
+ successfulMigrations.forEach(migration => {
1408
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1409
+ expect(entry).toBeDefined();
1410
+ expect(entry?.hash).toBe(migration.hash);
1411
+ });
1412
+
1413
+ // Verify failed migrations are not tracked
1414
+ const failedMigrations = migrations.filter(m => !m.success);
1415
+ failedMigrations.forEach(migration => {
1416
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1417
+ expect(entry).toBeUndefined();
1418
+ });
1419
+
1420
+ // Verify independence: a failed migration doesn't affect previous successful ones
1421
+ if (failedMigrations.length > 0 && successfulMigrations.length > 0) {
1422
+ // Even with failures, successful migrations should still be tracked
1423
+ expect(trackingTable.length).toBeGreaterThan(0);
1424
+ }
1425
+ }
1426
+ ),
1427
+ PROPERTY_CONFIG
1428
+ );
1429
+ });
1430
+
1431
+ /**
1432
+ * Feature: automated-drizzle-migrations, Property 8: Batch migration consistency
1433
+ * Validates: Requirements 8.1, 8.2, 8.3
1434
+ *
1435
+ * For any set of multiple pending migrations, if migration N fails, then migrations
1436
+ * N+1 through N+M should not be executed, and migrations 1 through N-1 should remain
1437
+ * applied in the tracking table.
1438
+ */
1439
+ test('Property 8: Batch migration consistency - failure halts subsequent migrations', async () => {
1440
+ await fc.assert(
1441
+ fc.asyncProperty(
1442
+ fc.array(
1443
+ fc.record({
1444
+ hash: hexStringArb(32),
1445
+ filename: fc.nat({ max: 9999 }).map(n => `${String(n).padStart(4, '0')}_migration.sql`),
1446
+ timestamp: fc.nat({ max: 9999 }),
1447
+ success: fc.boolean(),
1448
+ }),
1449
+ { minLength: 2, maxLength: 10 }
1450
+ ),
1451
+ async (migrations) => {
1452
+ // Sort migrations by timestamp (chronological order)
1453
+ const sortedMigrations = [...migrations].sort((a, b) => a.timestamp - b.timestamp);
1454
+
1455
+ // Find first failure index
1456
+ const firstFailureIndex = sortedMigrations.findIndex(m => !m.success);
1457
+
1458
+ // Simulate batch execution with halt-on-failure
1459
+ const trackingTable: Array<{ hash: string; created_at: number }> = [];
1460
+ const executedMigrations: string[] = [];
1461
+
1462
+ for (let i = 0; i < sortedMigrations.length; i++) {
1463
+ const migration = sortedMigrations[i];
1464
+
1465
+ // Execute migration
1466
+ executedMigrations.push(migration.hash);
1467
+
1468
+ if (migration.success) {
1469
+ // Success: add to tracking table
1470
+ trackingTable.push({
1471
+ hash: migration.hash,
1472
+ created_at: Date.now(),
1473
+ });
1474
+ } else {
1475
+ // Failure: halt execution (don't process subsequent migrations)
1476
+ break;
1477
+ }
1478
+ }
1479
+
1480
+ // Verify halt-on-failure behavior
1481
+ if (firstFailureIndex === -1) {
1482
+ // No failures: all migrations should be executed and tracked
1483
+ expect(executedMigrations.length).toBe(sortedMigrations.length);
1484
+ expect(trackingTable.length).toBe(sortedMigrations.length);
1485
+
1486
+ // Verify all migrations are tracked
1487
+ sortedMigrations.forEach(migration => {
1488
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1489
+ expect(entry).toBeDefined();
1490
+ });
1491
+ } else {
1492
+ // Failure occurred: verify halt behavior
1493
+
1494
+ // Migrations before failure should be executed and tracked
1495
+ const migrationsBeforeFailure = sortedMigrations.slice(0, firstFailureIndex);
1496
+ migrationsBeforeFailure.forEach(migration => {
1497
+ expect(executedMigrations).toContain(migration.hash);
1498
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1499
+ expect(entry).toBeDefined();
1500
+ });
1501
+
1502
+ // Failed migration should be executed but NOT tracked
1503
+ const failedMigration = sortedMigrations[firstFailureIndex];
1504
+ expect(executedMigrations).toContain(failedMigration.hash);
1505
+ const failedEntry = trackingTable.find(t => t.hash === failedMigration.hash);
1506
+ expect(failedEntry).toBeUndefined();
1507
+
1508
+ // Migrations after failure should NOT be executed
1509
+ const migrationsAfterFailure = sortedMigrations.slice(firstFailureIndex + 1);
1510
+ migrationsAfterFailure.forEach(migration => {
1511
+ expect(executedMigrations).not.toContain(migration.hash);
1512
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1513
+ expect(entry).toBeUndefined();
1514
+ });
1515
+
1516
+ // Verify execution count
1517
+ expect(executedMigrations.length).toBe(firstFailureIndex + 1);
1518
+
1519
+ // Verify tracking table only contains successful migrations before failure
1520
+ expect(trackingTable.length).toBe(firstFailureIndex);
1521
+ }
1522
+ }
1523
+ ),
1524
+ PROPERTY_CONFIG
1525
+ );
1526
+ });
1527
+
1528
+ /**
1529
+ * Property 8 (ordered processing): Batch migrations execute in chronological order
1530
+ */
1531
+ test('Property 8: Batch migration consistency - migrations execute in order', async () => {
1532
+ await fc.assert(
1533
+ fc.asyncProperty(
1534
+ fc.array(
1535
+ fc.record({
1536
+ hash: hexStringArb(32),
1537
+ timestamp: fc.nat({ max: 9999 }),
1538
+ filename: fc.nat({ max: 9999 }).map(n => `${String(n).padStart(4, '0')}_migration.sql`),
1539
+ }),
1540
+ { minLength: 2, maxLength: 10 }
1541
+ ),
1542
+ async (migrations) => {
1543
+ // Sort by timestamp (simulating batch processing)
1544
+ const sortedMigrations = [...migrations].sort((a, b) => a.timestamp - b.timestamp);
1545
+
1546
+ // Simulate execution order
1547
+ const executionOrder: number[] = [];
1548
+ sortedMigrations.forEach(m => {
1549
+ executionOrder.push(m.timestamp);
1550
+ });
1551
+
1552
+ // Verify execution order is chronological
1553
+ for (let i = 1; i < executionOrder.length; i++) {
1554
+ expect(executionOrder[i]).toBeGreaterThanOrEqual(executionOrder[i - 1]);
1555
+ }
1556
+
1557
+ // Verify order matches sorted order
1558
+ expect(executionOrder).toEqual(sortedMigrations.map(m => m.timestamp));
1559
+ }
1560
+ ),
1561
+ PROPERTY_CONFIG
1562
+ );
1563
+ });
1564
+
1565
+ /**
1566
+ * Property 8 (partial success): Successful migrations before failure remain applied
1567
+ */
1568
+ test('Property 8: Batch migration consistency - partial success preserves completed migrations', async () => {
1569
+ await fc.assert(
1570
+ fc.asyncProperty(
1571
+ fc.integer({ min: 1, max: 5 }),
1572
+ fc.integer({ min: 1, max: 5 }),
1573
+ fc.array(hexStringArb(32), { minLength: 20, maxLength: 20 }), // Generate enough hashes
1574
+ async (successfulCount, remainingCount, hashes) => {
1575
+ // Ensure we have enough hashes
1576
+ const totalNeeded = successfulCount + 1 + remainingCount;
1577
+ fc.pre(hashes.length >= totalNeeded);
1578
+
1579
+ // Create batch with successful migrations followed by a failure
1580
+ const successfulMigrations = Array.from({ length: successfulCount }, (_, i) => ({
1581
+ hash: hashes[i],
1582
+ timestamp: i,
1583
+ filename: `${String(i).padStart(4, '0')}_migration.sql`,
1584
+ success: true,
1585
+ }));
1586
+
1587
+ const failedMigration = {
1588
+ hash: hashes[successfulCount],
1589
+ timestamp: successfulCount,
1590
+ filename: `${String(successfulCount).padStart(4, '0')}_migration.sql`,
1591
+ success: false,
1592
+ };
1593
+
1594
+ const notExecutedMigrations = Array.from({ length: remainingCount }, (_, i) => ({
1595
+ hash: hashes[successfulCount + 1 + i],
1596
+ timestamp: successfulCount + 1 + i,
1597
+ filename: `${String(successfulCount + 1 + i).padStart(4, '0')}_migration.sql`,
1598
+ success: true,
1599
+ }));
1600
+
1601
+ const allMigrations = [...successfulMigrations, failedMigration, ...notExecutedMigrations];
1602
+
1603
+ // Simulate batch execution with halt-on-failure
1604
+ const trackingTable: Array<{ hash: string; created_at: number }> = [];
1605
+
1606
+ for (const migration of allMigrations) {
1607
+ if (migration.success) {
1608
+ trackingTable.push({
1609
+ hash: migration.hash,
1610
+ created_at: Date.now(),
1611
+ });
1612
+ } else {
1613
+ // Halt on failure
1614
+ break;
1615
+ }
1616
+ }
1617
+
1618
+ // Verify successful migrations before failure are tracked
1619
+ expect(trackingTable.length).toBe(successfulCount);
1620
+
1621
+ successfulMigrations.forEach(migration => {
1622
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1623
+ expect(entry).toBeDefined();
1624
+ });
1625
+
1626
+ // Verify failed migration is not tracked
1627
+ const failedEntry = trackingTable.find(t => t.hash === failedMigration.hash);
1628
+ expect(failedEntry).toBeUndefined();
1629
+
1630
+ // Verify migrations after failure are not tracked
1631
+ notExecutedMigrations.forEach(migration => {
1632
+ const entry = trackingTable.find(t => t.hash === migration.hash);
1633
+ expect(entry).toBeUndefined();
1634
+ });
1635
+ }
1636
+ ),
1637
+ PROPERTY_CONFIG
1638
+ );
1639
+ });
1640
+
1641
+ /**
1642
+ * Property 8 (batch summary): Batch execution reports accurate counts
1643
+ */
1644
+ test('Property 8: Batch migration consistency - batch summary is accurate', async () => {
1645
+ await fc.assert(
1646
+ fc.asyncProperty(
1647
+ fc.array(
1648
+ fc.record({
1649
+ hash: hexStringArb(32),
1650
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1651
+ success: fc.boolean(),
1652
+ }),
1653
+ { minLength: 1, maxLength: 10 }
1654
+ ),
1655
+ fc.array(
1656
+ fc.record({
1657
+ hash: hexStringArb(32),
1658
+ filename: fc.stringMatching(/^\d{4}_[a-z_]{1,30}\.sql$/),
1659
+ }),
1660
+ { minLength: 0, maxLength: 5 }
1661
+ ),
1662
+ async (pendingMigrations, alreadyAppliedMigrations) => {
1663
+ // Simulate tracking table with already applied migrations
1664
+ const trackingTableBefore = alreadyAppliedMigrations.map(m => ({
1665
+ hash: m.hash,
1666
+ created_at: Date.now() - 1000000,
1667
+ }));
1668
+
1669
+ const appliedHashes = new Set(alreadyAppliedMigrations.map(m => m.hash));
1670
+
1671
+ // Filter out already applied migrations
1672
+ const actuallyPendingMigrations = pendingMigrations.filter(m => !appliedHashes.has(m.hash));
1673
+
1674
+ // Find first failure
1675
+ const firstFailureIndex = actuallyPendingMigrations.findIndex(m => !m.success);
1676
+
1677
+ // Simulate batch execution
1678
+ let migrationsApplied = 0;
1679
+ const migrationsSkipped = alreadyAppliedMigrations.length;
1680
+ let migrationsFailed = 0;
1681
+
1682
+ if (firstFailureIndex === -1) {
1683
+ // No failures: all pending migrations applied
1684
+ migrationsApplied = actuallyPendingMigrations.length;
1685
+ } else {
1686
+ // Failure occurred
1687
+ migrationsApplied = firstFailureIndex;
1688
+ migrationsFailed = 1;
1689
+ // Remaining migrations not attempted (not counted as skipped or failed)
1690
+ }
1691
+
1692
+ // Verify counts
1693
+ expect(migrationsApplied).toBeGreaterThanOrEqual(0);
1694
+ expect(migrationsSkipped).toBeGreaterThanOrEqual(0);
1695
+ expect(migrationsFailed).toBeGreaterThanOrEqual(0);
1696
+ expect(migrationsFailed).toBeLessThanOrEqual(1); // At most one failure (halt-on-failure)
1697
+
1698
+ // Verify total accounting
1699
+ const totalProcessed = migrationsApplied + migrationsSkipped + migrationsFailed;
1700
+ const totalAttempted = actuallyPendingMigrations.length + alreadyAppliedMigrations.length;
1701
+
1702
+ if (firstFailureIndex === -1) {
1703
+ // No failures: all migrations processed
1704
+ expect(totalProcessed).toBe(totalAttempted);
1705
+ } else {
1706
+ // Failure: only migrations up to and including failure are processed
1707
+ expect(migrationsApplied + migrationsFailed).toBe(firstFailureIndex + 1);
1708
+ }
1709
+ }
1710
+ ),
1711
+ PROPERTY_CONFIG
1712
+ );
1713
+ });
1714
+ });