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,222 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Start Docker Compose services based on enabled features
4
+ *
5
+ * Usage:
6
+ * node scripts/docker-up.js # Start dev services
7
+ * node scripts/docker-up.js --prod # Start production services
8
+ * node scripts/docker-up.js --dry-run # Show command without running
9
+ */
10
+
11
+ import { execSync } from 'node:child_process';
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import {
14
+ buildComposeCommand,
15
+ discoverOptionalServices,
16
+ getEnabledServices,
17
+ loadManifest,
18
+ parseArgs,
19
+ } from './lib/manifest.js';
20
+
21
+ /**
22
+ * Read environment value from .env files
23
+ */
24
+ function getEnvValue(key) {
25
+ const envFiles = ['.env.local', '.env'];
26
+ for (const file of envFiles) {
27
+ if (existsSync(file)) {
28
+ const content = readFileSync(file, 'utf-8');
29
+ const match = content.match(new RegExp(`^${key}=["']?([^"'\\n]+)["']?`, 'm'));
30
+ if (match) return match[1];
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ /**
37
+ * Calculate port with offset, respecting explicit overrides
38
+ */
39
+ function getPort(envKey, defaultPort, offset) {
40
+ const explicit = getEnvValue(envKey);
41
+ if (explicit) return explicit;
42
+ return String(defaultPort + offset);
43
+ }
44
+
45
+ /**
46
+ * Build environment variables for port configuration
47
+ * Applies DEV_PORT_OFFSET to all configurable ports
48
+ */
49
+ function getPortEnvVars() {
50
+ const portOffset = Number.parseInt(getEnvValue('DEV_PORT_OFFSET') || '0', 10);
51
+
52
+ return {
53
+ DEV_DB_PORT: getPort('DEV_DB_PORT', 5432, portOffset),
54
+ DEV_KONG_PORT: getPort('DEV_KONG_PORT', 8000, portOffset),
55
+ DEV_KONG_SSL_PORT: getPort('DEV_KONG_SSL_PORT', 8443, portOffset),
56
+ DEV_TRAEFIK_PORT: getPort('DEV_TRAEFIK_PORT', 80, portOffset),
57
+ DEV_PROMETHEUS_PORT: getPort('DEV_PROMETHEUS_PORT', 9190, portOffset),
58
+ DEV_GRAFANA_PORT: getPort('DEV_GRAFANA_PORT', 3002, portOffset),
59
+ DEV_LOKI_PORT: getPort('DEV_LOKI_PORT', 3100, portOffset),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Run DB init scripts for optional services.
65
+ * These scripts are idempotent (IF NOT EXISTS, ALTER ROLE) so they're safe
66
+ * to run on every startup. This handles the case where a service is added
67
+ * after the DB volume already exists — Postgres only runs entrypoint scripts
68
+ * on first volume initialization.
69
+ */
70
+ function runServiceDbInit(manifest, { all, verbose }) {
71
+ let serviceNames;
72
+ if (all) {
73
+ // Extract service names from compose file names: docker-compose.metabase.yml → metabase
74
+ serviceNames = discoverOptionalServices().map((f) => f.replace('docker-compose.', '').replace('.yml', ''));
75
+ } else {
76
+ serviceNames = getEnabledServices(manifest);
77
+ }
78
+
79
+ const scripts = serviceNames
80
+ .map((name) => ({ name, path: `volumes/db/${name}-init.sh` }))
81
+ .filter(({ path }) => existsSync(path));
82
+
83
+ if (scripts.length === 0) return;
84
+
85
+ if (verbose) console.log('Phase 1.5 — running DB init scripts for services:', scripts.map((s) => s.name).join(', '));
86
+
87
+ for (const { name, path } of scripts) {
88
+ if (verbose) console.log(` Running DB init for ${name}...`);
89
+ try {
90
+ execSync(`docker compose -f docker-compose.yml exec -T db bash < ${path}`, {
91
+ stdio: verbose ? 'inherit' : 'pipe',
92
+ env: { ...process.env, ...getPortEnvVars() },
93
+ });
94
+ } catch (error) {
95
+ console.warn(`Warning: DB init for ${name} failed (${error.message}). The service may not connect.`);
96
+ }
97
+ }
98
+ }
99
+
100
+ const args = parseArgs(process.argv.slice(2));
101
+
102
+ if (args.help) {
103
+ console.log(`
104
+ Usage: node scripts/docker-up.js [options]
105
+
106
+ Options:
107
+ --all Include all optional services (ignores .vibecarbon.json)
108
+ --prod Include production overlay (docker-compose.prod.yml)
109
+ --dry-run Print the command without executing it
110
+ -v Verbose output
111
+ -h, --help Show this help message
112
+
113
+ Examples:
114
+ node scripts/docker-up.js # Start services from .vibecarbon.json
115
+ node scripts/docker-up.js --all # Start ALL optional services
116
+ node scripts/docker-up.js --prod # Start with production config
117
+ pnpm docker:up # Via package.json script
118
+ pnpm docker:up -- --all # Start all services via pnpm
119
+ `);
120
+ process.exit(0);
121
+ }
122
+
123
+ const manifest = loadManifest();
124
+
125
+ // Get port configuration with offset applied
126
+ const portEnv = getPortEnvVars();
127
+ const execOpts = { stdio: 'inherit', env: { ...process.env, ...portEnv } };
128
+
129
+ // Check if any optional services (metabase, n8n, observability, etc.) are enabled.
130
+ // These can have slow health checks (e.g. Metabase JVM takes 2-4 min), so we start
131
+ // core Supabase services first with --wait, then launch optional services in the
132
+ // background without blocking.
133
+ const hasOptionalServices = args.all || getEnabledServices(manifest).length > 0;
134
+
135
+ if (hasOptionalServices) {
136
+ const coreCmd = buildComposeCommand('up -d --wait', { services: {} }, { prod: args.prod });
137
+ const fullCmd = buildComposeCommand('up -d', manifest, { prod: args.prod, all: args.all });
138
+
139
+ if (args.dryRun) {
140
+ console.log('Phase 1 (core, blocking):', coreCmd);
141
+ console.log('Phase 1.5 (service DB init): run idempotent init scripts via exec -T db bash');
142
+ console.log('Phase 2 (optional, background):', fullCmd);
143
+ console.log('With port env:', portEnv);
144
+ process.exit(0);
145
+ }
146
+
147
+ // Phase 1: Start core services and wait until healthy (~17s)
148
+ if (args.verbose) console.log('Phase 1 — core services:', coreCmd);
149
+ try {
150
+ execSync(coreCmd, execOpts);
151
+ } catch (error) {
152
+ diagnoseFailure();
153
+ process.exit(error.status || 1);
154
+ }
155
+
156
+ // Phase 1.5: Run idempotent DB init scripts for services that need their own database/user.
157
+ // Postgres skips /docker-entrypoint-initdb.d/ when the data volume already exists,
158
+ // so we pipe the scripts into the running container to ensure roles/databases exist.
159
+ runServiceDbInit(manifest, { all: args.all, verbose: args.verbose });
160
+
161
+ // Phase 2: Start optional services in background (no --wait)
162
+ // Core services are already running so Docker skips them (idempotent up -d)
163
+ if (args.verbose) console.log('Phase 2 — optional services:', fullCmd);
164
+ console.log('Starting optional services in background...');
165
+ try {
166
+ execSync(fullCmd, execOpts);
167
+ } catch (error) {
168
+ // Non-fatal: optional services failing to launch shouldn't block dev
169
+ console.warn('Warning: some optional services may have failed to start.');
170
+ }
171
+
172
+ } else {
173
+ const cmd = buildComposeCommand('up -d --wait', manifest, { prod: args.prod, all: args.all });
174
+
175
+ if (args.dryRun) {
176
+ console.log('Would run:', cmd);
177
+ console.log('With port env:', portEnv);
178
+ process.exit(0);
179
+ }
180
+
181
+ if (args.verbose) console.log('Running:', cmd);
182
+
183
+ try {
184
+ execSync(cmd, execOpts);
185
+ } catch (error) {
186
+ diagnoseFailure();
187
+ process.exit(error.status || 1);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Check unhealthy containers for common issues and print helpful messages.
193
+ */
194
+ function diagnoseFailure() {
195
+ try {
196
+ const logs = execSync('docker compose logs --tail 20 auth rest storage 2>&1', {
197
+ encoding: 'utf-8',
198
+ env: { ...process.env, ...getPortEnvVars() },
199
+ });
200
+
201
+ if (logs.includes('password authentication failed')) {
202
+ console.error(`
203
+ \x1b[33m┌──────────────────────────────────────────────────────────┐
204
+ │ Database credentials mismatch detected │
205
+ │ │
206
+ │ The database volume was initialized with different │
207
+ │ credentials than your current .env.local. │
208
+ │ │
209
+ │ This happens when you recreate a project without │
210
+ │ removing the old Docker volumes. │
211
+ │ │
212
+ │ Fix: vibecarbon reset │
213
+ │ │
214
+ │ This will remove volumes and reinitialize the database. │
215
+ │ ⚠ All local database data will be lost. │
216
+ └──────────────────────────────────────────────────────────┘\x1b[0m
217
+ `);
218
+ }
219
+ } catch {
220
+ // Diagnostic check failed, don't block the error exit
221
+ }
222
+ }
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Generate dev config files from templates
4
+ # Replaces {{PLACEHOLDERS}} with hardcoded dev values
5
+ #
6
+ # Usage:
7
+ # ./generate-dev-configs.sh # Only generate missing files
8
+ # ./generate-dev-configs.sh --force # Regenerate all files
9
+ #
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ CARBON_DIR="$(dirname "$SCRIPT_DIR")"
14
+
15
+ FORCE=false
16
+ if [[ "${1:-}" == "--force" ]]; then
17
+ FORCE=true
18
+ fi
19
+
20
+ cd "$CARBON_DIR"
21
+
22
+ # Dev value substitutions (add new placeholders here).
23
+ # Note: ANON_KEY and SERVICE_ROLE_KEY are signed at runtime by
24
+ # scripts/_dev-jwt.mjs against JWT_SECRET. Committing literal JWT
25
+ # strings tripped GitHub's secret-scanner on every push; signing on
26
+ # the fly keeps the dev workflow deterministic without leaking
27
+ # service-role-shaped values into git history.
28
+ JWT_SECRET_VALUE="local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded"
29
+ ANON_KEY_VALUE=$(JWT_SECRET="$JWT_SECRET_VALUE" node "$SCRIPT_DIR/_dev-jwt.mjs" anon)
30
+ SERVICE_ROLE_KEY_VALUE=$(JWT_SECRET="$JWT_SECRET_VALUE" node "$SCRIPT_DIR/_dev-jwt.mjs" service_role)
31
+
32
+ declare -A SUBSTITUTIONS=(
33
+ ["{{PROJECT_NAME}}"]="vibecarbon"
34
+ ["{{ANON_KEY}}"]="$ANON_KEY_VALUE"
35
+ ["{{SERVICE_ROLE_KEY}}"]="$SERVICE_ROLE_KEY_VALUE"
36
+ ["{{JWT_SECRET}}"]="$JWT_SECRET_VALUE"
37
+ ["{{DB_PASSWORD}}"]="local-postgres-password-for-dev-only"
38
+ ["{{ADMIN_EMAIL}}"]="admin@localhost"
39
+ ["{{ADMIN_PASSWORD}}"]="local-admin-password"
40
+ ["{{GRAFANA_PASSWORD}}"]="admin"
41
+ ["{{N8N_PASSWORD}}"]="local-n8n-password"
42
+ ["{{REALTIME_SECRET}}"]="local-realtime-secret-padded"
43
+ ["{{VAULT_ENC_KEY}}"]="local-vault-encryption-key-1234567890"
44
+ ["{{DB_ENC_KEY}}"]="devdbencryptkey1"
45
+ )
46
+
47
+ # Files that need dev variants (relative to carbon/)
48
+ # Format: "template_path:dev_path"
49
+ declare -a CONFIG_PAIRS=(
50
+ # Grafana dashboards
51
+ "volumes/grafana/provisioning/dashboards/dashboards.yml:volumes/grafana/provisioning/dashboards/dashboards.dev.yml"
52
+ # Database initialization (only super-admin needs dev variant — roles/services use runtime env vars)
53
+ "volumes/db/super-admin.sql:volumes/db/super-admin.dev.sql"
54
+ )
55
+
56
+ # Directories where all files need dev variants
57
+ declare -a CONFIG_DIRS=(
58
+ "volumes/grafana/dashboards:volumes/grafana/dashboards.dev"
59
+ )
60
+
61
+ generate_dev_file() {
62
+ local src="$1"
63
+ local dest="$2"
64
+
65
+ if [[ ! -f "$src" ]]; then
66
+ echo " SKIP: $src (template not found)"
67
+ return
68
+ fi
69
+
70
+ if [[ -f "$dest" && "$FORCE" == "false" ]]; then
71
+ echo " SKIP: $dest (exists, use --force to regenerate)"
72
+ return
73
+ fi
74
+
75
+ # Start with source content
76
+ local content
77
+ content=$(<"$src")
78
+
79
+ # Apply all substitutions
80
+ for placeholder in "${!SUBSTITUTIONS[@]}"; do
81
+ local value="${SUBSTITUTIONS[$placeholder]}"
82
+ content="${content//${placeholder}/${value}}"
83
+ done
84
+
85
+ # Write dev file
86
+ mkdir -p "$(dirname "$dest")"
87
+ echo "$content" > "$dest"
88
+
89
+ if [[ "$FORCE" == "true" ]]; then
90
+ echo " REGENERATED: $dest"
91
+ else
92
+ echo " CREATED: $dest"
93
+ fi
94
+ }
95
+
96
+ echo "Generating dev config files..."
97
+ echo ""
98
+
99
+ # Process individual file pairs
100
+ echo "Config files:"
101
+ for pair in "${CONFIG_PAIRS[@]}"; do
102
+ src="${pair%%:*}"
103
+ dest="${pair##*:}"
104
+ generate_dev_file "$src" "$dest"
105
+ done
106
+
107
+ echo ""
108
+
109
+ # Process directories
110
+ echo "Config directories:"
111
+ for dir_pair in "${CONFIG_DIRS[@]}"; do
112
+ src_dir="${dir_pair%%:*}"
113
+ dest_dir="${dir_pair##*:}"
114
+
115
+ if [[ ! -d "$src_dir" ]]; then
116
+ echo " SKIP: $src_dir (not found)"
117
+ continue
118
+ fi
119
+
120
+ mkdir -p "$dest_dir"
121
+
122
+ for src_file in "$src_dir"/*; do
123
+ if [[ -f "$src_file" ]]; then
124
+ filename=$(basename "$src_file")
125
+ generate_dev_file "$src_file" "$dest_dir/$filename"
126
+ fi
127
+ done
128
+ done
129
+
130
+ echo ""
131
+ echo "Done. Remember to verify the generated files before committing."
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Build-time RSS feed generator.
3
+ *
4
+ * Run: npx tsx scripts/generate-rss.ts
5
+ *
6
+ * Reads blog post frontmatter from content/blog/*.mdx and generates an RSS feed.
7
+ */
8
+
9
+ import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
+ import { dirname, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+
15
+ interface PostMeta {
16
+ slug: string;
17
+ title: string;
18
+ description: string;
19
+ date: string;
20
+ author?: string;
21
+ }
22
+
23
+ function loadSiteUrl(): string {
24
+ try {
25
+ const envContent = readFileSync(resolve(__dirname, '../.env.local'), 'utf-8');
26
+ const match = envContent.match(/^SITE_URL=["']?(.+?)["']?\s*$/m);
27
+ if (match) return match[1];
28
+ } catch {
29
+ // .env.local may not exist in CI
30
+ }
31
+ return process.env.SITE_URL || 'http://localhost:5173';
32
+ }
33
+
34
+ function loadProjectName(): string {
35
+ try {
36
+ const pkgContent = readFileSync(resolve(__dirname, '../package.json'), 'utf-8');
37
+ const pkg = JSON.parse(pkgContent);
38
+ return pkg.name || 'My SaaS';
39
+ } catch {
40
+ return 'My SaaS';
41
+ }
42
+ }
43
+
44
+ function parseFrontmatter(content: string): Record<string, string> {
45
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
46
+ if (!match) return {};
47
+ const fm: Record<string, string> = {};
48
+ for (const line of match[1].split('\n')) {
49
+ const [key, ...rest] = line.split(':');
50
+ if (key && rest.length) {
51
+ fm[key.trim()] = rest.join(':').trim().replace(/^["']|["']$/g, '');
52
+ }
53
+ }
54
+ return fm;
55
+ }
56
+
57
+ function loadPosts(): PostMeta[] {
58
+ const blogDir = resolve(__dirname, '../content/blog');
59
+ const files = readdirSync(blogDir).filter((f) => f.endsWith('.mdx'));
60
+
61
+ return files
62
+ .map((file) => {
63
+ const content = readFileSync(resolve(blogDir, file), 'utf-8');
64
+ const fm = parseFrontmatter(content);
65
+ return {
66
+ slug: file.replace('.mdx', ''),
67
+ title: fm.title || file.replace('.mdx', ''),
68
+ description: fm.description || '',
69
+ date: fm.date || new Date().toISOString(),
70
+ author: fm.author,
71
+ };
72
+ })
73
+ .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
74
+ }
75
+
76
+ function generateRss(posts: PostMeta[], siteUrl: string, projectName: string): string {
77
+ const items = posts
78
+ .map(
79
+ (post) => ` <item>
80
+ <title>${escapeXml(post.title)}</title>
81
+ <link>${siteUrl}/blog/${post.slug}</link>
82
+ <description>${escapeXml(post.description)}</description>
83
+ <pubDate>${new Date(post.date).toUTCString()}</pubDate>
84
+ <guid>${siteUrl}/blog/${post.slug}</guid>
85
+ </item>`,
86
+ )
87
+ .join('\n');
88
+
89
+ return `<?xml version="1.0" encoding="UTF-8"?>
90
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
91
+ <channel>
92
+ <title>${escapeXml(projectName)} Blog</title>
93
+ <link>${siteUrl}/blog</link>
94
+ <description>Updates, guides, and insights from ${escapeXml(projectName)}.</description>
95
+ <language>en-us</language>
96
+ <atom:link href="${siteUrl}/rss.xml" rel="self" type="application/rss+xml"/>
97
+ ${items}
98
+ </channel>
99
+ </rss>
100
+ `;
101
+ }
102
+
103
+ function escapeXml(str: string): string {
104
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
105
+ }
106
+
107
+ const siteUrl = loadSiteUrl();
108
+ const projectName = loadProjectName();
109
+ const posts = loadPosts();
110
+ const rss = generateRss(posts, siteUrl, projectName);
111
+
112
+ const outDir = resolve(__dirname, '../dist/client');
113
+ mkdirSync(outDir, { recursive: true });
114
+ writeFileSync(resolve(outDir, 'rss.xml'), rss);
115
+
116
+ console.log(`RSS feed generated with ${posts.length} posts → dist/client/rss.xml`);
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Build-time sitemap generator.
3
+ *
4
+ * Run: npx tsx scripts/generate-sitemap.ts
5
+ *
6
+ * Reads SITE_URL from .env.local and outputs sitemap.xml to dist/client/.
7
+ * Includes static routes and dynamic blog post routes.
8
+ */
9
+
10
+ import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { dirname, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+
16
+ // Public routes that should be indexed
17
+ const PUBLIC_ROUTES = ['/', '/login', '/signup', '/blog', '/changelog', '/docs'];
18
+
19
+ function loadSiteUrl(): string {
20
+ try {
21
+ const envContent = readFileSync(resolve(__dirname, '../.env.local'), 'utf-8');
22
+ const match = envContent.match(/^SITE_URL=["']?(.+?)["']?\s*$/m);
23
+ if (match) return match[1];
24
+ } catch {
25
+ // .env.local may not exist in CI
26
+ }
27
+ return process.env.SITE_URL || 'http://localhost:5173';
28
+ }
29
+
30
+ function getBlogSlugs(): string[] {
31
+ try {
32
+ const blogDir = resolve(__dirname, '../content/blog');
33
+ return readdirSync(blogDir)
34
+ .filter((f) => f.endsWith('.mdx'))
35
+ .map((f) => f.replace('.mdx', ''));
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
40
+
41
+ function getChangelogSlugs(): string[] {
42
+ try {
43
+ const changelogDir = resolve(__dirname, '../content/changelog');
44
+ return readdirSync(changelogDir)
45
+ .filter((f) => f.endsWith('.mdx'))
46
+ .map((f) => f.replace('.mdx', ''));
47
+ } catch {
48
+ return [];
49
+ }
50
+ }
51
+
52
+ function getDocsSlugs(): string[] {
53
+ try {
54
+ const docsDir = resolve(__dirname, '../content/docs');
55
+ return readdirSync(docsDir)
56
+ .filter((f) => f.endsWith('.mdx'))
57
+ .map((f) => f.replace('.mdx', ''));
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ function generateSitemap(siteUrl: string): string {
64
+ const today = new Date().toISOString().split('T')[0];
65
+ const blogSlugs = getBlogSlugs();
66
+ const changelogSlugs = getChangelogSlugs();
67
+ const docsSlugs = getDocsSlugs();
68
+ const allRoutes = [
69
+ ...PUBLIC_ROUTES,
70
+ ...blogSlugs.map((s) => `/blog/${s}`),
71
+ ...changelogSlugs.map((s) => `/changelog/${s}`),
72
+ ...docsSlugs.map((s) => `/docs/${s}`),
73
+ ];
74
+
75
+ const urls = allRoutes
76
+ .map(
77
+ (route) => ` <url>
78
+ <loc>${siteUrl}${route === '/' ? '' : route}</loc>
79
+ <lastmod>${today}</lastmod>
80
+ </url>`,
81
+ )
82
+ .join('\n');
83
+
84
+ return `<?xml version="1.0" encoding="UTF-8"?>
85
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
86
+ ${urls}
87
+ </urlset>
88
+ `;
89
+ }
90
+
91
+ const siteUrl = loadSiteUrl();
92
+ const sitemap = generateSitemap(siteUrl);
93
+
94
+ const outDir = resolve(__dirname, '../dist/client');
95
+ mkdirSync(outDir, { recursive: true });
96
+ writeFileSync(resolve(outDir, 'sitemap.xml'), sitemap);
97
+
98
+ const blogCount = getBlogSlugs().length;
99
+ const changelogCount = getChangelogSlugs().length;
100
+ const docsCount = getDocsSlugs().length;
101
+ const totalUrls = PUBLIC_ROUTES.length + blogCount + changelogCount + docsCount;
102
+ console.log(`Sitemap generated with ${totalUrls} URLs (${blogCount} blog, ${changelogCount} changelog, ${docsCount} docs) → dist/client/sitemap.xml`);
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Apply Kubernetes manifests using Kustomize
4
+ *
5
+ * Usage:
6
+ * node scripts/k8s-apply.js # Apply local overlay
7
+ * node scripts/k8s-apply.js production-nbg1 # Apply specific overlay
8
+ * node scripts/k8s-apply.js --dry-run # Show what would be applied
9
+ */
10
+
11
+ import { execSync } from 'node:child_process';
12
+ import { existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { loadManifest, getEnabledFeatures, parseArgs } from './lib/manifest.js';
15
+
16
+ const args = parseArgs(process.argv.slice(2));
17
+
18
+ if (args.help) {
19
+ console.log(`
20
+ Usage: node scripts/k8s-apply.js [overlay] [options]
21
+
22
+ Arguments:
23
+ overlay Kustomize overlay to apply (default: local)
24
+ Available: local, production-nbg1, production-hel1
25
+
26
+ Options:
27
+ --dry-run Show what would be applied without executing
28
+ --verbose Verbose output
29
+ -h, --help Show this help message
30
+
31
+ Examples:
32
+ node scripts/k8s-apply.js # Apply local overlay
33
+ node scripts/k8s-apply.js production-nbg1 # Apply production overlay
34
+ pnpm k8s:apply # Via package.json script
35
+ `);
36
+ process.exit(0);
37
+ }
38
+
39
+ const manifest = loadManifest();
40
+ const overlay = args.positional[0] || 'local';
41
+ const overlayPath = join(process.cwd(), 'k8s', 'overlays', overlay);
42
+
43
+ if (!existsSync(overlayPath)) {
44
+ console.error(`Error: Overlay '${overlay}' not found at ${overlayPath}`);
45
+ console.error('Available overlays: local, production-nbg1, production-hel1');
46
+ process.exit(1);
47
+ }
48
+
49
+ const features = getEnabledFeatures(manifest);
50
+ if (args.verbose && features.length > 0) {
51
+ console.log('Enabled features:', features.join(', '));
52
+ }
53
+
54
+ // Build the kubectl apply command
55
+ const dryRunFlag = args.dryRun ? ' --dry-run=client' : '';
56
+ const cmd = `kubectl apply -k ${overlayPath}${dryRunFlag}`;
57
+
58
+ if (args.dryRun) {
59
+ console.log('Would run:', cmd);
60
+ console.log('\nDry run output:');
61
+ }
62
+
63
+ if (args.verbose) {
64
+ console.log('Running:', cmd);
65
+ }
66
+
67
+ try {
68
+ execSync(cmd, { stdio: 'inherit' });
69
+ } catch (error) {
70
+ process.exit(error.status || 1);
71
+ }