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,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
|
+
}
|