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.
- package/LICENSE +663 -0
- package/README.md +188 -0
- package/carbon/.claude/hooks/task-completed-gate.sh +19 -0
- package/carbon/.claude/hooks/teammate-idle-gate.sh +29 -0
- package/carbon/.claude/settings.json +29 -0
- package/carbon/.cursor/rules/vibecarbon.mdc +6 -0
- package/carbon/.dockerignore +57 -0
- package/carbon/.env.example +219 -0
- package/carbon/.github/copilot-instructions.md +3 -0
- package/carbon/.github/workflows/deploy.yml +346 -0
- package/carbon/.github/workflows/vibecarbon-build.yml +81 -0
- package/carbon/.windsurfrules +74 -0
- package/carbon/AGENTS.md +422 -0
- package/carbon/CLAUDE.md +3 -0
- package/carbon/DEVELOPMENT.md +187 -0
- package/carbon/Dockerfile +98 -0
- package/carbon/LICENSE +21 -0
- package/carbon/PRODUCTION.md +364 -0
- package/carbon/README.md +193 -0
- package/carbon/TESTING.md +138 -0
- package/carbon/backup/Dockerfile +75 -0
- package/carbon/backup/backup.sh +95 -0
- package/carbon/backup/compose-backup.sh +140 -0
- package/carbon/biome.json +83 -0
- package/carbon/cloud-init/docker-ce-setup.yaml +55 -0
- package/carbon/cloud-init/k3s/master-init.sh +215 -0
- package/carbon/cloud-init/k3s/supabase-init.sh +167 -0
- package/carbon/cloud-init/k3s/worker-init.sh +147 -0
- package/carbon/components.json +24 -0
- package/carbon/content/blog/authentication-guide.mdx +34 -0
- package/carbon/content/blog/getting-started.mdx +41 -0
- package/carbon/content/changelog/v0-1-0.mdx +40 -0
- package/carbon/content/docs/analytics.mdx +90 -0
- package/carbon/content/docs/authentication.mdx +231 -0
- package/carbon/content/docs/background-jobs.mdx +116 -0
- package/carbon/content/docs/cli.mdx +630 -0
- package/carbon/content/docs/database.mdx +236 -0
- package/carbon/content/docs/deployment.mdx +227 -0
- package/carbon/content/docs/development.mdx +238 -0
- package/carbon/content/docs/environments.mdx +84 -0
- package/carbon/content/docs/getting-started.mdx +112 -0
- package/carbon/content/docs/legal/privacy-policy.mdx +89 -0
- package/carbon/content/docs/legal/terms-of-service.mdx +99 -0
- package/carbon/content/docs/optional-services.mdx +160 -0
- package/carbon/db/Dockerfile +23 -0
- package/carbon/docker-compose.dev-init.yml +5 -0
- package/carbon/docker-compose.metabase.override.yml +14 -0
- package/carbon/docker-compose.metabase.prod.yml +28 -0
- package/carbon/docker-compose.metabase.yml +84 -0
- package/carbon/docker-compose.n8n.override.yml +14 -0
- package/carbon/docker-compose.n8n.prod.yml +31 -0
- package/carbon/docker-compose.n8n.yml +97 -0
- package/carbon/docker-compose.observability.override.yml +18 -0
- package/carbon/docker-compose.observability.prod.yml +28 -0
- package/carbon/docker-compose.observability.yml +125 -0
- package/carbon/docker-compose.override.yml +28 -0
- package/carbon/docker-compose.prod.yml +294 -0
- package/carbon/docker-compose.yml +508 -0
- package/carbon/docker-entrypoint.sh +12 -0
- package/carbon/ha/activate-standby.sh +52 -0
- package/carbon/ha/primary-init.sql +36 -0
- package/carbon/ha/standby-init.sh +74 -0
- package/carbon/k8s/LICENSE +99 -0
- package/carbon/k8s/README.md +272 -0
- package/carbon/k8s/base/app/deployment.yaml +130 -0
- package/carbon/k8s/base/app/hpa.yaml +44 -0
- package/carbon/k8s/base/app/kustomization.yaml +10 -0
- package/carbon/k8s/base/app/network-policy.yaml +124 -0
- package/carbon/k8s/base/app/pdb.yaml +14 -0
- package/carbon/k8s/base/app/rbac.yaml +36 -0
- package/carbon/k8s/base/app/service.yaml +16 -0
- package/carbon/k8s/base/backup/cronjob.yaml +120 -0
- package/carbon/k8s/base/backup/kustomization.yaml +7 -0
- package/carbon/k8s/base/backup/network-policy.yaml +31 -0
- package/carbon/k8s/base/backup/rbac.yaml +7 -0
- package/carbon/k8s/base/cluster-autoscaler/deployment.yaml +103 -0
- package/carbon/k8s/base/cluster-autoscaler/kustomization.yaml +17 -0
- package/carbon/k8s/base/cluster-autoscaler/rbac.yaml +109 -0
- package/carbon/k8s/base/config/configmap.yaml +48 -0
- package/carbon/k8s/base/config/kustomization.yaml +8 -0
- package/carbon/k8s/base/hetzner-ccm/kustomization.yaml +9 -0
- package/carbon/k8s/base/hetzner-csi/kustomization.yaml +9 -0
- package/carbon/k8s/base/kustomization.yaml +43 -0
- package/carbon/k8s/base/namespace.yaml +7 -0
- package/carbon/k8s/base/network-policies.yaml +95 -0
- package/carbon/k8s/base/registry/kustomization.yaml +5 -0
- package/carbon/k8s/base/registry/local-registry.yaml +193 -0
- package/carbon/k8s/base/traefik/certificate.yaml +21 -0
- package/carbon/k8s/base/traefik/configmap.yaml +13 -0
- package/carbon/k8s/base/traefik/deployment.yaml +165 -0
- package/carbon/k8s/base/traefik/ingressroute.yaml +141 -0
- package/carbon/k8s/base/traefik/kustomization.yaml +11 -0
- package/carbon/k8s/base/traefik/middleware.yaml +109 -0
- package/carbon/k8s/base/traefik/network-policy.yaml +92 -0
- package/carbon/k8s/base/traefik/service.yaml +38 -0
- package/carbon/k8s/flux/README.md +49 -0
- package/carbon/k8s/flux/clusters/primary/vibecarbon.yaml +128 -0
- package/carbon/k8s/flux/clusters/standby/vibecarbon.yaml +83 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-release.yaml +38 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/helm-repository.yaml +16 -0
- package/carbon/k8s/gitops/cert-manager-webhook-hetzner/kustomization.yaml +10 -0
- package/carbon/k8s/gitops/supabase/helm-release.yaml +66 -0
- package/carbon/k8s/gitops/supabase/helm-repository.yaml +18 -0
- package/carbon/k8s/gitops/supabase/kustomization.yaml +33 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-cloudflare.yaml +51 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-hetzner.yaml +59 -0
- package/carbon/k8s/infra/cert-manager-resources/cluster-issuers-manual.yaml +43 -0
- package/carbon/k8s/infra/cert-manager-resources/kustomization.yaml +15 -0
- package/carbon/k8s/infra/kustomization.yaml +21 -0
- package/carbon/k8s/infra/traefik-crds/kustomization.yaml +10 -0
- package/carbon/k8s/overlays/local/configmap.yaml +23 -0
- package/carbon/k8s/overlays/local/ingressroute-studio.yaml +104 -0
- package/carbon/k8s/overlays/local/kustomization.yaml +215 -0
- package/carbon/k8s/overlays/local/namespace.yaml +8 -0
- package/carbon/k8s/overlays/local/secrets.yaml +32 -0
- package/carbon/k8s/overlays/production/kustomization.yaml +34 -0
- package/carbon/k8s/test-local.sh +318 -0
- package/carbon/k8s/values/supabase.values.yaml +133 -0
- package/carbon/package.json +154 -0
- package/carbon/runtime.Dockerfile +17 -0
- package/carbon/scripts/_dev-jwt.mjs +45 -0
- package/carbon/scripts/check-rls.ts +53 -0
- package/carbon/scripts/dev-init.js +191 -0
- package/carbon/scripts/dev.js +191 -0
- package/carbon/scripts/docker-down.js +63 -0
- package/carbon/scripts/docker-logs.js +59 -0
- package/carbon/scripts/docker-up.js +222 -0
- package/carbon/scripts/generate-dev-configs.sh +131 -0
- package/carbon/scripts/generate-rss.ts +116 -0
- package/carbon/scripts/generate-sitemap.ts +102 -0
- package/carbon/scripts/k8s-apply.js +71 -0
- package/carbon/scripts/k8s-delete.js +75 -0
- package/carbon/scripts/lib/manifest.js +176 -0
- package/carbon/scripts/secret-scan.mjs +278 -0
- package/carbon/scripts/validate-dev-configs.sh +101 -0
- package/carbon/src/client/App.tsx +202 -0
- package/carbon/src/client/assets/hyperformant-dark.svg +29 -0
- package/carbon/src/client/assets/hyperformant-light.svg +29 -0
- package/carbon/src/client/assets/logos/aider.svg +1 -0
- package/carbon/src/client/assets/logos/antigravity.svg +1 -0
- package/carbon/src/client/assets/logos/bolt.svg +1 -0
- package/carbon/src/client/assets/logos/claude-code.svg +1 -0
- package/carbon/src/client/assets/logos/copilot.svg +1 -0
- package/carbon/src/client/assets/logos/cursor.svg +1 -0
- package/carbon/src/client/assets/logos/gemini-cli.svg +1 -0
- package/carbon/src/client/assets/logos/lovable.svg +1 -0
- package/carbon/src/client/assets/logos/openai-codex.svg +1 -0
- package/carbon/src/client/assets/logos/v0.svg +1 -0
- package/carbon/src/client/assets/logos/windsurf.svg +1 -0
- package/carbon/src/client/assets/vibecarbon-icon.svg +19 -0
- package/carbon/src/client/assets/vibecarbon-logo-dark.svg +29 -0
- package/carbon/src/client/assets/vibecarbon-logo-light.svg +29 -0
- package/carbon/src/client/components/AIIntegrationSection.tsx +120 -0
- package/carbon/src/client/components/AppSidebar.tsx +760 -0
- package/carbon/src/client/components/ArchitectureSection.tsx +46 -0
- package/carbon/src/client/components/CTAFooter.tsx +59 -0
- package/carbon/src/client/components/ComparisonSection.tsx +132 -0
- package/carbon/src/client/components/ContentPanel.tsx +66 -0
- package/carbon/src/client/components/ContentSkeleton.tsx +21 -0
- package/carbon/src/client/components/ErrorBoundary.tsx +46 -0
- package/carbon/src/client/components/FAQSection.tsx +76 -0
- package/carbon/src/client/components/FileUpload.tsx +210 -0
- package/carbon/src/client/components/HeaderActions.tsx +17 -0
- package/carbon/src/client/components/Hero.tsx +608 -0
- package/carbon/src/client/components/ImpersonationBanner.tsx +31 -0
- package/carbon/src/client/components/LanguageSwitcher.tsx +67 -0
- package/carbon/src/client/components/Logo.tsx +87 -0
- package/carbon/src/client/components/LogoStrip.tsx +76 -0
- package/carbon/src/client/components/MetricsStrip.tsx +48 -0
- package/carbon/src/client/components/Nav.tsx +195 -0
- package/carbon/src/client/components/NewsletterSignup.tsx +147 -0
- package/carbon/src/client/components/NotificationDrawer.tsx +171 -0
- package/carbon/src/client/components/PageHeader.tsx +24 -0
- package/carbon/src/client/components/PlanGate.tsx +36 -0
- package/carbon/src/client/components/PricingSection.tsx +371 -0
- package/carbon/src/client/components/SEO.tsx +34 -0
- package/carbon/src/client/components/SmoothScroll.tsx +45 -0
- package/carbon/src/client/components/TechStackSection.tsx +165 -0
- package/carbon/src/client/components/WorkflowSection.tsx +604 -0
- package/carbon/src/client/components/admin/DockerLogs.tsx +276 -0
- package/carbon/src/client/components/admin/PerformanceMetrics.tsx +230 -0
- package/carbon/src/client/components/admin/ServiceCard.tsx +45 -0
- package/carbon/src/client/components/admin/ServicesStatus.tsx +296 -0
- package/carbon/src/client/components/architecture/ArchitectureDiagram.tsx +413 -0
- package/carbon/src/client/components/auth/AuthProvider.tsx +466 -0
- package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +35 -0
- package/carbon/src/client/components/effects/FresnelEdge.tsx +49 -0
- package/carbon/src/client/components/effects/GlowTracker.tsx +46 -0
- package/carbon/src/client/components/effects/SparkBurst.tsx +145 -0
- package/carbon/src/client/components/effects/VGlowEffect.tsx +83 -0
- package/carbon/src/client/components/effects/index.ts +2 -0
- package/carbon/src/client/components/layouts/SidebarLayout.tsx +20 -0
- package/carbon/src/client/components/scroll/ScrollSection.tsx +76 -0
- package/carbon/src/client/components/scroll/ScrollytellingProvider.tsx +81 -0
- package/carbon/src/client/components/scroll/index.ts +2 -0
- package/carbon/src/client/components/ui/accordion.tsx +71 -0
- package/carbon/src/client/components/ui/alert-dialog.tsx +162 -0
- package/carbon/src/client/components/ui/alert.tsx +73 -0
- package/carbon/src/client/components/ui/aspect-ratio.tsx +22 -0
- package/carbon/src/client/components/ui/avatar.tsx +91 -0
- package/carbon/src/client/components/ui/badge.tsx +50 -0
- package/carbon/src/client/components/ui/breadcrumb.tsx +100 -0
- package/carbon/src/client/components/ui/button-group.tsx +78 -0
- package/carbon/src/client/components/ui/button.tsx +120 -0
- package/carbon/src/client/components/ui/calendar.tsx +182 -0
- package/carbon/src/client/components/ui/card.tsx +85 -0
- package/carbon/src/client/components/ui/carousel.tsx +227 -0
- package/carbon/src/client/components/ui/chart.tsx +357 -0
- package/carbon/src/client/components/ui/checkbox.tsx +27 -0
- package/carbon/src/client/components/ui/collapsible.tsx +15 -0
- package/carbon/src/client/components/ui/command.tsx +178 -0
- package/carbon/src/client/components/ui/context-menu.tsx +233 -0
- package/carbon/src/client/components/ui/dialog.tsx +132 -0
- package/carbon/src/client/components/ui/drawer.tsx +118 -0
- package/carbon/src/client/components/ui/dropdown-menu.tsx +242 -0
- package/carbon/src/client/components/ui/empty.tsx +94 -0
- package/carbon/src/client/components/ui/field.tsx +226 -0
- package/carbon/src/client/components/ui/hover-card.tsx +44 -0
- package/carbon/src/client/components/ui/input-group.tsx +146 -0
- package/carbon/src/client/components/ui/input-otp.tsx +83 -0
- package/carbon/src/client/components/ui/input.tsx +20 -0
- package/carbon/src/client/components/ui/item.tsx +188 -0
- package/carbon/src/client/components/ui/kbd.tsx +26 -0
- package/carbon/src/client/components/ui/label.tsx +21 -0
- package/carbon/src/client/components/ui/menubar.tsx +250 -0
- package/carbon/src/client/components/ui/navigation-menu.tsx +155 -0
- package/carbon/src/client/components/ui/pagination.tsx +102 -0
- package/carbon/src/client/components/ui/popover.tsx +75 -0
- package/carbon/src/client/components/ui/progress.tsx +66 -0
- package/carbon/src/client/components/ui/radio-group.tsx +36 -0
- package/carbon/src/client/components/ui/resizable.tsx +46 -0
- package/carbon/src/client/components/ui/scroll-area.tsx +48 -0
- package/carbon/src/client/components/ui/select.tsx +186 -0
- package/carbon/src/client/components/ui/separator.tsx +21 -0
- package/carbon/src/client/components/ui/sheet.tsx +124 -0
- package/carbon/src/client/components/ui/sidebar.tsx +702 -0
- package/carbon/src/client/components/ui/skeleton.tsx +13 -0
- package/carbon/src/client/components/ui/slider.tsx +57 -0
- package/carbon/src/client/components/ui/sonner.tsx +45 -0
- package/carbon/src/client/components/ui/spinner.tsx +15 -0
- package/carbon/src/client/components/ui/switch.tsx +30 -0
- package/carbon/src/client/components/ui/table.tsx +89 -0
- package/carbon/src/client/components/ui/tabs.tsx +73 -0
- package/carbon/src/client/components/ui/textarea.tsx +18 -0
- package/carbon/src/client/components/ui/toggle-group.tsx +87 -0
- package/carbon/src/client/components/ui/toggle.tsx +44 -0
- package/carbon/src/client/components/ui/tooltip.tsx +56 -0
- package/carbon/src/client/hooks/api/index.ts +14 -0
- package/carbon/src/client/hooks/api/useAuthSettings.ts +80 -0
- package/carbon/src/client/hooks/api/useSubscription.ts +87 -0
- package/carbon/src/client/hooks/use-mobile.ts +21 -0
- package/carbon/src/client/hooks/useMousePosition.ts +91 -0
- package/carbon/src/client/hooks/useNotifications.tsx +124 -0
- package/carbon/src/client/hooks/useOrganizationMembers.ts +127 -0
- package/carbon/src/client/hooks/useOrganizations.tsx +230 -0
- package/carbon/src/client/hooks/usePackageManager.ts +69 -0
- package/carbon/src/client/hooks/useReducedMotion.ts +20 -0
- package/carbon/src/client/hooks/useRunningServices.ts +114 -0
- package/carbon/src/client/hooks/useScrollProgress.ts +56 -0
- package/carbon/src/client/index.css +467 -0
- package/carbon/src/client/index.html +56 -0
- package/carbon/src/client/lib/admin-services.ts +151 -0
- package/carbon/src/client/lib/api.ts +32 -0
- package/carbon/src/client/lib/blog.ts +35 -0
- package/carbon/src/client/lib/changelog.ts +33 -0
- package/carbon/src/client/lib/docs-search.ts +101 -0
- package/carbon/src/client/lib/docs.ts +37 -0
- package/carbon/src/client/lib/i18n.ts +32 -0
- package/carbon/src/client/lib/supabase.ts +72 -0
- package/carbon/src/client/lib/tailwind-colors.ts +357 -0
- package/carbon/src/client/lib/theme.ts +117 -0
- package/carbon/src/client/lib/utils.ts +22 -0
- package/carbon/src/client/locales/de.json +529 -0
- package/carbon/src/client/locales/en.json +461 -0
- package/carbon/src/client/locales/es.json +529 -0
- package/carbon/src/client/locales/fr.json +529 -0
- package/carbon/src/client/locales/pt.json +529 -0
- package/carbon/src/client/main.tsx +56 -0
- package/carbon/src/client/mdx.d.ts +13 -0
- package/carbon/src/client/pages/ApiDocs.tsx +76 -0
- package/carbon/src/client/pages/AuthCallback.tsx +34 -0
- package/carbon/src/client/pages/Blog.tsx +167 -0
- package/carbon/src/client/pages/Changelog.tsx +171 -0
- package/carbon/src/client/pages/Charts.tsx +388 -0
- package/carbon/src/client/pages/Checkout.tsx +227 -0
- package/carbon/src/client/pages/Contact.tsx +174 -0
- package/carbon/src/client/pages/Dashboard.tsx +368 -0
- package/carbon/src/client/pages/Docs.tsx +372 -0
- package/carbon/src/client/pages/ForgotPassword.tsx +111 -0
- package/carbon/src/client/pages/Home.tsx +187 -0
- package/carbon/src/client/pages/Legal.tsx +100 -0
- package/carbon/src/client/pages/Login.tsx +408 -0
- package/carbon/src/client/pages/MFAVerify.tsx +156 -0
- package/carbon/src/client/pages/NotFound.tsx +21 -0
- package/carbon/src/client/pages/Onboarding.tsx +246 -0
- package/carbon/src/client/pages/ResetPassword.tsx +200 -0
- package/carbon/src/client/pages/UIComponents.tsx +390 -0
- package/carbon/src/client/pages/admin/ContactSubmissions.tsx +220 -0
- package/carbon/src/client/pages/admin/Dashboard.tsx +24 -0
- package/carbon/src/client/pages/admin/Infrastructure.tsx +257 -0
- package/carbon/src/client/pages/admin/Jobs.tsx +225 -0
- package/carbon/src/client/pages/admin/Logs.tsx +18 -0
- package/carbon/src/client/pages/admin/Newsletter.tsx +300 -0
- package/carbon/src/client/pages/admin/Notifications.tsx +603 -0
- package/carbon/src/client/pages/admin/Organizations.tsx +306 -0
- package/carbon/src/client/pages/admin/Settings.tsx +314 -0
- package/carbon/src/client/pages/admin/Theme.tsx +465 -0
- package/carbon/src/client/pages/admin/Users.tsx +365 -0
- package/carbon/src/client/pages/api-docs.css +156 -0
- package/carbon/src/client/pages/organizations/Details.tsx +200 -0
- package/carbon/src/client/pages/organizations/Members.tsx +402 -0
- package/carbon/src/client/pages/settings/Billing.tsx +473 -0
- package/carbon/src/client/pages/settings/Profile.tsx +160 -0
- package/carbon/src/client/pages/settings/Security.tsx +341 -0
- package/carbon/src/client/public/favicon.svg +19 -0
- package/carbon/src/client/public/robots.txt +8 -0
- package/carbon/src/server/billing/index.ts +104 -0
- package/carbon/src/server/billing/provider.ts +81 -0
- package/carbon/src/server/billing/providers/paddle.ts +314 -0
- package/carbon/src/server/billing/providers/polar.ts +325 -0
- package/carbon/src/server/billing/providers/stripe.ts +233 -0
- package/carbon/src/server/emails/templates.ts +116 -0
- package/carbon/src/server/index.ts +554 -0
- package/carbon/src/server/lib/auth.ts +6 -0
- package/carbon/src/server/lib/email.ts +64 -0
- package/carbon/src/server/lib/env.ts +112 -0
- package/carbon/src/server/lib/errors.ts +21 -0
- package/carbon/src/server/lib/logger.ts +17 -0
- package/carbon/src/server/lib/rate-limiter.ts +288 -0
- package/carbon/src/server/lib/request.ts +34 -0
- package/carbon/src/server/lib/stripe.ts +42 -0
- package/carbon/src/server/lib/supabase.ts +65 -0
- package/carbon/src/server/middleware/requirePlan.ts +80 -0
- package/carbon/src/server/routes/_internal/services-status.ts +958 -0
- package/carbon/src/server/routes/_internal/verify-role.ts +185 -0
- package/carbon/src/server/routes/health.ts +48 -0
- package/carbon/src/server/routes/v1/admin/contact.ts +128 -0
- package/carbon/src/server/routes/v1/admin/jobs.ts +171 -0
- package/carbon/src/server/routes/v1/admin/newsletter.ts +237 -0
- package/carbon/src/server/routes/v1/auth.ts +390 -0
- package/carbon/src/server/routes/v1/billing.ts +718 -0
- package/carbon/src/server/routes/v1/contact.ts +93 -0
- package/carbon/src/server/routes/v1/index.ts +1333 -0
- package/carbon/src/server/routes/v1/newsletter.ts +181 -0
- package/carbon/src/server/routes/v1/performance.ts +157 -0
- package/carbon/src/server/routes/v1/stats.ts +170 -0
- package/carbon/src/server/routes/v1/theme.ts +106 -0
- package/carbon/src/server/routes/webhooks/billing.ts +376 -0
- package/carbon/src/server/routes/webhooks/stripe.ts +276 -0
- package/carbon/src/server/types.ts +11 -0
- package/carbon/src/shared/pricing.ts +155 -0
- package/carbon/src/shared/types.ts +338 -0
- package/carbon/supabase/migrations/00001_init.sql +717 -0
- package/carbon/supabase/migrations/00002_theme_settings.sql +13 -0
- package/carbon/supabase/migrations/00003_pg_cron.sql +121 -0
- package/carbon/supabase/migrations/00004_contact_newsletter.sql +81 -0
- package/carbon/supabase/migrations/00005_localization_languages.sql +22 -0
- package/carbon/supabase/seed.sql +16 -0
- package/carbon/tests/_helpers/app.ts +45 -0
- package/carbon/tests/_helpers/env.ts +37 -0
- package/carbon/tests/_helpers/factories.ts +69 -0
- package/carbon/tests/_helpers/jwt.ts +23 -0
- package/carbon/tests/_helpers/setup-integration.ts +20 -0
- package/carbon/tests/_helpers/setup-rtl.ts +12 -0
- package/carbon/tests/component/ErrorBoundary.test.tsx +53 -0
- package/carbon/tests/component/use-auth-settings.test.tsx +119 -0
- package/carbon/tests/integration/server/routes/contact.test.ts +162 -0
- package/carbon/tests/integration/server/routes/health.test.ts +61 -0
- package/carbon/tests/structural/i18n-parity.test.ts +42 -0
- package/carbon/tests/unit/client/utils.test.ts +49 -0
- package/carbon/tests/unit/shared/pricing.test.ts +93 -0
- package/carbon/tsconfig.json +27 -0
- package/carbon/tsconfig.server.json +27 -0
- package/carbon/tsconfig.test.json +9 -0
- package/carbon/vite.config.ts +110 -0
- package/carbon/vitest.config.ts +74 -0
- package/carbon/volumes/db/jwt.sql +57 -0
- package/carbon/volumes/db/metabase-init.sh +29 -0
- package/carbon/volumes/db/n8n-init.sh +25 -0
- package/carbon/volumes/db/realtime.sql +33 -0
- package/carbon/volumes/db/roles.sql +93 -0
- package/carbon/volumes/db/set-passwords.sh +12 -0
- package/carbon/volumes/db/super-admin.dev.sql +113 -0
- package/carbon/volumes/db/super-admin.generated.sql +113 -0
- package/carbon/volumes/db/super-admin.sql +114 -0
- package/carbon/volumes/grafana/dashboards/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards/overview.json +523 -0
- package/carbon/volumes/grafana/dashboards/postgresql.json +337 -0
- package/carbon/volumes/grafana/dashboards.dev/logs.json +179 -0
- package/carbon/volumes/grafana/dashboards.dev/overview.json +156 -0
- package/carbon/volumes/grafana/dashboards.dev/postgresql.json +337 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.dev.yml +16 -0
- package/carbon/volumes/grafana/provisioning/dashboards/dashboards.yml +16 -0
- package/carbon/volumes/grafana/provisioning/datasources/datasources.yml +43 -0
- package/carbon/volumes/kong/docker-entrypoint.sh +9 -0
- package/carbon/volumes/kong/kong.yml +208 -0
- package/carbon/volumes/loki/loki-config.yml +58 -0
- package/carbon/volumes/n8n/hooks.js +131 -0
- package/carbon/volumes/n8n/scripts/setup.sh +66 -0
- package/carbon/volumes/prometheus/prometheus.yml +43 -0
- package/carbon/volumes/promtail/promtail-config.yml +64 -0
- package/carbon/volumes/traefik/middlewares.dev.yml +83 -0
- package/carbon/volumes/traefik/middlewares.yml +95 -0
- package/carbon/volumes/traefik/vite-dev.yml +20 -0
- package/package.json +95 -0
- package/src/access.js +354 -0
- package/src/activate.js +187 -0
- package/src/add.js +718 -0
- package/src/backup.js +786 -0
- package/src/cli.js +350 -0
- package/src/configure.js +967 -0
- package/src/console.js +155 -0
- package/src/create.js +1499 -0
- package/src/deploy.js +311 -0
- package/src/destroy.js +2033 -0
- package/src/diagnose.js +735 -0
- package/src/down.js +80 -0
- package/src/failover.js +1032 -0
- package/src/lib/backup-s3.js +179 -0
- package/src/lib/build.js +33 -0
- package/src/lib/checksum.js +28 -0
- package/src/lib/ci-setup.js +666 -0
- package/src/lib/cli/help.js +129 -0
- package/src/lib/cli/parse-flags.js +160 -0
- package/src/lib/cli/select-action.js +65 -0
- package/src/lib/cli/select-environment.js +108 -0
- package/src/lib/cli/tty-guard.js +75 -0
- package/src/lib/cloudflare.js +447 -0
- package/src/lib/colors.js +52 -0
- package/src/lib/command.js +361 -0
- package/src/lib/config.js +359 -0
- package/src/lib/cost.js +103 -0
- package/src/lib/deploy/bundle.js +231 -0
- package/src/lib/deploy/compose/acme-verify.js +121 -0
- package/src/lib/deploy/compose/build-args.js +78 -0
- package/src/lib/deploy/compose/ha.js +1874 -0
- package/src/lib/deploy/compose/index.js +1294 -0
- package/src/lib/deploy/compose/reconcile.js +74 -0
- package/src/lib/deploy/github.js +382 -0
- package/src/lib/deploy/image.js +191 -0
- package/src/lib/deploy/index.js +119 -0
- package/src/lib/deploy/k8s/LICENSE +99 -0
- package/src/lib/deploy/k8s/gitops-deploy.js +298 -0
- package/src/lib/deploy/k8s/ha/index.js +1000 -0
- package/src/lib/deploy/k8s/index.js +62 -0
- package/src/lib/deploy/k8s/k3s.js +2515 -0
- package/src/lib/deploy/orchestrator.js +1401 -0
- package/src/lib/deploy/prompts.js +545 -0
- package/src/lib/deploy/remote-build.js +130 -0
- package/src/lib/deploy/state.js +120 -0
- package/src/lib/deploy/utils.js +328 -0
- package/src/lib/deploy-logger.js +93 -0
- package/src/lib/dns-propagation.js +48 -0
- package/src/lib/environment.js +58 -0
- package/src/lib/fetch-retry.js +103 -0
- package/src/lib/github-environments.js +335 -0
- package/src/lib/hetzner-dns.js +377 -0
- package/src/lib/hetzner-guided-setup.js +275 -0
- package/src/lib/host-keys.js +37 -0
- package/src/lib/iac/cloud-init.js +52 -0
- package/src/lib/iac/index.js +325 -0
- package/src/lib/iac/programs/hetzner-compose.js +130 -0
- package/src/lib/iac/programs/hetzner-k8s.js +320 -0
- package/src/lib/kubectl.js +81 -0
- package/src/lib/licensing/index.js +259 -0
- package/src/lib/licensing/tiers.js +181 -0
- package/src/lib/licensing/validator.js +171 -0
- package/src/lib/merge-package-json.js +90 -0
- package/src/lib/operator-ip.js +381 -0
- package/src/lib/package-manager.js +111 -0
- package/src/lib/perf.js +71 -0
- package/src/lib/project-guard.js +39 -0
- package/src/lib/project.js +334 -0
- package/src/lib/providers/base.js +276 -0
- package/src/lib/providers/hetzner-s3.js +656 -0
- package/src/lib/providers/hetzner.js +755 -0
- package/src/lib/providers/index.js +164 -0
- package/src/lib/s3.js +510 -0
- package/src/lib/secret-scan.js +583 -0
- package/src/lib/secrets.js +63 -0
- package/src/lib/server-types.js +195 -0
- package/src/lib/shell.js +91 -0
- package/src/lib/ssh.js +241 -0
- package/src/lib/tracker.js +242 -0
- package/src/lib/upgrade-policy.js +170 -0
- package/src/lib/validators.js +105 -0
- package/src/lib/version.js +5 -0
- package/src/remove.js +292 -0
- package/src/reset.js +97 -0
- package/src/restore.js +871 -0
- package/src/scale.js +1734 -0
- package/src/shell.js +222 -0
- package/src/status.js +981 -0
- package/src/up.js +264 -0
- package/src/upgrade.js +721 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detection and manifest management utilities
|
|
3
|
+
* Shared across CLI commands (add.js, s3.js, deploy.js, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { escapeDotenv } from './shell.js';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// PACKAGE MANAGER DETECTION
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect the package manager used by a project.
|
|
16
|
+
* Checks lock files first, then package.json packageManager field, falls back to pnpm.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
19
|
+
* @returns {'npm'|'pnpm'|'bun'}
|
|
20
|
+
*/
|
|
21
|
+
export function detectPackageManager(cwd = process.cwd()) {
|
|
22
|
+
if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
23
|
+
if (existsSync(join(cwd, 'bun.lock')) || existsSync(join(cwd, 'bun.lockb'))) return 'bun';
|
|
24
|
+
if (existsSync(join(cwd, 'package-lock.json'))) return 'npm';
|
|
25
|
+
|
|
26
|
+
const pkgPath = join(cwd, 'package.json');
|
|
27
|
+
if (existsSync(pkgPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
30
|
+
if (pkg.packageManager) {
|
|
31
|
+
const pm = pkg.packageManager.split('@')[0];
|
|
32
|
+
if (['npm', 'pnpm', 'bun'].includes(pm)) return pm;
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Ignore parse errors
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 'pnpm';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// MANIFEST MANAGEMENT (.vibecarbon.json services tracking)
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load the project manifest (.vibecarbon.json)
|
|
48
|
+
* This tracks which services have been added to the project
|
|
49
|
+
*
|
|
50
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
51
|
+
* @returns {object} - Manifest object with version and services
|
|
52
|
+
*/
|
|
53
|
+
export function loadManifest(cwd = process.cwd()) {
|
|
54
|
+
const manifestPath = join(cwd, '.vibecarbon.json');
|
|
55
|
+
if (existsSync(manifestPath)) {
|
|
56
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
57
|
+
}
|
|
58
|
+
return { version: '1', services: {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Save the project manifest (.vibecarbon.json)
|
|
63
|
+
*
|
|
64
|
+
* @param {object} manifest - Manifest object to save
|
|
65
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
66
|
+
*/
|
|
67
|
+
export function saveManifest(manifest, cwd = process.cwd()) {
|
|
68
|
+
const manifestPath = join(cwd, '.vibecarbon.json');
|
|
69
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// ENVIRONMENT FILE MANAGEMENT
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse a dotenv-format string into a key-value object.
|
|
78
|
+
*
|
|
79
|
+
* Supports:
|
|
80
|
+
* - Single-quoted values (produced by serializeDotenv / escapeDotenv)
|
|
81
|
+
* - Legacy double-quoted values (for backwards compatibility with
|
|
82
|
+
* previously-written files using the old KEY="value" format)
|
|
83
|
+
* - Unquoted values (no embedded whitespace)
|
|
84
|
+
* - Multi-line single-quoted values (value spans multiple lines)
|
|
85
|
+
* - Comment lines (#) and blank lines are ignored
|
|
86
|
+
*
|
|
87
|
+
* The single-quoted branch uses a tiny state machine that correctly handles
|
|
88
|
+
* the '\'' close-reopen escape AND literal backslashes (which are not
|
|
89
|
+
* escape characters in POSIX single-quoted strings).
|
|
90
|
+
*/
|
|
91
|
+
export function parseDotenv(text) {
|
|
92
|
+
const out = {};
|
|
93
|
+
const lines = text.split('\n');
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
const line = lines[i];
|
|
96
|
+
if (!line || line.startsWith('#')) continue;
|
|
97
|
+
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
98
|
+
if (!m) continue;
|
|
99
|
+
const key = m[1];
|
|
100
|
+
const rest = m[2];
|
|
101
|
+
|
|
102
|
+
if (rest.startsWith("'")) {
|
|
103
|
+
// Scan forward, possibly consuming more lines, until we find the
|
|
104
|
+
// closing single-quote. The only "escape" in POSIX single-quoted
|
|
105
|
+
// strings is the close-reopen trick: '\'' produces a literal '.
|
|
106
|
+
// We consume buffer[pos] one char at a time starting after the
|
|
107
|
+
// opening '.
|
|
108
|
+
let buf = rest.slice(1);
|
|
109
|
+
const parts = [];
|
|
110
|
+
let done = false;
|
|
111
|
+
while (!done) {
|
|
112
|
+
let pos = 0;
|
|
113
|
+
while (pos < buf.length) {
|
|
114
|
+
const ch = buf[pos];
|
|
115
|
+
if (ch === "'") {
|
|
116
|
+
// Check for close-reopen '\''
|
|
117
|
+
if (buf.slice(pos, pos + 4) === "'\\''") {
|
|
118
|
+
parts.push("'");
|
|
119
|
+
pos += 4;
|
|
120
|
+
} else {
|
|
121
|
+
// Actual closing quote.
|
|
122
|
+
done = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
parts.push(ch);
|
|
127
|
+
pos += 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (done) break;
|
|
131
|
+
// Exhausted this line without finding the close — consume the next.
|
|
132
|
+
if (i + 1 >= lines.length) {
|
|
133
|
+
// Malformed: unterminated single quote. Salvage what we have.
|
|
134
|
+
done = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
parts.push('\n');
|
|
138
|
+
i += 1;
|
|
139
|
+
buf = lines[i];
|
|
140
|
+
}
|
|
141
|
+
out[key] = parts.join('');
|
|
142
|
+
} else if (rest.startsWith('"')) {
|
|
143
|
+
// Legacy double-quoted. No escape handling (pre-C-8 files used simple "...").
|
|
144
|
+
const closeIdx = rest.indexOf('"', 1);
|
|
145
|
+
if (closeIdx !== -1) out[key] = rest.slice(1, closeIdx);
|
|
146
|
+
} else {
|
|
147
|
+
// Unquoted value — up to first whitespace-then-# or end of line.
|
|
148
|
+
out[key] = rest.replace(/\s+#.*$/, '').trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Serialize a key-value object into dotenv format using escapeDotenv
|
|
156
|
+
* (single-quoted, safe for dotenv-package parsers).
|
|
157
|
+
*/
|
|
158
|
+
export function serializeDotenv(obj) {
|
|
159
|
+
return `${Object.entries(obj)
|
|
160
|
+
.map(([k, v]) => `${k}=${escapeDotenv(v)}`)
|
|
161
|
+
.join('\n')}\n`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load environment variables from .env.local
|
|
166
|
+
*
|
|
167
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
168
|
+
* @returns {object} - Key-value object of environment variables
|
|
169
|
+
*/
|
|
170
|
+
export function loadEnvVariables(cwd = process.cwd()) {
|
|
171
|
+
const envPath = join(cwd, '.env.local');
|
|
172
|
+
if (!existsSync(envPath)) return {};
|
|
173
|
+
return parseDotenv(readFileSync(envPath, 'utf-8'));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Append a section of environment variables to .env.local and .env
|
|
178
|
+
*
|
|
179
|
+
* @param {string} sectionName - Name for the section header (e.g., 'N8N', 'S3 STORAGE')
|
|
180
|
+
* @param {object} envVars - Key-value pairs to add
|
|
181
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
182
|
+
*/
|
|
183
|
+
export function appendToEnv(sectionName, envVars, cwd = process.cwd()) {
|
|
184
|
+
const envFiles = ['.env.local', '.env'];
|
|
185
|
+
for (const filename of envFiles) {
|
|
186
|
+
const envPath = join(cwd, filename);
|
|
187
|
+
if (!existsSync(envPath)) continue;
|
|
188
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
189
|
+
const sectionHeader = `# ${sectionName.toUpperCase()}`;
|
|
190
|
+
if (content.includes(sectionHeader)) continue;
|
|
191
|
+
const body = Object.entries(envVars)
|
|
192
|
+
.map(([k, v]) => `${k}=${escapeDotenv(v)}`)
|
|
193
|
+
.join('\n');
|
|
194
|
+
const newSection = `\n\n# =============================================================================\n# ${sectionName.toUpperCase()}\n# =============================================================================\n\n${body}\n`;
|
|
195
|
+
writeFileSync(envPath, content.trimEnd() + newSection);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Set or update a single environment variable in .env.local and .env
|
|
201
|
+
*
|
|
202
|
+
* @param {string} key - Variable name
|
|
203
|
+
* @param {string} value - Variable value
|
|
204
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
205
|
+
*/
|
|
206
|
+
export function setEnvVar(key, value, cwd = process.cwd()) {
|
|
207
|
+
const envFiles = ['.env.local', '.env'];
|
|
208
|
+
for (const filename of envFiles) {
|
|
209
|
+
const envPath = join(cwd, filename);
|
|
210
|
+
if (!existsSync(envPath)) continue;
|
|
211
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
212
|
+
// Match either legacy KEY="..." or new KEY='...' forms at line start.
|
|
213
|
+
const regex = new RegExp(`^${key}=(?:"[^"]*"|'(?:[^']|'\\\\'')*')`, 'm');
|
|
214
|
+
const replacement = `${key}=${escapeDotenv(value)}`;
|
|
215
|
+
if (regex.test(content)) {
|
|
216
|
+
writeFileSync(envPath, content.replace(regex, replacement));
|
|
217
|
+
} else {
|
|
218
|
+
writeFileSync(envPath, `${content.trimEnd()}\n${replacement}\n`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// GIT ADD ALLOWLIST
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Allowlist of paths that vibecarbon knows are safe to commit.
|
|
229
|
+
* Used by add/deploy/push in place of `git add .` to prevent leaking
|
|
230
|
+
* `.vibecarbon/`, `.env.local`, or other untracked secrets.
|
|
231
|
+
*/
|
|
232
|
+
const PROJECT_GIT_ADD_ALLOWLIST = [
|
|
233
|
+
// Top-level config
|
|
234
|
+
'.gitignore',
|
|
235
|
+
'.dockerignore',
|
|
236
|
+
'.github/',
|
|
237
|
+
'.claude/',
|
|
238
|
+
'package.json',
|
|
239
|
+
'pnpm-lock.yaml',
|
|
240
|
+
'package-lock.json',
|
|
241
|
+
'bun.lock',
|
|
242
|
+
'bun.lockb',
|
|
243
|
+
'biome.json',
|
|
244
|
+
'tsconfig.json',
|
|
245
|
+
'tsconfig.server.json',
|
|
246
|
+
'vite.config.ts',
|
|
247
|
+
'components.json',
|
|
248
|
+
// Docker
|
|
249
|
+
'Dockerfile',
|
|
250
|
+
'docker-entrypoint.sh',
|
|
251
|
+
'docker-compose.yml',
|
|
252
|
+
'docker-compose.prod.yml',
|
|
253
|
+
'docker-compose.override.yml',
|
|
254
|
+
'docker-compose.metabase.yml',
|
|
255
|
+
'docker-compose.metabase.prod.yml',
|
|
256
|
+
'docker-compose.metabase.override.yml',
|
|
257
|
+
'docker-compose.n8n.yml',
|
|
258
|
+
'docker-compose.n8n.prod.yml',
|
|
259
|
+
'docker-compose.n8n.override.yml',
|
|
260
|
+
'docker-compose.observability.yml',
|
|
261
|
+
'docker-compose.observability.prod.yml',
|
|
262
|
+
'docker-compose.observability.override.yml',
|
|
263
|
+
// Source
|
|
264
|
+
'src/',
|
|
265
|
+
'scripts/',
|
|
266
|
+
'content/',
|
|
267
|
+
'backup/',
|
|
268
|
+
'ha/',
|
|
269
|
+
'k8s/',
|
|
270
|
+
'supabase/',
|
|
271
|
+
'cloud-init/',
|
|
272
|
+
'volumes/',
|
|
273
|
+
// Docs and metadata
|
|
274
|
+
'README.md',
|
|
275
|
+
'LICENSE',
|
|
276
|
+
'AGENTS.md',
|
|
277
|
+
'CLAUDE.md',
|
|
278
|
+
'DEVELOPMENT.md',
|
|
279
|
+
'PRODUCTION.md',
|
|
280
|
+
'CHANGELOG.md',
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Build a `git add` argv limited to template-known paths that exist.
|
|
285
|
+
* The first 3 tokens ('git', 'add', '--') are always included; the rest
|
|
286
|
+
* are filtered by existsSync so missing optional paths don't error.
|
|
287
|
+
*
|
|
288
|
+
* If NO allowlisted path exists in cwd, the result is ['git', 'add', '--']
|
|
289
|
+
* with no trailing paths — git will exit 0 with a "nothing added" message
|
|
290
|
+
* on stderr. Callers pass silent:true + ignoreError:true to make this a no-op.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
293
|
+
* @param {string[]} [extras] - Additional paths to include (also filtered by existsSync)
|
|
294
|
+
* @returns {string[]} argv array safe to pass to runCommand
|
|
295
|
+
*/
|
|
296
|
+
export function buildGitAddArgv(cwd = process.cwd(), extras = []) {
|
|
297
|
+
const paths = [...PROJECT_GIT_ADD_ALLOWLIST, ...extras].filter((p) => existsSync(join(cwd, p)));
|
|
298
|
+
return ['git', 'add', '--', ...paths];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// GITIGNORE VALIDATION
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check that a .gitignore file contains the required security patterns.
|
|
307
|
+
* Returns an array of missing patterns (empty array on success).
|
|
308
|
+
*
|
|
309
|
+
* @param {string} gitignorePath - Absolute path to the .gitignore file
|
|
310
|
+
* @returns {string[]} Array of missing patterns
|
|
311
|
+
*/
|
|
312
|
+
export function validateGitignore(gitignorePath) {
|
|
313
|
+
let content;
|
|
314
|
+
try {
|
|
315
|
+
content = readFileSync(gitignorePath, 'utf-8');
|
|
316
|
+
} catch {
|
|
317
|
+
return ['.gitignore is missing'];
|
|
318
|
+
}
|
|
319
|
+
const lines = content
|
|
320
|
+
.split('\n')
|
|
321
|
+
.map((l) => l.trim())
|
|
322
|
+
.filter((l) => l && !l.startsWith('#'));
|
|
323
|
+
const required = [
|
|
324
|
+
'.vibecarbon/',
|
|
325
|
+
'.env',
|
|
326
|
+
'.env.local',
|
|
327
|
+
'.env.*.local',
|
|
328
|
+
'*.pem',
|
|
329
|
+
'*.key',
|
|
330
|
+
'*.tfstate',
|
|
331
|
+
'*.tfstate.*',
|
|
332
|
+
];
|
|
333
|
+
return required.filter((r) => !lines.includes(r));
|
|
334
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class that defines the interface for cloud providers.
|
|
5
|
+
* All cloud providers (Hetzner, DigitalOcean, Vultr, etc.) must implement
|
|
6
|
+
* this interface to be used with Vibecarbon.
|
|
7
|
+
*
|
|
8
|
+
* To add a new provider:
|
|
9
|
+
* 1. Create a new file (e.g., digitalocean.js) that extends BaseProvider
|
|
10
|
+
* 2. Implement all abstract methods
|
|
11
|
+
* 3. Add static NAME, REGIONS, and SERVER_TYPES properties
|
|
12
|
+
* 4. Register the provider in index.js
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export class BaseProvider {
|
|
16
|
+
/**
|
|
17
|
+
* Provider display name (e.g., "Hetzner Cloud")
|
|
18
|
+
* @type {string}
|
|
19
|
+
*/
|
|
20
|
+
static NAME = 'Base Provider';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* API base URL
|
|
24
|
+
* @type {string}
|
|
25
|
+
*/
|
|
26
|
+
static API_BASE = '';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Available regions
|
|
30
|
+
* @type {Object<string, string>}
|
|
31
|
+
*/
|
|
32
|
+
static REGIONS = {};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Available server types
|
|
36
|
+
* @type {Object<string, {vcpu: number, ram: number, disk: number, price: string}>}
|
|
37
|
+
*/
|
|
38
|
+
static SERVER_TYPES = {};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default server type
|
|
42
|
+
* @type {string}
|
|
43
|
+
*/
|
|
44
|
+
static DEFAULT_TYPE = '';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Default region for HA deployments
|
|
48
|
+
* @type {string[]}
|
|
49
|
+
*/
|
|
50
|
+
static HA_REGIONS = [];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new provider instance
|
|
54
|
+
* @param {string} apiToken - API token for authentication
|
|
55
|
+
*/
|
|
56
|
+
constructor(apiToken) {
|
|
57
|
+
if (!apiToken) {
|
|
58
|
+
throw new Error('API token is required');
|
|
59
|
+
}
|
|
60
|
+
this.apiToken = apiToken;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the provider name
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
getName() {
|
|
68
|
+
return this.constructor.NAME;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get available regions
|
|
73
|
+
* @returns {Object<string, string>}
|
|
74
|
+
*/
|
|
75
|
+
getRegions() {
|
|
76
|
+
return this.constructor.REGIONS;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get available server types
|
|
81
|
+
* @returns {Object<string, object>}
|
|
82
|
+
*/
|
|
83
|
+
getServerTypes() {
|
|
84
|
+
return this.constructor.SERVER_TYPES;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the default server type
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
getDefaultType() {
|
|
92
|
+
return this.constructor.DEFAULT_TYPE;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get default regions for HA deployment
|
|
97
|
+
* @returns {string[]}
|
|
98
|
+
*/
|
|
99
|
+
getHARegions() {
|
|
100
|
+
return this.constructor.HA_REGIONS;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// ABSTRACT METHODS - Must be implemented by subclasses
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a new server
|
|
109
|
+
* @param {object} config - Server configuration
|
|
110
|
+
* @param {string} config.name - Server name
|
|
111
|
+
* @param {string} config.serverType - Server type/size
|
|
112
|
+
* @param {string} config.region - Region/location
|
|
113
|
+
* @param {string|number} config.sshKeyId - SSH key ID
|
|
114
|
+
* @param {string} config.environment - Environment name
|
|
115
|
+
* @returns {Promise<{id: string|number, server?: object}>}
|
|
116
|
+
*/
|
|
117
|
+
async createServer(_config) {
|
|
118
|
+
throw new Error('createServer() must be implemented by subclass');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Delete a server
|
|
123
|
+
* @param {string|number} serverId - Server ID
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async deleteServer(_serverId) {
|
|
127
|
+
throw new Error('deleteServer() must be implemented by subclass');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get server details
|
|
132
|
+
* @param {string|number} serverId - Server ID
|
|
133
|
+
* @returns {Promise<object>}
|
|
134
|
+
*/
|
|
135
|
+
async getServer(_serverId) {
|
|
136
|
+
throw new Error('getServer() must be implemented by subclass');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Wait for a server to become ready/running
|
|
141
|
+
* @param {string|number} serverId - Server ID
|
|
142
|
+
* @param {number} [timeout=300000] - Timeout in milliseconds
|
|
143
|
+
* @returns {Promise<object>} Server details when ready
|
|
144
|
+
*/
|
|
145
|
+
async waitForServer(_serverId, _timeout = 300000) {
|
|
146
|
+
throw new Error('waitForServer() must be implemented by subclass');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create or get an SSH key
|
|
151
|
+
* @param {string} name - SSH key name
|
|
152
|
+
* @param {string} publicKey - Public key content
|
|
153
|
+
* @returns {Promise<string|number>} SSH key ID
|
|
154
|
+
*/
|
|
155
|
+
async createSSHKey(_name, _publicKey) {
|
|
156
|
+
throw new Error('createSSHKey() must be implemented by subclass');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Delete an SSH key
|
|
161
|
+
* @param {string|number} keyId - SSH key ID
|
|
162
|
+
* @returns {Promise<void>}
|
|
163
|
+
*/
|
|
164
|
+
async deleteSSHKey(_keyId) {
|
|
165
|
+
throw new Error('deleteSSHKey() must be implemented by subclass');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Create a firewall and attach it to a server
|
|
170
|
+
* @param {string} name - Firewall name
|
|
171
|
+
* @param {string|number} serverId - Server ID to attach to
|
|
172
|
+
* @param {number[]} [ports=[22, 80, 443]] - Ports to open
|
|
173
|
+
* @returns {Promise<object>}
|
|
174
|
+
*/
|
|
175
|
+
async createFirewall(_name, _serverId, _ports = [22, 80, 443]) {
|
|
176
|
+
throw new Error('createFirewall() must be implemented by subclass');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Delete a firewall
|
|
181
|
+
* @param {string|number} firewallId - Firewall ID
|
|
182
|
+
* @returns {Promise<void>}
|
|
183
|
+
*/
|
|
184
|
+
async deleteFirewall(_firewallId) {
|
|
185
|
+
throw new Error('deleteFirewall() must be implemented by subclass');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List servers matching labels/tags
|
|
190
|
+
* @param {object} labels - Labels to filter by
|
|
191
|
+
* @returns {Promise<object[]>}
|
|
192
|
+
*/
|
|
193
|
+
async listServers(_labels = {}) {
|
|
194
|
+
throw new Error('listServers() must be implemented by subclass');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* List firewalls matching labels/tags
|
|
199
|
+
* @param {object} labels - Labels to filter by
|
|
200
|
+
* @returns {Promise<object[]>}
|
|
201
|
+
*/
|
|
202
|
+
async listFirewalls(_labels = {}) {
|
|
203
|
+
throw new Error('listFirewalls() must be implemented by subclass');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* List SSH keys
|
|
208
|
+
* @returns {Promise<object[]>}
|
|
209
|
+
*/
|
|
210
|
+
async listSSHKeys() {
|
|
211
|
+
throw new Error('listSSHKeys() must be implemented by subclass');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// HELPER METHODS - Can be overridden if needed
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Make an authenticated API request
|
|
220
|
+
* @param {string} endpoint - API endpoint (relative to API_BASE)
|
|
221
|
+
* @param {object} [options={}] - Fetch options
|
|
222
|
+
* @returns {Promise<Response>}
|
|
223
|
+
*/
|
|
224
|
+
async apiRequest(endpoint, options = {}) {
|
|
225
|
+
const url = `${this.constructor.API_BASE}${endpoint}`;
|
|
226
|
+
const headers = {
|
|
227
|
+
Authorization: `Bearer ${this.apiToken}`,
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
...options.headers,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Delegate to the shared fetch-retry helper so "fetch failed" /
|
|
233
|
+
// ECONNRESET / EAI_AGAIN from Hetzner gets 5 tries with exponential
|
|
234
|
+
// backoff instead of the previous 3. Transient 5xx / 429 also retry.
|
|
235
|
+
const { fetchWithRetry } = await import('../fetch-retry.js');
|
|
236
|
+
return await fetchWithRetry(url, { ...options, headers });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Validate that a region is supported
|
|
241
|
+
* @param {string} region - Region ID
|
|
242
|
+
* @returns {boolean}
|
|
243
|
+
*/
|
|
244
|
+
isValidRegion(region) {
|
|
245
|
+
return region in this.constructor.REGIONS;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validate that a server type is supported
|
|
250
|
+
* @param {string} serverType - Server type ID
|
|
251
|
+
* @returns {boolean}
|
|
252
|
+
*/
|
|
253
|
+
isValidServerType(serverType) {
|
|
254
|
+
return serverType in this.constructor.SERVER_TYPES;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Format a region ID to display name
|
|
259
|
+
* @param {string} region - Region ID
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
formatRegion(region) {
|
|
263
|
+
return this.constructor.REGIONS[region] || region;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Format a server type to display string
|
|
268
|
+
* @param {string} serverType - Server type ID
|
|
269
|
+
* @returns {string}
|
|
270
|
+
*/
|
|
271
|
+
formatServerType(serverType) {
|
|
272
|
+
const type = this.constructor.SERVER_TYPES[serverType];
|
|
273
|
+
if (!type) return serverType;
|
|
274
|
+
return `${serverType} (${type.vcpu} vCPU, ${type.ram}GB RAM, ${type.disk}GB SSD, ${type.price})`;
|
|
275
|
+
}
|
|
276
|
+
}
|