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,74 @@
1
+ /**
2
+ * Declarative Compose Reconciler
3
+ * Renders the state and scripts for remote reconciliation
4
+ */
5
+
6
+ /**
7
+ * Render the reconcile.sh script
8
+ * @param {string} projectName
9
+ * @param {string} composeFlags
10
+ * @returns {string}
11
+ */
12
+ export function renderReconcileScript(projectName, composeFlags) {
13
+ return `#!/bin/bash
14
+ set -e
15
+
16
+ # Declarative Compose Reconciler
17
+ # Generated by Vibecarbon
18
+
19
+ PROJECT_DIR="/opt/${projectName}"
20
+ cd "$PROJECT_DIR"
21
+
22
+ echo "Reconciling project: ${projectName}..."
23
+
24
+ # 1. Pull latest images if needed.
25
+ # --ignore-buildable only skips services with build but no image. Services
26
+ # with both build and image sections (e.g. db) are NOT skipped and will
27
+ # warn "access denied" since the tag is local-only. Non-fatal; the actual
28
+ # build happens at step 2 via --build.
29
+ echo "Pulling images..."
30
+ docker compose ${composeFlags} pull --policy missing --ignore-buildable || true
31
+
32
+ # 2. Re-create containers if state differs.
33
+ # --build builds any service with a build section whose image isn't already
34
+ # present locally. In prod, app build is reset (image-only via APP_IMAGE);
35
+ # db keeps its build context and gets compiled here on first deploy. BuildKit
36
+ # cache makes subsequent re-deploys a no-op when the build context is unchanged.
37
+ echo "Updating containers..."
38
+ docker compose ${composeFlags} up -d --remove-orphans --build
39
+
40
+ # 3. Ensure systemd service is active (if applicable)
41
+ if [ -f "/etc/systemd/system/${projectName}.service" ]; then
42
+ echo "Ensuring systemd service is active..."
43
+ systemctl daemon-reload
44
+ systemctl enable "${projectName}.service"
45
+ fi
46
+
47
+ echo "Reconciliation complete."
48
+ `;
49
+ }
50
+
51
+ /**
52
+ * Render the systemd service unit
53
+ * @param {string} projectName
54
+ * @param {string} composeFlags
55
+ * @returns {string}
56
+ */
57
+ export function renderSystemdUnit(projectName, composeFlags) {
58
+ return `[Unit]
59
+ Description=Vibecarbon Project: ${projectName}
60
+ After=docker.service
61
+ Requires=docker.service
62
+
63
+ [Service]
64
+ Type=oneshot
65
+ RemainAfterExit=yes
66
+ WorkingDirectory=/opt/${projectName}
67
+ ExecStart=/usr/bin/docker compose ${composeFlags} up -d --remove-orphans
68
+ ExecStop=/usr/bin/docker compose ${composeFlags} down
69
+ Restart=on-failure
70
+
71
+ [Install]
72
+ WantedBy=multi-user.target
73
+ `;
74
+ }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * GitHub Integration & Workflows
3
+ * Logic for repository setup, environment secrets, and CI/CD generation
4
+ */
5
+
6
+ import { existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import * as p from '@clack/prompts';
9
+ import { c } from '../colors.js';
10
+ import { checkDependency, runCommand } from '../command.js';
11
+ import { buildGitAddArgv } from '../project.js';
12
+
13
+ /**
14
+ * Check if GitHub CLI is authenticated
15
+ * @returns {boolean}
16
+ */
17
+ export function checkGitHubAuth() {
18
+ try {
19
+ const result = runCommand('gh auth status', { silent: true, ignoreError: true });
20
+ return result !== null && result !== false;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get the authenticated GitHub username
28
+ * @returns {string|null}
29
+ */
30
+ export function getGitHubUsername() {
31
+ try {
32
+ const result = runCommand('gh api user -q .login', { silent: true, ignoreError: true });
33
+ return result?.trim() || null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Check if a GitHub repo already exists for this directory
41
+ * @returns {string|null}
42
+ */
43
+ export function checkExistingRepo() {
44
+ try {
45
+ const result = runCommand('gh repo view --json nameWithOwner -q .nameWithOwner', {
46
+ silent: true,
47
+ ignoreError: true,
48
+ });
49
+ return result?.trim() || null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Check if git remote 'origin' is configured
57
+ * @returns {string|null}
58
+ */
59
+ export function checkGitRemote() {
60
+ try {
61
+ const result = runCommand('git remote get-url origin', {
62
+ silent: true,
63
+ ignoreError: true,
64
+ });
65
+ return result?.trim() || null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Create a GitHub repository for the project
73
+ * @param {string} projectName
74
+ * @param {object} options
75
+ * @returns {Promise<object>}
76
+ */
77
+ export async function createGitHubRepository(projectName, options = {}) {
78
+ const s = p.spinner();
79
+ const visibility = options.isPublic ? '--public' : '--private';
80
+
81
+ // Ensure we have a git repo
82
+ if (!existsSync(join(process.cwd(), '.git'))) {
83
+ s.start('Initializing git repository');
84
+ runCommand('git init', { silent: true });
85
+ s.stop('Git repository initialized');
86
+ }
87
+
88
+ // Ensure we have at least one commit
89
+ const hasCommits = runCommand('git rev-parse HEAD', { silent: true, ignoreError: true });
90
+ if (!hasCommits) {
91
+ s.start('Creating initial commit');
92
+ runCommand(buildGitAddArgv(), { silent: true, ignoreError: true });
93
+ runCommand('git commit -m "Initial commit - Vibecarbon project"', { silent: true });
94
+ s.stop('Initial commit created');
95
+ }
96
+
97
+ s.start(`Creating GitHub repository: ${projectName}`);
98
+ try {
99
+ // Create repo with source and remote in one command
100
+ runCommand(['gh', 'repo', 'create', projectName, visibility, '--source=.', '--remote=origin'], {
101
+ silent: true,
102
+ timeout: 60000,
103
+ });
104
+ s.stop('GitHub repository created');
105
+
106
+ // Get the repo URL
107
+ const repoUrl = runCommand('gh repo view --json url -q .url', { silent: true })?.trim();
108
+ return { success: true, url: repoUrl, name: projectName };
109
+ } catch (error) {
110
+ s.stop('Failed to create GitHub repository');
111
+ return { success: false, error: error.message };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Commit changes and push to GitHub to trigger deployment
117
+ * @param {string} branchName
118
+ * @param {string} workflowPath
119
+ * @param {object} options
120
+ * @returns {Promise<object>}
121
+ */
122
+ export async function commitAndPush(branchName, workflowPath, options = {}) {
123
+ const s = p.spinner();
124
+ const skipCiStr = options.skipCi ? ' [skip ci]' : '';
125
+
126
+ // Stage the workflow file
127
+ s.start('Staging deployment workflow');
128
+ runCommand(buildGitAddArgv(process.cwd(), [workflowPath]), { silent: true, ignoreError: true });
129
+ s.stop('Changes staged');
130
+
131
+ // Commit
132
+ s.start('Committing changes');
133
+ const commitMessage = `Add deployment workflow and configuration${skipCiStr}`;
134
+ const commitResult = runCommand(['git', 'commit', '-m', commitMessage], {
135
+ silent: true,
136
+ ignoreError: true,
137
+ });
138
+ if (!commitResult) {
139
+ s.stop('No new changes to commit');
140
+ } else {
141
+ s.stop('Changes committed');
142
+ }
143
+
144
+ // Push
145
+ s.start(`Pushing to ${branchName} branch`);
146
+ try {
147
+ runCommand(['git', 'push', '-u', 'origin', branchName], { silent: true, timeout: 60000 });
148
+ s.stop(`Code pushed to ${branchName}`);
149
+ return { success: true };
150
+ } catch (error) {
151
+ s.stop(`Failed to push: ${error.message}`);
152
+ return { success: false, error: error.message };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Monitor GitHub Actions deployment progress
158
+ * @param {string} _repoName
159
+ * @param {number} timeout
160
+ * @returns {Promise<object>}
161
+ */
162
+ export async function monitorDeployment(_repoName, timeout = 600000) {
163
+ const s = p.spinner();
164
+ s.start('Waiting for GitHub Actions deployment');
165
+
166
+ const startTime = Date.now();
167
+ let lastStatus = '';
168
+
169
+ while (Date.now() - startTime < timeout) {
170
+ try {
171
+ const result = runCommand('gh run list --limit 1 --json status,conclusion,name,url', {
172
+ silent: true,
173
+ ignoreError: true,
174
+ });
175
+
176
+ if (result) {
177
+ const runs = JSON.parse(result);
178
+ if (runs.length > 0) {
179
+ const run = runs[0];
180
+ const status = run.status;
181
+
182
+ if (status !== lastStatus) {
183
+ lastStatus = status;
184
+ if (status === 'completed') {
185
+ if (run.conclusion === 'success') {
186
+ s.stop('Deployment complete!');
187
+ return { success: true, url: run.url };
188
+ }
189
+ s.stop(`Deployment ${run.conclusion}`);
190
+ return { success: false, conclusion: run.conclusion, url: run.url };
191
+ }
192
+ s.message(`Deployment ${status}...`);
193
+ }
194
+ }
195
+ }
196
+ } catch {
197
+ // Ignore errors, keep polling
198
+ }
199
+
200
+ await new Promise((r) => setTimeout(r, 10000));
201
+ }
202
+
203
+ s.stop('Deployment monitoring timed out');
204
+ return { success: false, timedOut: true };
205
+ }
206
+
207
+ /**
208
+ * Orchestrate GitHub integration setup
209
+ * @param {object} projectConfig
210
+ * @param {string} _branchName
211
+ * @param {object} options
212
+ * @returns {Promise<object>}
213
+ */
214
+ export async function setupGitHubIntegration(projectConfig, _branchName, options = {}) {
215
+ const { defaultCreate = true, isPublic = false, skipPrompts = false } = options;
216
+
217
+ // Check if gh CLI is available and authenticated
218
+ if (!checkDependency('gh', 'GitHub CLI')) {
219
+ p.log.warn('GitHub CLI (gh) not installed - skipping automatic GitHub setup');
220
+ return { skipped: true, reason: 'gh_not_installed' };
221
+ }
222
+
223
+ if (!checkGitHubAuth()) {
224
+ p.log.warn('GitHub CLI not authenticated');
225
+ p.log.info('Run: gh auth login');
226
+ return { skipped: true, reason: 'not_authenticated' };
227
+ }
228
+
229
+ // Check for existing repo
230
+ const existingRepo = checkExistingRepo();
231
+ const existingRemote = checkGitRemote();
232
+
233
+ if (existingRepo) {
234
+ p.log.info(`Using existing repository: ${c.bold(existingRepo)}`);
235
+ return { existing: true, repoName: existingRepo };
236
+ }
237
+
238
+ if (existingRemote && !existingRemote.includes('github.com')) {
239
+ p.log.warn(`Git remote points to non-GitHub URL: ${existingRemote}`);
240
+ if (!skipPrompts) {
241
+ const replace = await p.confirm({
242
+ message: 'Replace with GitHub remote?',
243
+ initialValue: false,
244
+ });
245
+ if (!replace) {
246
+ return { skipped: true, reason: 'non_github_remote' };
247
+ }
248
+ runCommand('git remote remove origin', { silent: true, ignoreError: true });
249
+ } else {
250
+ return { skipped: true, reason: 'non_github_remote' };
251
+ }
252
+ }
253
+
254
+ // Prompt for repo creation
255
+ let shouldCreate = defaultCreate;
256
+ if (!skipPrompts) {
257
+ shouldCreate = await p.confirm({
258
+ message: 'Create GitHub repository for auto deployment (CI/CD)?',
259
+ initialValue: true,
260
+ });
261
+ }
262
+
263
+ if (!shouldCreate) {
264
+ return { skipped: true, reason: 'user_declined' };
265
+ }
266
+
267
+ // Ask about visibility
268
+ let repoIsPublic = isPublic;
269
+ if (!skipPrompts && shouldCreate) {
270
+ const visibility = await p.select({
271
+ message: 'Repository visibility',
272
+ options: [
273
+ { value: 'private', label: 'Private', hint: 'recommended' },
274
+ { value: 'public', label: 'Public' },
275
+ ],
276
+ initialValue: 'private',
277
+ });
278
+ repoIsPublic = visibility === 'public';
279
+ }
280
+
281
+ // Create the repository
282
+ const result = await createGitHubRepository(projectConfig.projectName, {
283
+ isPublic: repoIsPublic,
284
+ });
285
+
286
+ if (result.success) {
287
+ return { created: true, repoName: projectConfig.projectName, url: result.url };
288
+ }
289
+
290
+ p.log.error(`Failed to create repository: ${result.error}`);
291
+ return { skipped: true, reason: 'creation_failed', error: result.error };
292
+ }
293
+
294
+ /**
295
+ * Setup GitHub environment and secrets
296
+ * @param {string} _projectName
297
+ * @param {string} envName
298
+ * @param {object} secrets
299
+ * @returns {Promise<boolean>}
300
+ */
301
+ export async function setupGitHubEnvironment(_projectName, envName, secrets) {
302
+ const s = p.spinner();
303
+ s.start(`Setting up GitHub environment: ${envName}`);
304
+
305
+ try {
306
+ const repoInfo = runCommand('gh repo view --json nameWithOwner -q .nameWithOwner', {
307
+ silent: true,
308
+ }).trim();
309
+
310
+ // Create environment
311
+ runCommand(['gh', 'api', `repos/${repoInfo}/environments/${envName}`, '-X', 'PUT'], {
312
+ silent: true,
313
+ ignoreError: true,
314
+ });
315
+
316
+ // Set secrets for the environment
317
+ for (const [key, value] of Object.entries(secrets)) {
318
+ if (value && typeof value === 'string' && value.length > 0) {
319
+ runCommand(['gh', 'secret', 'set', key, '--env', envName, '--body', value], {
320
+ silent: true,
321
+ ignoreError: true,
322
+ });
323
+ }
324
+ }
325
+
326
+ s.stop(`GitHub environment '${envName}' configured`);
327
+ return true;
328
+ } catch (error) {
329
+ s.stop(`Failed to setup GitHub environment: ${error.message}`);
330
+ return false;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Create or checkout a git branch for deployment
336
+ * @param {string} branchName
337
+ * @returns {Promise<boolean>}
338
+ */
339
+ export async function createGitBranch(branchName) {
340
+ const s = p.spinner();
341
+
342
+ if (branchName === 'main' || branchName === 'master') {
343
+ p.log.info(`Using '${branchName}' branch for deployment (push manually when ready)`);
344
+ return true;
345
+ }
346
+
347
+ try {
348
+ const hasCommits = runCommand('git rev-parse HEAD', {
349
+ silent: true,
350
+ ignoreError: true,
351
+ });
352
+ if (!hasCommits) {
353
+ p.log.warn(`No commits yet - create initial commit, then run: git checkout -b ${branchName}`);
354
+ return true;
355
+ }
356
+
357
+ const currentBranch = runCommand(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], {
358
+ encoding: 'utf-8',
359
+ }).trim();
360
+
361
+ const existsLocally = runCommand(['git', 'rev-parse', '--verify', branchName], {
362
+ silent: true,
363
+ ignoreError: true,
364
+ });
365
+
366
+ if (existsLocally) {
367
+ s.start(`Checking out existing branch: ${branchName}`);
368
+ runCommand(['git', 'checkout', branchName], { silent: true });
369
+ s.stop(`Switched to branch '${branchName}'`);
370
+ } else {
371
+ s.start(`Creating branch '${branchName}' from '${currentBranch}'`);
372
+ runCommand(['git', 'checkout', '-b', branchName], { silent: true });
373
+ s.stop(`Created branch '${branchName}' from '${currentBranch}'`);
374
+ }
375
+
376
+ p.log.info(`Push when ready: git push -u origin ${branchName}`);
377
+ return true;
378
+ } catch (error) {
379
+ s.stop(`Failed to create branch: ${error.message}`);
380
+ return false;
381
+ }
382
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Sideload primitives for local-first deploys.
3
+ *
4
+ * Three responsibilities:
5
+ * 1. Generate a unique local image tag (pure)
6
+ * 2. Inspect git state (sha + dirty)
7
+ * 3. Build + sideload to a compose-mode server (laptop → docker daemon)
8
+ *
9
+ * The k8s-mode sideload lives in src/lib/deploy/k8s/k3s.js (`sideloadK3s`).
10
+ * No registry round-trip; the image goes operator → target directly.
11
+ *
12
+ * See docs/superpowers/specs/2026-04-25-local-first-deploy-design.md §1.
13
+ */
14
+
15
+ import { execFileSync } from 'node:child_process';
16
+
17
+ const LOCAL_REGISTRY_PREFIX = 'vibecarbon-local';
18
+
19
+ /**
20
+ * Build a unique local image tag.
21
+ *
22
+ * Tag scheme: `<prefix>/<project>:<sha>[-dirty]-<timestamp>`. The
23
+ * `-dirty` marker fires when the working tree has uncommitted changes, so
24
+ * stale rebuilds from a clean tree don't collide with iterative
25
+ * uncommitted work. Timestamp is UTC YYYYMMDDHHMMSS for sortability.
26
+ *
27
+ * `prefix` defaults to the compose-mode default `vibecarbon-local`. The
28
+ * k8s-mode caller (buildAppImage in k8s/k3s.js) passes `10.0.1.1:5000` so
29
+ * the same tag works as both a sideload reference (kubelet `IfNotPresent`
30
+ * on static workers) and a registry-pull reference (CA-spawned workers
31
+ * pulling from master's local registry pod via `registries.yaml`).
32
+ *
33
+ * @param {{projectName: string, gitSha: string, isDirty: boolean, timestamp: string, prefix?: string}} args
34
+ * @returns {string}
35
+ */
36
+ export function generateLocalImageTag({
37
+ projectName,
38
+ gitSha,
39
+ isDirty,
40
+ timestamp,
41
+ prefix = LOCAL_REGISTRY_PREFIX,
42
+ }) {
43
+ const dirtyMarker = isDirty ? '-dirty' : '';
44
+ return `${prefix}/${projectName}:${gitSha}${dirtyMarker}-${timestamp}`;
45
+ }
46
+
47
+ /**
48
+ * Read git short-sha + dirty-state for `cwd`. Both halves swallow errors and
49
+ * return safe fallbacks: `nogit`/`false`. The caller (buildLocalImage)
50
+ * doesn't fail just because the project is outside a git repo or git isn't
51
+ * on PATH — sketch-mode users may not have committed yet.
52
+ *
53
+ * @param {string} cwd
54
+ * @returns {{gitSha: string, isDirty: boolean}}
55
+ */
56
+ export function inspectGitState(cwd) {
57
+ let gitSha = 'nogit';
58
+ let isDirty = false;
59
+ try {
60
+ const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], {
61
+ cwd,
62
+ encoding: 'utf-8',
63
+ stdio: ['ignore', 'pipe', 'ignore'],
64
+ });
65
+ gitSha = sha.trim() || 'nogit';
66
+ } catch {
67
+ return { gitSha: 'nogit', isDirty: false };
68
+ }
69
+ try {
70
+ const status = execFileSync('git', ['status', '--porcelain'], {
71
+ cwd,
72
+ encoding: 'utf-8',
73
+ stdio: ['ignore', 'pipe', 'ignore'],
74
+ });
75
+ isDirty = status.trim().length > 0;
76
+ } catch {
77
+ // git status failed but rev-parse worked — non-fatal, treat as clean.
78
+ }
79
+ return { gitSha, isDirty };
80
+ }
81
+
82
+ /**
83
+ * Build a local container image and return its unique tag.
84
+ *
85
+ * Combines `inspectGitState` for cache-busting inputs, `generateLocalImageTag`
86
+ * for the tag scheme, and a `docker build` shell-out. Side-effecting; the
87
+ * docker build step inherits stdio so the operator sees layer progress.
88
+ *
89
+ * `tagPrefix` defaults to `vibecarbon-local` (compose-mode behavior). k8s
90
+ * mode passes `10.0.1.1:5000` so the resulting tag is a valid registry
91
+ * reference that CA-spawned workers can pull from master's local registry
92
+ * pod (Phase 1 wired the registries.yaml; Phase 6 wires the push).
93
+ *
94
+ * @param {string} projectDir - Project root with the Dockerfile.
95
+ * @param {{projectName: string, timestamp?: string, rebuild?: boolean, tagPrefix?: string}} options
96
+ * @returns {{tag: string, gitSha: string, isDirty: boolean}}
97
+ */
98
+ export function buildLocalImage(projectDir, options) {
99
+ const { projectName, rebuild = false, tagPrefix } = options;
100
+ if (!projectName) {
101
+ throw new Error('buildLocalImage: projectName is required');
102
+ }
103
+ const timestamp = options.timestamp ?? defaultTimestamp();
104
+ const { gitSha, isDirty } = inspectGitState(projectDir);
105
+ const tag = generateLocalImageTag({
106
+ projectName,
107
+ gitSha,
108
+ isDirty,
109
+ timestamp,
110
+ prefix: tagPrefix,
111
+ });
112
+
113
+ const args = ['build'];
114
+ if (rebuild) args.push('--no-cache');
115
+ args.push('-t', tag, projectDir);
116
+
117
+ execFileSync('docker', args, { stdio: 'inherit' });
118
+ return { tag, gitSha, isDirty };
119
+ }
120
+
121
+ function defaultTimestamp() {
122
+ return new Date().toISOString().replace(/[-:T]/g, '').replace(/\..*$/, '');
123
+ }
124
+
125
+ /**
126
+ * Sideload a built local image to a compose-mode server.
127
+ *
128
+ * `set -o pipefail && docker save | gzip -1 | ssh target gunzip | docker load`
129
+ * streams the image tarball compressed over the wire — no temp file on
130
+ * either end, no registry. The image lands in the server's docker daemon
131
+ * and is immediately referenceable by tag for `docker compose up`.
132
+ *
133
+ * gzip on the wire: Node app images compress 3-5x (most layers are JS
134
+ * source + node_modules text), so a ~600MB save becomes ~150-200MB on
135
+ * the wire. The K8s sideload (sideloadK3s in k3s.js) used the same trick
136
+ * to drop sideload time from 23-29 min to 5-10 min on the operator's
137
+ * residential upload — compose-mode missed the optimization until now.
138
+ * `gzip -1` is universally present and dominant cost is network, not CPU.
139
+ *
140
+ * `pipefail` so a docker-save / gzip failure isn't masked by ssh's exit code.
141
+ * `StrictHostKeyChecking=accept-new` matches the convention used elsewhere
142
+ * in vibecarbon's ssh paths: trust-on-first-use, error on host-key change.
143
+ *
144
+ * @param {{tag: string, sshTarget: string, sshKey?: string}} args
145
+ */
146
+ export function sideloadCompose({ tag, sshTarget, sshKey }) {
147
+ // SSH options mirror remote-build.js's wrapper script — same tradeoffs:
148
+ // * StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null: ephemeral
149
+ // VPS IPs get recycled across e2e runs (and across customer
150
+ // redeploys after destroy). accept-new + the operator's real
151
+ // ~/.ssh/known_hosts would fail with "host key verification failed"
152
+ // whenever a recycled IP shows up with a new host key, even with
153
+ // BatchMode=yes (in fact especially then, since ssh can't prompt to
154
+ // update the entry). iter-perfwave4 hit exactly this on first run.
155
+ // * BatchMode=yes per feedback_ssh_batchmode_required.md (PR 1T):
156
+ // fast-fail rather than hang on interactive password prompt if the
157
+ // key fails.
158
+ // * ConnectTimeout=30: bound the network half of dial; sideload's own
159
+ // stream is unbounded (limited by docker save throughput).
160
+ const sshFlags = [
161
+ '-o',
162
+ 'StrictHostKeyChecking=no',
163
+ '-o',
164
+ 'UserKnownHostsFile=/dev/null',
165
+ '-o',
166
+ 'BatchMode=yes',
167
+ '-o',
168
+ 'ConnectTimeout=30',
169
+ ];
170
+ if (sshKey) sshFlags.unshift('-i', sshKey);
171
+ const cmd = `set -o pipefail && docker save ${tag} | gzip -1 | ssh ${sshFlags.join(' ')} ${sshTarget} 'gunzip | docker load'`;
172
+ // Capture stdio rather than inheriting it: ssh's "Permanently added" host-
173
+ // key warning + `docker load`'s "Loaded image:" success line both bypass
174
+ // the clack gutter when inherited and print flush-left, breaking the
175
+ // visual flow. On success we don't need either; on failure we surface
176
+ // the captured stderr for debugging via the thrown Error.
177
+ try {
178
+ execFileSync('bash', ['-c', cmd], { stdio: ['ignore', 'pipe', 'pipe'] });
179
+ } catch (err) {
180
+ const stderr = err.stderr?.toString().trim() || '';
181
+ const stdout = err.stdout?.toString().trim() || '';
182
+ // Surface whatever signal the operator can act on: child stderr first
183
+ // (real ssh / docker errors), then stdout, then the wrapping error's
184
+ // message (preserves "Connection refused" when execFileSync's own
185
+ // error string is the only thing we have).
186
+ const detail = [stderr, stdout, err.message].filter(Boolean).join('\n').trim();
187
+ const wrapped = new Error(`sideloadCompose failed (exit ${err.status ?? '?'}): ${detail}`);
188
+ wrapped.cause = err;
189
+ throw wrapped;
190
+ }
191
+ }