vibecarbon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +188 -0
  3. package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
  4. package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
  5. package/carbon/.claude/settings.json +29 -0
  6. package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
  7. package/carbon/.dockerignore +57 -0
  8. package/carbon/.env.example +219 -0
  9. package/carbon/.github/copilot-instructions.md +3 -0
  10. package/carbon/.github/workflows/deploy.yml +346 -0
  11. package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
  12. package/carbon/.windsurfrules +74 -0
  13. package/carbon/AGENTS.md +422 -0
  14. package/carbon/CLAUDE.md +3 -0
  15. package/carbon/DEVELOPMENT.md +187 -0
  16. package/carbon/Dockerfile +98 -0
  17. package/carbon/LICENSE +21 -0
  18. package/carbon/PRODUCTION.md +364 -0
  19. package/carbon/README.md +193 -0
  20. package/carbon/TESTING.md +138 -0
  21. package/carbon/backup/Dockerfile +75 -0
  22. package/carbon/backup/backup.sh +95 -0
  23. package/carbon/backup/compose-backup.sh +140 -0
  24. package/carbon/biome.json +83 -0
  25. package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
  26. package/carbon/cloud-init/k3s/master-init.sh +215 -0
  27. package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
  28. package/carbon/cloud-init/k3s/worker-init.sh +147 -0
  29. package/carbon/components.json +24 -0
  30. package/carbon/content/blog/authentication-guide.mdx +34 -0
  31. package/carbon/content/blog/getting-started.mdx +41 -0
  32. package/carbon/content/changelog/v0-1-0.mdx +40 -0
  33. package/carbon/content/docs/analytics.mdx +90 -0
  34. package/carbon/content/docs/authentication.mdx +231 -0
  35. package/carbon/content/docs/background-jobs.mdx +116 -0
  36. package/carbon/content/docs/cli.mdx +630 -0
  37. package/carbon/content/docs/database.mdx +236 -0
  38. package/carbon/content/docs/deployment.mdx +227 -0
  39. package/carbon/content/docs/development.mdx +238 -0
  40. package/carbon/content/docs/environments.mdx +84 -0
  41. package/carbon/content/docs/getting-started.mdx +112 -0
  42. package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
  43. package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
  44. package/carbon/content/docs/optional-services.mdx +160 -0
  45. package/carbon/db/Dockerfile +23 -0
  46. package/carbon/docker-compose.dev-init.yml +5 -0
  47. package/carbon/docker-compose.metabase.override.yml +14 -0
  48. package/carbon/docker-compose.metabase.prod.yml +28 -0
  49. package/carbon/docker-compose.metabase.yml +84 -0
  50. package/carbon/docker-compose.n8n.override.yml +14 -0
  51. package/carbon/docker-compose.n8n.prod.yml +31 -0
  52. package/carbon/docker-compose.n8n.yml +97 -0
  53. package/carbon/docker-compose.observability.override.yml +18 -0
  54. package/carbon/docker-compose.observability.prod.yml +28 -0
  55. package/carbon/docker-compose.observability.yml +125 -0
  56. package/carbon/docker-compose.override.yml +28 -0
  57. package/carbon/docker-compose.prod.yml +294 -0
  58. package/carbon/docker-compose.yml +508 -0
  59. package/carbon/docker-entrypoint.sh +12 -0
  60. package/carbon/ha/activate-standby.sh +52 -0
  61. package/carbon/ha/primary-init.sql +36 -0
  62. package/carbon/ha/standby-init.sh +74 -0
  63. package/carbon/k8s/LICENSE +99 -0
  64. package/carbon/k8s/README.md +272 -0
  65. package/carbon/k8s/base/app/deployment.yaml +130 -0
  66. package/carbon/k8s/base/app/hpa.yaml +44 -0
  67. package/carbon/k8s/base/app/kustomization.yaml +10 -0
  68. package/carbon/k8s/base/app/network-policy.yaml +124 -0
  69. package/carbon/k8s/base/app/pdb.yaml +14 -0
  70. package/carbon/k8s/base/app/rbac.yaml +36 -0
  71. package/carbon/k8s/base/app/service.yaml +16 -0
  72. package/carbon/k8s/base/backup/cronjob.yaml +120 -0
  73. package/carbon/k8s/base/backup/kustomization.yaml +7 -0
  74. package/carbon/k8s/base/backup/network-policy.yaml +31 -0
  75. package/carbon/k8s/base/backup/rbac.yaml +7 -0
  76. package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
  77. package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
  78. package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
  79. package/carbon/k8s/base/config/configmap.yaml +48 -0
  80. package/carbon/k8s/base/config/kustomization.yaml +8 -0
  81. package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
  82. package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
  83. package/carbon/k8s/base/kustomization.yaml +43 -0
  84. package/carbon/k8s/base/namespace.yaml +7 -0
  85. package/carbon/k8s/base/network-policies.yaml +95 -0
  86. package/carbon/k8s/base/registry/kustomization.yaml +5 -0
  87. package/carbon/k8s/base/registry/local-registry.yaml +193 -0
  88. package/carbon/k8s/base/traefik/certificate.yaml +21 -0
  89. package/carbon/k8s/base/traefik/configmap.yaml +13 -0
  90. package/carbon/k8s/base/traefik/deployment.yaml +165 -0
  91. package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
  92. package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
  93. package/carbon/k8s/base/traefik/middleware.yaml +109 -0
  94. package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
  95. package/carbon/k8s/base/traefik/service.yaml +38 -0
  96. package/carbon/k8s/flux/README.md +49 -0
  97. package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
  98. package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
  99. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
  100. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
  101. package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
  102. package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
  103. package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
  104. package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
  105. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
  106. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
  107. package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
  108. package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
  109. package/carbon/k8s/infra/kustomization.yaml +21 -0
  110. package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
  111. package/carbon/k8s/overlays/local/configmap.yaml +23 -0
  112. package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
  113. package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
  114. package/carbon/k8s/overlays/local/namespace.yaml +8 -0
  115. package/carbon/k8s/overlays/local/secrets.yaml +32 -0
  116. package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
  117. package/carbon/k8s/test-local.sh +318 -0
  118. package/carbon/k8s/values/supabase.values.yaml +133 -0
  119. package/carbon/package.json +154 -0
  120. package/carbon/runtime.Dockerfile +17 -0
  121. package/carbon/scripts/_dev-jwt.mjs +45 -0
  122. package/carbon/scripts/check-rls.ts +53 -0
  123. package/carbon/scripts/dev-init.js +191 -0
  124. package/carbon/scripts/dev.js +191 -0
  125. package/carbon/scripts/docker-down.js +63 -0
  126. package/carbon/scripts/docker-logs.js +59 -0
  127. package/carbon/scripts/docker-up.js +222 -0
  128. package/carbon/scripts/generate-dev-configs.sh +131 -0
  129. package/carbon/scripts/generate-rss.ts +116 -0
  130. package/carbon/scripts/generate-sitemap.ts +102 -0
  131. package/carbon/scripts/k8s-apply.js +71 -0
  132. package/carbon/scripts/k8s-delete.js +75 -0
  133. package/carbon/scripts/lib/manifest.js +176 -0
  134. package/carbon/scripts/secret-scan.mjs +278 -0
  135. package/carbon/scripts/validate-dev-configs.sh +101 -0
  136. package/carbon/src/client/App.tsx +202 -0
  137. package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
  138. package/carbon/src/client/assets/hyperformant-light.svg +29 -0
  139. package/carbon/src/client/assets/logos/aider.svg +1 -0
  140. package/carbon/src/client/assets/logos/antigravity.svg +1 -0
  141. package/carbon/src/client/assets/logos/bolt.svg +1 -0
  142. package/carbon/src/client/assets/logos/claude-code.svg +1 -0
  143. package/carbon/src/client/assets/logos/copilot.svg +1 -0
  144. package/carbon/src/client/assets/logos/cursor.svg +1 -0
  145. package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
  146. package/carbon/src/client/assets/logos/lovable.svg +1 -0
  147. package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
  148. package/carbon/src/client/assets/logos/v0.svg +1 -0
  149. package/carbon/src/client/assets/logos/windsurf.svg +1 -0
  150. package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
  151. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
  152. package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
  153. package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
  154. package/carbon/src/client/components/AppSidebar.tsx +760 -0
  155. package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
  156. package/carbon/src/client/components/CTAFooter.tsx +59 -0
  157. package/carbon/src/client/components/ComparisonSection.tsx +132 -0
  158. package/carbon/src/client/components/ContentPanel.tsx +66 -0
  159. package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
  160. package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
  161. package/carbon/src/client/components/FAQSection.tsx +76 -0
  162. package/carbon/src/client/components/FileUpload.tsx +210 -0
  163. package/carbon/src/client/components/HeaderActions.tsx +17 -0
  164. package/carbon/src/client/components/Hero.tsx +608 -0
  165. package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
  166. package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
  167. package/carbon/src/client/components/Logo.tsx +87 -0
  168. package/carbon/src/client/components/LogoStrip.tsx +76 -0
  169. package/carbon/src/client/components/MetricsStrip.tsx +48 -0
  170. package/carbon/src/client/components/Nav.tsx +195 -0
  171. package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
  172. package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
  173. package/carbon/src/client/components/PageHeader.tsx +24 -0
  174. package/carbon/src/client/components/PlanGate.tsx +36 -0
  175. package/carbon/src/client/components/PricingSection.tsx +371 -0
  176. package/carbon/src/client/components/SEO.tsx +34 -0
  177. package/carbon/src/client/components/SmoothScroll.tsx +45 -0
  178. package/carbon/src/client/components/TechStackSection.tsx +165 -0
  179. package/carbon/src/client/components/WorkflowSection.tsx +604 -0
  180. package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
  181. package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
  182. package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
  183. package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
  184. package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
  185. package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
  186. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
  187. package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
  188. package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
  189. package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
  190. package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
  191. package/carbon/src/client/components/effects/index.ts +2 -0
  192. package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
  193. package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
  194. package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
  195. package/carbon/src/client/components/scroll/index.ts +2 -0
  196. package/carbon/src/client/components/ui/accordion.tsx +71 -0
  197. package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
  198. package/carbon/src/client/components/ui/alert.tsx +73 -0
  199. package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
  200. package/carbon/src/client/components/ui/avatar.tsx +91 -0
  201. package/carbon/src/client/components/ui/badge.tsx +50 -0
  202. package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
  203. package/carbon/src/client/components/ui/button-group.tsx +78 -0
  204. package/carbon/src/client/components/ui/button.tsx +120 -0
  205. package/carbon/src/client/components/ui/calendar.tsx +182 -0
  206. package/carbon/src/client/components/ui/card.tsx +85 -0
  207. package/carbon/src/client/components/ui/carousel.tsx +227 -0
  208. package/carbon/src/client/components/ui/chart.tsx +357 -0
  209. package/carbon/src/client/components/ui/checkbox.tsx +27 -0
  210. package/carbon/src/client/components/ui/collapsible.tsx +15 -0
  211. package/carbon/src/client/components/ui/command.tsx +178 -0
  212. package/carbon/src/client/components/ui/context-menu.tsx +233 -0
  213. package/carbon/src/client/components/ui/dialog.tsx +132 -0
  214. package/carbon/src/client/components/ui/drawer.tsx +118 -0
  215. package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
  216. package/carbon/src/client/components/ui/empty.tsx +94 -0
  217. package/carbon/src/client/components/ui/field.tsx +226 -0
  218. package/carbon/src/client/components/ui/hover-card.tsx +44 -0
  219. package/carbon/src/client/components/ui/input-group.tsx +146 -0
  220. package/carbon/src/client/components/ui/input-otp.tsx +83 -0
  221. package/carbon/src/client/components/ui/input.tsx +20 -0
  222. package/carbon/src/client/components/ui/item.tsx +188 -0
  223. package/carbon/src/client/components/ui/kbd.tsx +26 -0
  224. package/carbon/src/client/components/ui/label.tsx +21 -0
  225. package/carbon/src/client/components/ui/menubar.tsx +250 -0
  226. package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
  227. package/carbon/src/client/components/ui/pagination.tsx +102 -0
  228. package/carbon/src/client/components/ui/popover.tsx +75 -0
  229. package/carbon/src/client/components/ui/progress.tsx +66 -0
  230. package/carbon/src/client/components/ui/radio-group.tsx +36 -0
  231. package/carbon/src/client/components/ui/resizable.tsx +46 -0
  232. package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
  233. package/carbon/src/client/components/ui/select.tsx +186 -0
  234. package/carbon/src/client/components/ui/separator.tsx +21 -0
  235. package/carbon/src/client/components/ui/sheet.tsx +124 -0
  236. package/carbon/src/client/components/ui/sidebar.tsx +702 -0
  237. package/carbon/src/client/components/ui/skeleton.tsx +13 -0
  238. package/carbon/src/client/components/ui/slider.tsx +57 -0
  239. package/carbon/src/client/components/ui/sonner.tsx +45 -0
  240. package/carbon/src/client/components/ui/spinner.tsx +15 -0
  241. package/carbon/src/client/components/ui/switch.tsx +30 -0
  242. package/carbon/src/client/components/ui/table.tsx +89 -0
  243. package/carbon/src/client/components/ui/tabs.tsx +73 -0
  244. package/carbon/src/client/components/ui/textarea.tsx +18 -0
  245. package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
  246. package/carbon/src/client/components/ui/toggle.tsx +44 -0
  247. package/carbon/src/client/components/ui/tooltip.tsx +56 -0
  248. package/carbon/src/client/hooks/api/index.ts +14 -0
  249. package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
  250. package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
  251. package/carbon/src/client/hooks/use-mobile.ts +21 -0
  252. package/carbon/src/client/hooks/useMousePosition.ts +91 -0
  253. package/carbon/src/client/hooks/useNotifications.tsx +124 -0
  254. package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
  255. package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
  256. package/carbon/src/client/hooks/usePackageManager.ts +69 -0
  257. package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
  258. package/carbon/src/client/hooks/useRunningServices.ts +114 -0
  259. package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
  260. package/carbon/src/client/index.css +467 -0
  261. package/carbon/src/client/index.html +56 -0
  262. package/carbon/src/client/lib/admin-services.ts +151 -0
  263. package/carbon/src/client/lib/api.ts +32 -0
  264. package/carbon/src/client/lib/blog.ts +35 -0
  265. package/carbon/src/client/lib/changelog.ts +33 -0
  266. package/carbon/src/client/lib/docs-search.ts +101 -0
  267. package/carbon/src/client/lib/docs.ts +37 -0
  268. package/carbon/src/client/lib/i18n.ts +32 -0
  269. package/carbon/src/client/lib/supabase.ts +72 -0
  270. package/carbon/src/client/lib/tailwind-colors.ts +357 -0
  271. package/carbon/src/client/lib/theme.ts +117 -0
  272. package/carbon/src/client/lib/utils.ts +22 -0
  273. package/carbon/src/client/locales/de.json +529 -0
  274. package/carbon/src/client/locales/en.json +461 -0
  275. package/carbon/src/client/locales/es.json +529 -0
  276. package/carbon/src/client/locales/fr.json +529 -0
  277. package/carbon/src/client/locales/pt.json +529 -0
  278. package/carbon/src/client/main.tsx +56 -0
  279. package/carbon/src/client/mdx.d.ts +13 -0
  280. package/carbon/src/client/pages/ApiDocs.tsx +76 -0
  281. package/carbon/src/client/pages/AuthCallback.tsx +34 -0
  282. package/carbon/src/client/pages/Blog.tsx +167 -0
  283. package/carbon/src/client/pages/Changelog.tsx +171 -0
  284. package/carbon/src/client/pages/Charts.tsx +388 -0
  285. package/carbon/src/client/pages/Checkout.tsx +227 -0
  286. package/carbon/src/client/pages/Contact.tsx +174 -0
  287. package/carbon/src/client/pages/Dashboard.tsx +368 -0
  288. package/carbon/src/client/pages/Docs.tsx +372 -0
  289. package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
  290. package/carbon/src/client/pages/Home.tsx +187 -0
  291. package/carbon/src/client/pages/Legal.tsx +100 -0
  292. package/carbon/src/client/pages/Login.tsx +408 -0
  293. package/carbon/src/client/pages/MFAVerify.tsx +156 -0
  294. package/carbon/src/client/pages/NotFound.tsx +21 -0
  295. package/carbon/src/client/pages/Onboarding.tsx +246 -0
  296. package/carbon/src/client/pages/ResetPassword.tsx +200 -0
  297. package/carbon/src/client/pages/UIComponents.tsx +390 -0
  298. package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
  299. package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
  300. package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
  301. package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
  302. package/carbon/src/client/pages/admin/Logs.tsx +18 -0
  303. package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
  304. package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
  305. package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
  306. package/carbon/src/client/pages/admin/Settings.tsx +314 -0
  307. package/carbon/src/client/pages/admin/Theme.tsx +465 -0
  308. package/carbon/src/client/pages/admin/Users.tsx +365 -0
  309. package/carbon/src/client/pages/api-docs.css +156 -0
  310. package/carbon/src/client/pages/organizations/Details.tsx +200 -0
  311. package/carbon/src/client/pages/organizations/Members.tsx +402 -0
  312. package/carbon/src/client/pages/settings/Billing.tsx +473 -0
  313. package/carbon/src/client/pages/settings/Profile.tsx +160 -0
  314. package/carbon/src/client/pages/settings/Security.tsx +341 -0
  315. package/carbon/src/client/public/favicon.svg +19 -0
  316. package/carbon/src/client/public/robots.txt +8 -0
  317. package/carbon/src/server/billing/index.ts +104 -0
  318. package/carbon/src/server/billing/provider.ts +81 -0
  319. package/carbon/src/server/billing/providers/paddle.ts +314 -0
  320. package/carbon/src/server/billing/providers/polar.ts +325 -0
  321. package/carbon/src/server/billing/providers/stripe.ts +233 -0
  322. package/carbon/src/server/emails/templates.ts +116 -0
  323. package/carbon/src/server/index.ts +554 -0
  324. package/carbon/src/server/lib/auth.ts +6 -0
  325. package/carbon/src/server/lib/email.ts +64 -0
  326. package/carbon/src/server/lib/env.ts +112 -0
  327. package/carbon/src/server/lib/errors.ts +21 -0
  328. package/carbon/src/server/lib/logger.ts +17 -0
  329. package/carbon/src/server/lib/rate-limiter.ts +288 -0
  330. package/carbon/src/server/lib/request.ts +34 -0
  331. package/carbon/src/server/lib/stripe.ts +42 -0
  332. package/carbon/src/server/lib/supabase.ts +65 -0
  333. package/carbon/src/server/middleware/requirePlan.ts +80 -0
  334. package/carbon/src/server/routes/_internal/services-status.ts +958 -0
  335. package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
  336. package/carbon/src/server/routes/health.ts +48 -0
  337. package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
  338. package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
  339. package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
  340. package/carbon/src/server/routes/v1/auth.ts +390 -0
  341. package/carbon/src/server/routes/v1/billing.ts +718 -0
  342. package/carbon/src/server/routes/v1/contact.ts +93 -0
  343. package/carbon/src/server/routes/v1/index.ts +1333 -0
  344. package/carbon/src/server/routes/v1/newsletter.ts +181 -0
  345. package/carbon/src/server/routes/v1/performance.ts +157 -0
  346. package/carbon/src/server/routes/v1/stats.ts +170 -0
  347. package/carbon/src/server/routes/v1/theme.ts +106 -0
  348. package/carbon/src/server/routes/webhooks/billing.ts +376 -0
  349. package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
  350. package/carbon/src/server/types.ts +11 -0
  351. package/carbon/src/shared/pricing.ts +155 -0
  352. package/carbon/src/shared/types.ts +338 -0
  353. package/carbon/supabase/migrations/00001_init.sql +717 -0
  354. package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
  355. package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
  356. package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
  357. package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
  358. package/carbon/supabase/seed.sql +16 -0
  359. package/carbon/tests/_helpers/app.ts +45 -0
  360. package/carbon/tests/_helpers/env.ts +37 -0
  361. package/carbon/tests/_helpers/factories.ts +69 -0
  362. package/carbon/tests/_helpers/jwt.ts +23 -0
  363. package/carbon/tests/_helpers/setup-integration.ts +20 -0
  364. package/carbon/tests/_helpers/setup-rtl.ts +12 -0
  365. package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
  366. package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
  367. package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
  368. package/carbon/tests/integration/server/routes/health.test.ts +61 -0
  369. package/carbon/tests/structural/i18n-parity.test.ts +42 -0
  370. package/carbon/tests/unit/client/utils.test.ts +49 -0
  371. package/carbon/tests/unit/shared/pricing.test.ts +93 -0
  372. package/carbon/tsconfig.json +27 -0
  373. package/carbon/tsconfig.server.json +27 -0
  374. package/carbon/tsconfig.test.json +9 -0
  375. package/carbon/vite.config.ts +110 -0
  376. package/carbon/vitest.config.ts +74 -0
  377. package/carbon/volumes/db/jwt.sql +57 -0
  378. package/carbon/volumes/db/metabase-init.sh +29 -0
  379. package/carbon/volumes/db/n8n-init.sh +25 -0
  380. package/carbon/volumes/db/realtime.sql +33 -0
  381. package/carbon/volumes/db/roles.sql +93 -0
  382. package/carbon/volumes/db/set-passwords.sh +12 -0
  383. package/carbon/volumes/db/super-admin.dev.sql +113 -0
  384. package/carbon/volumes/db/super-admin.generated.sql +113 -0
  385. package/carbon/volumes/db/super-admin.sql +114 -0
  386. package/carbon/volumes/grafana/dashboards/logs.json +179 -0
  387. package/carbon/volumes/grafana/dashboards/overview.json +523 -0
  388. package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
  389. package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
  390. package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
  391. package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
  392. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
  393. package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
  394. package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
  395. package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
  396. package/carbon/volumes/kong/kong.yml +208 -0
  397. package/carbon/volumes/loki/loki-config.yml +58 -0
  398. package/carbon/volumes/n8n/hooks.js +131 -0
  399. package/carbon/volumes/n8n/scripts/setup.sh +66 -0
  400. package/carbon/volumes/prometheus/prometheus.yml +43 -0
  401. package/carbon/volumes/promtail/promtail-config.yml +64 -0
  402. package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
  403. package/carbon/volumes/traefik/middlewares.yml +95 -0
  404. package/carbon/volumes/traefik/vite-dev.yml +20 -0
  405. package/package.json +95 -0
  406. package/src/access.js +354 -0
  407. package/src/activate.js +187 -0
  408. package/src/add.js +718 -0
  409. package/src/backup.js +786 -0
  410. package/src/cli.js +350 -0
  411. package/src/configure.js +967 -0
  412. package/src/console.js +155 -0
  413. package/src/create.js +1499 -0
  414. package/src/deploy.js +311 -0
  415. package/src/destroy.js +2033 -0
  416. package/src/diagnose.js +735 -0
  417. package/src/down.js +80 -0
  418. package/src/failover.js +1032 -0
  419. package/src/lib/backup-s3.js +179 -0
  420. package/src/lib/build.js +33 -0
  421. package/src/lib/checksum.js +28 -0
  422. package/src/lib/ci-setup.js +666 -0
  423. package/src/lib/cli/help.js +129 -0
  424. package/src/lib/cli/parse-flags.js +160 -0
  425. package/src/lib/cli/select-action.js +65 -0
  426. package/src/lib/cli/select-environment.js +108 -0
  427. package/src/lib/cli/tty-guard.js +75 -0
  428. package/src/lib/cloudflare.js +447 -0
  429. package/src/lib/colors.js +52 -0
  430. package/src/lib/command.js +361 -0
  431. package/src/lib/config.js +359 -0
  432. package/src/lib/cost.js +103 -0
  433. package/src/lib/deploy/bundle.js +231 -0
  434. package/src/lib/deploy/compose/acme-verify.js +121 -0
  435. package/src/lib/deploy/compose/build-args.js +78 -0
  436. package/src/lib/deploy/compose/ha.js +1874 -0
  437. package/src/lib/deploy/compose/index.js +1294 -0
  438. package/src/lib/deploy/compose/reconcile.js +74 -0
  439. package/src/lib/deploy/github.js +382 -0
  440. package/src/lib/deploy/image.js +191 -0
  441. package/src/lib/deploy/index.js +119 -0
  442. package/src/lib/deploy/k8s/LICENSE +99 -0
  443. package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
  444. package/src/lib/deploy/k8s/ha/index.js +1000 -0
  445. package/src/lib/deploy/k8s/index.js +62 -0
  446. package/src/lib/deploy/k8s/k3s.js +2515 -0
  447. package/src/lib/deploy/orchestrator.js +1401 -0
  448. package/src/lib/deploy/prompts.js +545 -0
  449. package/src/lib/deploy/remote-build.js +130 -0
  450. package/src/lib/deploy/state.js +120 -0
  451. package/src/lib/deploy/utils.js +328 -0
  452. package/src/lib/deploy-logger.js +93 -0
  453. package/src/lib/dns-propagation.js +48 -0
  454. package/src/lib/environment.js +58 -0
  455. package/src/lib/fetch-retry.js +103 -0
  456. package/src/lib/github-environments.js +335 -0
  457. package/src/lib/hetzner-dns.js +377 -0
  458. package/src/lib/hetzner-guided-setup.js +275 -0
  459. package/src/lib/host-keys.js +37 -0
  460. package/src/lib/iac/cloud-init.js +52 -0
  461. package/src/lib/iac/index.js +325 -0
  462. package/src/lib/iac/programs/hetzner-compose.js +130 -0
  463. package/src/lib/iac/programs/hetzner-k8s.js +320 -0
  464. package/src/lib/kubectl.js +81 -0
  465. package/src/lib/licensing/index.js +259 -0
  466. package/src/lib/licensing/tiers.js +181 -0
  467. package/src/lib/licensing/validator.js +171 -0
  468. package/src/lib/merge-package-json.js +90 -0
  469. package/src/lib/operator-ip.js +381 -0
  470. package/src/lib/package-manager.js +111 -0
  471. package/src/lib/perf.js +71 -0
  472. package/src/lib/project-guard.js +39 -0
  473. package/src/lib/project.js +334 -0
  474. package/src/lib/providers/base.js +276 -0
  475. package/src/lib/providers/hetzner-s3.js +656 -0
  476. package/src/lib/providers/hetzner.js +755 -0
  477. package/src/lib/providers/index.js +164 -0
  478. package/src/lib/s3.js +510 -0
  479. package/src/lib/secret-scan.js +583 -0
  480. package/src/lib/secrets.js +63 -0
  481. package/src/lib/server-types.js +195 -0
  482. package/src/lib/shell.js +91 -0
  483. package/src/lib/ssh.js +241 -0
  484. package/src/lib/tracker.js +242 -0
  485. package/src/lib/upgrade-policy.js +170 -0
  486. package/src/lib/validators.js +105 -0
  487. package/src/lib/version.js +5 -0
  488. package/src/remove.js +292 -0
  489. package/src/reset.js +97 -0
  490. package/src/restore.js +871 -0
  491. package/src/scale.js +1734 -0
  492. package/src/shell.js +222 -0
  493. package/src/status.js +981 -0
  494. package/src/up.js +264 -0
  495. package/src/upgrade.js +721 -0
@@ -0,0 +1,53 @@
1
+ import { Client } from 'pg';
2
+ import { env } from '../src/server/lib/env';
3
+
4
+ /**
5
+ * CI/CD Security Check
6
+ * Verifies that Row Level Security (RLS) is enabled on ALL tables in the 'public' schema.
7
+ *
8
+ * This ensures that no table is accidentally left open to public access
9
+ * (especially now that we rely on RLS as the primary defense mechanism).
10
+ */
11
+
12
+ async function main() {
13
+ console.log('🔒 Verifying Row Level Security (RLS) policies...');
14
+
15
+ // Use connection string from env or default to local dev
16
+ const connectionString =
17
+ process.env.DATABASE_URL ||
18
+ `postgres://postgres:${process.env.POSTGRES_PASSWORD || 'postgres'}@localhost:5432/postgres`;
19
+
20
+ const client = new Client({ connectionString });
21
+
22
+ try {
23
+ await client.connect();
24
+
25
+ // Query finding tables in public schema that have RLS disabled
26
+ const result = await client.query(`
27
+ SELECT tablename
28
+ FROM pg_tables
29
+ WHERE schemaname = 'public'
30
+ AND rowsecurity = false;
31
+ `);
32
+
33
+ if (result.rows.length > 0) {
34
+ console.error('\n❌ CRITICAL SECURITY FAILURE: The following tables do not have RLS enabled:');
35
+ result.rows.forEach(row => {
36
+ console.error(` - ${row.tablename}`);
37
+ });
38
+ console.error('\nResult: Security check FAILED. All public tables must have RLS enabled.');
39
+ process.exit(1);
40
+ }
41
+
42
+ console.log('✅ Success: All tables in public schema have RLS enabled.');
43
+ process.exit(0);
44
+
45
+ } catch (err) {
46
+ console.error('Error running security check:', err);
47
+ process.exit(1);
48
+ } finally {
49
+ await client.end();
50
+ }
51
+ }
52
+
53
+ main();
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate .env and .env.local for developing directly in the carbon/ template.
4
+ *
5
+ * Uses the same hardcoded dev values as generate-dev-configs.sh
6
+ * so everything works out of the box without running `vibecarbon create`.
7
+ *
8
+ * Usage:
9
+ * node scripts/dev-init.js # Generate if missing
10
+ * node scripts/dev-init.js --force # Overwrite existing files
11
+ */
12
+
13
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
14
+
15
+ const force = process.argv.includes('--force');
16
+
17
+ // Dev values matching generate-dev-configs.sh.
18
+ // Note: ANON_KEY / SERVICE_ROLE_KEY are NOT hardcoded here — they're
19
+ // signed at runtime against JWT_SECRET below. Committing literal JWT
20
+ // strings tripped GitHub's secret-scanner on every push; signing on
21
+ // the fly keeps the dev workflow deterministic without leaking
22
+ // service-role-shaped values into git history.
23
+ const DEV = {
24
+ PROJECT_NAME: 'vibecarbon',
25
+ JWT_SECRET: 'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded',
26
+ DB_ENC_KEY: 'devdbencryptkey1',
27
+ VAULT_ENC_KEY: 'local-vault-encryption-key-1234567890',
28
+ REALTIME_SECRET: 'local-realtime-secret-pad',
29
+ DB_PASSWORD: 'local-postgres-password-for-dev-only',
30
+ ANON_KEY: signDevJwt('anon', 'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded'),
31
+ SERVICE_ROLE_KEY: signDevJwt(
32
+ 'service_role',
33
+ 'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded',
34
+ ),
35
+ ADMIN_EMAIL: 'admin@example.com',
36
+ ADMIN_PASSWORD: 'local-admin-password',
37
+ };
38
+
39
+ function signDevJwt(role, secret) {
40
+ const { createHmac } = require('node:crypto');
41
+ const b64url = (buf) =>
42
+ Buffer.from(buf)
43
+ .toString('base64')
44
+ .replace(/=+$/, '')
45
+ .replace(/\+/g, '-')
46
+ .replace(/\//g, '_');
47
+ const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
48
+ const payload = b64url(
49
+ JSON.stringify({
50
+ iss: 'supabase-demo',
51
+ role,
52
+ exp: Math.floor(Date.now() / 1000) + 100 * 365 * 24 * 60 * 60,
53
+ }),
54
+ );
55
+ const sig = b64url(createHmac('sha256', secret).update(`${header}.${payload}`).digest());
56
+ return `${header}.${payload}.${sig}`;
57
+ }
58
+
59
+ const envContent = `# =============================================================================
60
+ # DEV ENVIRONMENT - Generated by \`pnpm dev:init\`
61
+ # Matches hardcoded values in generate-dev-configs.sh
62
+ # DO NOT COMMIT THIS FILE
63
+ # =============================================================================
64
+
65
+ # Project
66
+ PROJECT_NAME="${DEV.PROJECT_NAME}"
67
+ VITE_PROJECT_NAME="${DEV.PROJECT_NAME}"
68
+
69
+ # Supabase URLs (base ports — DEV_PORT_OFFSET is applied dynamically at runtime
70
+ # by src/server/lib/env.ts and vite.config.ts, so these stay at default 8000)
71
+ SUPABASE_URL="http://localhost:8000"
72
+ VITE_SUPABASE_URL="http://localhost:8000"
73
+
74
+ # Supabase Keys (standard Supabase demo keys)
75
+ SUPABASE_ANON_KEY="${DEV.ANON_KEY}"
76
+ VITE_SUPABASE_ANON_KEY="${DEV.ANON_KEY}"
77
+ SUPABASE_SERVICE_ROLE_KEY="${DEV.SERVICE_ROLE_KEY}"
78
+
79
+ # JWT Secret
80
+ JWT_SECRET="${DEV.JWT_SECRET}"
81
+
82
+ # Database
83
+ DB_PASSWORD="${DEV.DB_PASSWORD}"
84
+ POSTGRES_PASSWORD="${DEV.DB_PASSWORD}"
85
+
86
+ # Encryption keys (DB_ENC_KEY must be exactly 16 bytes for Realtime AES-128-ECB)
87
+ DB_ENC_KEY="${DEV.DB_ENC_KEY}"
88
+ VAULT_ENC_KEY="${DEV.VAULT_ENC_KEY}"
89
+ REALTIME_SECRET="${DEV.REALTIME_SECRET}"
90
+
91
+ # Admin credentials (matches super-admin.dev.sql)
92
+ ADMIN_EMAIL="${DEV.ADMIN_EMAIL}"
93
+ ADMIN_PASSWORD="${DEV.ADMIN_PASSWORD}"
94
+
95
+ # Site URL (base port — offset applied dynamically, see above)
96
+ SITE_URL="http://localhost:5173"
97
+
98
+ # OAuth (disabled for dev)
99
+ GOOGLE_ENABLED="false"
100
+ GOOGLE_CLIENT_ID=""
101
+ GOOGLE_CLIENT_SECRET=""
102
+
103
+ MICROSOFT_ENABLED="false"
104
+ MICROSOFT_CLIENT_ID=""
105
+ MICROSOFT_CLIENT_SECRET=""
106
+ MICROSOFT_TENANT_ID=""
107
+
108
+ # External services (disabled for dev)
109
+ STRIPE_SECRET_KEY=""
110
+ STRIPE_WEBHOOK_SECRET=""
111
+ SMTP_HOST=""
112
+ SMTP_PORT="587"
113
+ SMTP_USER=""
114
+ SMTP_PASS=""
115
+ # SMTP_ADMIN_EMAIL=""
116
+ SMTP_SENDER_NAME="${DEV.PROJECT_NAME}"
117
+
118
+ # Optional services
119
+ VITE_N8N_ENABLED="false"
120
+ VITE_OBSERVABILITY_ENABLED="false"
121
+
122
+ # Analytics (Plausible - leave empty to disable)
123
+ VITE_PLAUSIBLE_DOMAIN=""
124
+ VITE_PLAUSIBLE_SCRIPT_URL=""
125
+
126
+ # Observability
127
+ GRAFANA_ADMIN_USER="admin"
128
+ GRAFANA_ADMIN_PASSWORD="admin"
129
+
130
+ # Ports
131
+ DEV_PORT_OFFSET="0"
132
+
133
+ # Environment
134
+ NODE_ENV="development"
135
+ PORT="3000"
136
+ VITE_ENVIRONMENT="development"
137
+ VITE_APP_VERSION="0.1.0"
138
+ `;
139
+
140
+ function writeEnv(filename) {
141
+ if (existsSync(filename) && !force) {
142
+ console.log(` SKIP: ${filename} (exists, use --force to overwrite)`);
143
+ return false;
144
+ }
145
+ writeFileSync(filename, envContent, { mode: 0o600 });
146
+ console.log(` ${force && existsSync(filename) ? 'OVERWRITTEN' : 'CREATED'}: ${filename}`);
147
+ return true;
148
+ }
149
+
150
+ console.log('');
151
+ console.log(' Generating dev environment files...');
152
+ console.log('');
153
+
154
+ const wrote1 = writeEnv('.env');
155
+ const wrote2 = writeEnv('.env.local');
156
+
157
+ // Generate super-admin SQL with real credentials from the dev template.
158
+ // The template has ADMIN_EMAIL and ADMIN_PASSWORD_HASH placeholders that
159
+ // `vibecarbon create` replaces for real projects. For local CLI development we
160
+ // generate a separate file so the template stays clean (tracked by git), plus a
161
+ // docker-compose override that mounts the generated file into the db container.
162
+ // The hash is pre-computed for the fixed dev password — no bcrypt dependency needed.
163
+ const ADMIN_PASSWORD_HASH = '$2b$10$OWzZo/eSlFAb2S9oX4Ps5.prj7LzVfAWo7N1cFrL3N8Z3HLUZtvOm';
164
+ const sqlTemplatePath = 'volumes/db/super-admin.dev.sql';
165
+ const sqlGeneratedPath = 'volumes/db/super-admin.generated.sql';
166
+ if (existsSync(sqlTemplatePath)) {
167
+ const sql = readFileSync(sqlTemplatePath, 'utf-8')
168
+ .replace(/\{\{ADMIN_EMAIL\}\}/g, DEV.ADMIN_EMAIL)
169
+ .replace(/\{\{ADMIN_PASSWORD_HASH\}\}/g, ADMIN_PASSWORD_HASH);
170
+ writeFileSync(sqlGeneratedPath, sql);
171
+ console.log(` CREATED: ${sqlGeneratedPath} (dev admin credentials)`);
172
+
173
+ // Write a compose override that mounts the generated SQL instead of the template
174
+ const composeOverride = `# Generated by dev-init.js — DO NOT COMMIT
175
+ services:
176
+ db:
177
+ volumes:
178
+ - ./volumes/db/super-admin.generated.sql:/docker-entrypoint-initdb.d/post-init/super-admin.sql:Z
179
+ `;
180
+ writeFileSync('docker-compose.dev-init.yml', composeOverride);
181
+ console.log(' CREATED: docker-compose.dev-init.yml (mounts generated admin SQL)');
182
+ }
183
+
184
+ console.log('');
185
+ if (wrote1 || wrote2) {
186
+ console.log(` Admin login: ${DEV.ADMIN_EMAIL} / ${DEV.ADMIN_PASSWORD}`);
187
+ console.log(' Run `pnpm dev:start` to start all services.');
188
+ } else {
189
+ console.log(' No files written. Use --force to overwrite existing files.');
190
+ }
191
+ console.log('');
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Development server script with proper signal handling.
4
+ * Spawns both the API server (tsx watch) and Vite dev server,
5
+ * and ensures clean shutdown on Ctrl+C.
6
+ */
7
+
8
+ import { execFileSync, spawn } from 'node:child_process';
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { createServer } from 'node:net';
11
+
12
+ const isWindows = process.platform === 'win32';
13
+
14
+ // Read project name from package.json
15
+ let projectName = 'your app';
16
+ try {
17
+ const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
18
+ projectName = pkg.name || projectName;
19
+ } catch {
20
+ // Ignore errors, use default
21
+ }
22
+
23
+ // Read port configuration from .env.local
24
+ function getPortConfig() {
25
+ const defaults = { vite: 5173, api: 3000 };
26
+ try {
27
+ const envFiles = ['.env.local', '.env'];
28
+ for (const file of envFiles) {
29
+ if (existsSync(file)) {
30
+ const content = readFileSync(file, 'utf-8');
31
+ const getEnvValue = (key) => {
32
+ const match = content.match(new RegExp(`^${key}=["']?([^"'\\n]+)["']?`, 'm'));
33
+ return match ? match[1] : null;
34
+ };
35
+
36
+ const portOffset = Number.parseInt(getEnvValue('DEV_PORT_OFFSET') || '0', 10);
37
+ const vitePort = getEnvValue('DEV_VITE_PORT') || String(5173 + portOffset);
38
+ const apiPort = getEnvValue('DEV_API_PORT') || String(3000 + portOffset);
39
+
40
+ return { vite: Number.parseInt(vitePort, 10), api: Number.parseInt(apiPort, 10) };
41
+ }
42
+ }
43
+ } catch {
44
+ // Fall through to defaults
45
+ }
46
+ return defaults;
47
+ }
48
+
49
+ /** Resolves to true if no listener can grab the port on 0.0.0.0. */
50
+ function isPortFree(port) {
51
+ return new Promise((resolve) => {
52
+ const probe = createServer();
53
+ probe.once('error', () => resolve(false));
54
+ probe.once('listening', () => probe.close(() => resolve(true)));
55
+ probe.listen(port, '0.0.0.0');
56
+ });
57
+ }
58
+
59
+ /** Best-effort `command/pid` lookup for whatever owns a busy port. */
60
+ function describePortHolder(port) {
61
+ if (isWindows) return null;
62
+ // execFileSync (no shell) — port is already numeric, but using execFile
63
+ // keeps us aligned with the codebase's no-shell convention.
64
+ if (!Number.isInteger(port)) return null;
65
+ try {
66
+ const out = execFileSync('lsof', [`-iTCP:${port}`, '-sTCP:LISTEN', '-P', '-n'], {
67
+ encoding: 'utf-8',
68
+ stdio: ['ignore', 'pipe', 'ignore'],
69
+ });
70
+ const dataLines = out.trim().split('\n').slice(1);
71
+ if (dataLines.length === 0) return null;
72
+ const [command, pid] = dataLines[0].split(/\s+/);
73
+ return { command, pid };
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ const ports = getPortConfig();
80
+
81
+ // ANSI colors
82
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
83
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
84
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
85
+
86
+ // Pre-flight: fail fast if either port is already in use. Without this,
87
+ // `tsx watch` swallows the EADDRINUSE from the API server, Vite keeps
88
+ // serving on its port, and `/api/*` requests return 502 with empty bodies —
89
+ // surfacing in the UI as "Unexpected end of JSON input".
90
+ const portChecks = await Promise.all(
91
+ [
92
+ { name: 'API', port: ports.api },
93
+ { name: 'Vite', port: ports.vite },
94
+ ].map(async (p) => ({ ...p, free: await isPortFree(p.port) })),
95
+ );
96
+ const conflicts = portChecks.filter((c) => !c.free);
97
+ if (conflicts.length > 0) {
98
+ console.error(`\n ${yellow('✗')} Cannot start dev servers — port conflict:\n`);
99
+ for (const { name, port } of conflicts) {
100
+ const holder = describePortHolder(port);
101
+ const who = holder ? ` (held by ${holder.command} PID ${holder.pid})` : '';
102
+ console.error(` ${name} port ${bold(String(port))} is in use${who}`);
103
+ }
104
+ console.error(
105
+ `\n An orphaned dev session is likely still running. To recover:\n` +
106
+ ` ${cyan('pkill -f "scripts/dev.js"')} ${cyan('# kill the previous dev-server tree')}\n` +
107
+ ` ${cyan('pnpm dev:start')} ${cyan('# restart')}\n`,
108
+ );
109
+ process.exit(1);
110
+ }
111
+
112
+ console.log(`
113
+ ${cyan('✦')} Starting ${projectName} services...
114
+
115
+ ${cyan('→')} ${bold('Frontend:')} ${cyan(`http://localhost:${ports.vite}`)}
116
+ ${cyan('→')} ${bold('API:')} ${cyan(`http://localhost:${ports.api}`)}
117
+ ${cyan('→')} ${bold('Studio:')} ${cyan('http://studio.localhost')} (requires login)
118
+ ${cyan('→')} ${bold('Traefik:')} ${cyan('http://traefik.localhost')} (requires login)
119
+ `);
120
+
121
+ const children = [];
122
+
123
+ // Spawn server process with its own process group for clean shutdown
124
+ const server = spawn('npx', ['tsx', 'watch', '--env-file=.env.local', 'src/server/index.ts'], {
125
+ stdio: 'inherit',
126
+ shell: isWindows,
127
+ detached: !isWindows, // Create process group on Unix for clean group kill
128
+ });
129
+ children.push(server);
130
+
131
+ // Spawn client process
132
+ const client = spawn('npx', ['vite'], {
133
+ stdio: 'inherit',
134
+ shell: isWindows,
135
+ detached: !isWindows,
136
+ });
137
+ children.push(client);
138
+
139
+ // Track if we're shutting down
140
+ let shuttingDown = false;
141
+
142
+ function shutdown(exitCode = 0) {
143
+ if (shuttingDown) return;
144
+ shuttingDown = true;
145
+
146
+ console.log('\n Shutting down...');
147
+
148
+ // Kill all child process groups
149
+ for (const child of children) {
150
+ if (child.exitCode === null) {
151
+ if (isWindows) {
152
+ spawn('taskkill', ['/pid', child.pid, '/f', '/t']);
153
+ } else {
154
+ // Kill the entire process group (negative PID)
155
+ try {
156
+ process.kill(-child.pid, 'SIGTERM');
157
+ } catch {
158
+ // Fallback: kill just the process
159
+ try {
160
+ child.kill('SIGTERM');
161
+ } catch {
162
+ // Process already dead
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ // Exit immediately - don't wait
170
+ process.exit(exitCode);
171
+ }
172
+
173
+ // Handle signals
174
+ process.on('SIGINT', () => shutdown(0));
175
+ process.on('SIGTERM', () => shutdown(0));
176
+
177
+ // If either child exits while the session is meant to be running, take the
178
+ // rest down with it. Otherwise an API crash leaves Vite happily serving a
179
+ // half-dead app where /api/* returns 502s.
180
+ for (const child of children) {
181
+ child.on('exit', (code, signal) => {
182
+ if (shuttingDown) return;
183
+ const failed = code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGINT';
184
+ if (failed) {
185
+ console.error(
186
+ `\n ${yellow('✗')} Dev process exited unexpectedly (code=${code}, signal=${signal}). Stopping remaining processes.`,
187
+ );
188
+ }
189
+ shutdown(failed ? (code ?? 1) : 0);
190
+ });
191
+ }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop Docker Compose services based on enabled features
4
+ *
5
+ * Usage:
6
+ * node scripts/docker-down.js # Stop services
7
+ * node scripts/docker-down.js -v # Stop and remove volumes
8
+ * node scripts/docker-down.js --dry-run # Show command without running
9
+ */
10
+
11
+ import { execSync } from 'node:child_process';
12
+ import { buildComposeCommand, loadManifest, parseArgs } from './lib/manifest.js';
13
+
14
+ const rawArgs = process.argv.slice(2);
15
+ const args = parseArgs(rawArgs);
16
+
17
+ // Check for -v flag (volumes) - different from --verbose
18
+ const removeVolumes = rawArgs.includes('-v') && !rawArgs.includes('--verbose');
19
+ const removeImages = rawArgs.includes('--rmi');
20
+
21
+ if (args.help) {
22
+ console.log(`
23
+ Usage: node scripts/docker-down.js [options]
24
+
25
+ Options:
26
+ --all Include all optional services (ignores .vibecarbon.json)
27
+ -v Remove volumes (docker compose down -v)
28
+ --prod Include production overlay
29
+ --rmi Remove locally-built images (docker compose down --rmi local)
30
+ --dry-run Print the command without executing it
31
+ --verbose Verbose output
32
+ -h, --help Show this help message
33
+
34
+ Examples:
35
+ node scripts/docker-down.js # Stop services from .vibecarbon.json
36
+ node scripts/docker-down.js --all # Stop ALL services
37
+ node scripts/docker-down.js -v # Stop and remove volumes
38
+ node scripts/docker-down.js -v --rmi # Stop, remove volumes and built images
39
+ pnpm docker:down # Via package.json script
40
+ `);
41
+ process.exit(0);
42
+ }
43
+
44
+ const manifest = loadManifest();
45
+ let action = 'down';
46
+ if (removeVolumes) action += ' -v';
47
+ if (removeImages) action += ' --rmi local';
48
+ const cmd = buildComposeCommand(action, manifest, { prod: args.prod, all: args.all });
49
+
50
+ if (args.dryRun) {
51
+ console.log('Would run:', cmd);
52
+ process.exit(0);
53
+ }
54
+
55
+ if (args.verbose) {
56
+ console.log('Running:', cmd);
57
+ }
58
+
59
+ try {
60
+ execSync(cmd, { stdio: 'inherit' });
61
+ } catch (error) {
62
+ process.exit(error.status || 1);
63
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * View Docker Compose logs for enabled services
4
+ *
5
+ * Usage:
6
+ * node scripts/docker-logs.js # Follow all logs
7
+ * node scripts/docker-logs.js app # Follow logs for specific service
8
+ * node scripts/docker-logs.js --dry-run # Show command without running
9
+ */
10
+
11
+ import { execSync } from 'node:child_process';
12
+ import { buildComposeCommand, loadManifest, parseArgs } from './lib/manifest.js';
13
+
14
+ const args = parseArgs(process.argv.slice(2));
15
+
16
+ if (args.help) {
17
+ console.log(`
18
+ Usage: node scripts/docker-logs.js [service] [options]
19
+
20
+ Arguments:
21
+ service Optional service name to filter logs
22
+
23
+ Options:
24
+ --prod Include production overlay
25
+ --dry-run Print the command without executing it
26
+ --verbose Verbose output
27
+ -h, --help Show this help message
28
+
29
+ Examples:
30
+ node scripts/docker-logs.js # Follow all logs
31
+ node scripts/docker-logs.js app # Follow app service logs
32
+ node scripts/docker-logs.js db # Follow database logs
33
+ pnpm docker:logs # Via package.json script
34
+ `);
35
+ process.exit(0);
36
+ }
37
+
38
+ const manifest = loadManifest();
39
+ const service = args.positional[0] || '';
40
+ const action = `logs -f ${service}`.trim();
41
+ const cmd = buildComposeCommand(action, manifest, { prod: args.prod });
42
+
43
+ if (args.dryRun) {
44
+ console.log('Would run:', cmd);
45
+ process.exit(0);
46
+ }
47
+
48
+ if (args.verbose) {
49
+ console.log('Running:', cmd);
50
+ }
51
+
52
+ try {
53
+ execSync(cmd, { stdio: 'inherit' });
54
+ } catch (error) {
55
+ // Logs can be interrupted with Ctrl+C, which is expected
56
+ if (error.status !== 130) {
57
+ process.exit(error.status || 1);
58
+ }
59
+ }