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
package/src/upgrade.js
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vibecarbon Upgrade Command
|
|
3
|
+
* Upgrades infrastructure files in an existing project to match the latest template
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* vibecarbon upgrade # Interactive upgrade
|
|
7
|
+
* vibecarbon upgrade --dry-run # Preview changes without applying
|
|
8
|
+
* vibecarbon upgrade --force # Replace all files (creates .upgrade-backup copies)
|
|
9
|
+
* vibecarbon upgrade -y # Auto-accept safe replacements, skip merge files
|
|
10
|
+
* vibecarbon upgrade --help # Show help
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
chmodSync,
|
|
15
|
+
copyFileSync,
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
} from 'node:fs';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import * as p from '@clack/prompts';
|
|
24
|
+
import { hashContent, hashFile } from './lib/checksum.js';
|
|
25
|
+
import { renderHelp } from './lib/cli/help.js';
|
|
26
|
+
import { parseFlags } from './lib/cli/parse-flags.js';
|
|
27
|
+
import { c, printBanner } from './lib/colors.js';
|
|
28
|
+
import { runCommandAsync } from './lib/command.js';
|
|
29
|
+
import { requireLicense } from './lib/licensing/index.js';
|
|
30
|
+
import { mergePackageJson } from './lib/merge-package-json.js';
|
|
31
|
+
import {
|
|
32
|
+
adaptDockerfileForPackageManager,
|
|
33
|
+
getPackageManagerVersion,
|
|
34
|
+
} from './lib/package-manager.js';
|
|
35
|
+
import {
|
|
36
|
+
detectPackageManager,
|
|
37
|
+
loadEnvVariables,
|
|
38
|
+
loadManifest,
|
|
39
|
+
saveManifest,
|
|
40
|
+
} from './lib/project.js';
|
|
41
|
+
import { assertInProjectDir } from './lib/project-guard.js';
|
|
42
|
+
import { getFilePolicy, getUpgradeableFiles } from './lib/upgrade-policy.js';
|
|
43
|
+
import { VERSION } from './lib/version.js';
|
|
44
|
+
|
|
45
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
46
|
+
const __dirname = dirname(__filename);
|
|
47
|
+
const TEMPLATE_DIR = join(__dirname, '..', 'carbon');
|
|
48
|
+
|
|
49
|
+
// Template variable placeholders (same as create.js)
|
|
50
|
+
const PLACEHOLDERS = {
|
|
51
|
+
PROJECT_NAME: '{{PROJECT_NAME}}',
|
|
52
|
+
GITHUB_OWNER: '{{GITHUB_OWNER}}',
|
|
53
|
+
DB_PASSWORD: '{{DB_PASSWORD}}',
|
|
54
|
+
JWT_SECRET: '{{JWT_SECRET}}',
|
|
55
|
+
ANON_KEY: '{{ANON_KEY}}',
|
|
56
|
+
SERVICE_ROLE_KEY: '{{SERVICE_ROLE_KEY}}',
|
|
57
|
+
REALTIME_SECRET: '{{REALTIME_SECRET}}',
|
|
58
|
+
LOGFLARE_API_KEY: '{{LOGFLARE_API_KEY}}',
|
|
59
|
+
VAULT_ENC_KEY: '{{VAULT_ENC_KEY}}',
|
|
60
|
+
PG_META_CRYPTO_KEY: '{{PG_META_CRYPTO_KEY}}',
|
|
61
|
+
DB_ENC_KEY: '{{DB_ENC_KEY}}',
|
|
62
|
+
SITE_URL: '{{SITE_URL}}',
|
|
63
|
+
ADMIN_EMAIL: '{{ADMIN_EMAIL}}',
|
|
64
|
+
ADMIN_PASSWORD: '{{ADMIN_PASSWORD}}',
|
|
65
|
+
ADMIN_PASSWORD_HASH: '{{ADMIN_PASSWORD_HASH}}',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Mirrors SECRET_PLACEHOLDERS in src/create.js — placeholders that must never
|
|
69
|
+
// be substituted into checked-in files (deploy code patches them at runtime
|
|
70
|
+
// from .env.local). Keep in sync with create.js.
|
|
71
|
+
const SECRET_PLACEHOLDERS = new Set([
|
|
72
|
+
'DB_PASSWORD',
|
|
73
|
+
'JWT_SECRET',
|
|
74
|
+
'ANON_KEY',
|
|
75
|
+
'SERVICE_ROLE_KEY',
|
|
76
|
+
'REALTIME_SECRET',
|
|
77
|
+
'LOGFLARE_API_KEY',
|
|
78
|
+
'VAULT_ENC_KEY',
|
|
79
|
+
'PG_META_CRYPTO_KEY',
|
|
80
|
+
'DB_ENC_KEY',
|
|
81
|
+
'ADMIN_PASSWORD',
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// Binary file extensions — copy raw, skip placeholder replacement
|
|
85
|
+
const BINARY_EXTENSIONS = new Set([
|
|
86
|
+
'.png',
|
|
87
|
+
'.jpg',
|
|
88
|
+
'.jpeg',
|
|
89
|
+
'.gif',
|
|
90
|
+
'.ico',
|
|
91
|
+
'.svg',
|
|
92
|
+
'.woff',
|
|
93
|
+
'.woff2',
|
|
94
|
+
'.ttf',
|
|
95
|
+
'.eot',
|
|
96
|
+
'.otf',
|
|
97
|
+
'.zip',
|
|
98
|
+
'.tar',
|
|
99
|
+
'.gz',
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// Files that contain {{PLACEHOLDER}} strings as their own substitution keys
|
|
103
|
+
// (not vibecarbon template variables). Must be copied raw without replacement.
|
|
104
|
+
const RAW_COPY_FILES = new Set(['scripts/generate-dev-configs.sh']);
|
|
105
|
+
|
|
106
|
+
// Optional-feature file prefixes — only upgrade these if they already exist in the project.
|
|
107
|
+
// These are added by `vibecarbon add` or `vibecarbon deploy`, not during `create`.
|
|
108
|
+
const OPTIONAL_FILE_PREFIXES = ['docker-compose.n8n.', 'docker-compose.metabase.'];
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// COMMAND SPEC — single source of truth for argv parsing AND help output.
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
/** @type {import('./lib/cli/parse-flags.js').CommandSpec & { summary?: string, description?: string, examples?: Array<{ command: string, description?: string }> }} */
|
|
115
|
+
const SPEC = {
|
|
116
|
+
name: 'upgrade',
|
|
117
|
+
summary: 'Update infrastructure files to the latest template',
|
|
118
|
+
description: [
|
|
119
|
+
"Compares your project's infrastructure files against the latest template",
|
|
120
|
+
'and applies updates. User source code (src/**, content/**, migrations/**)',
|
|
121
|
+
'is never touched.',
|
|
122
|
+
'',
|
|
123
|
+
'Files are classified into three categories:',
|
|
124
|
+
" Safe Auto-replaced if you haven't modified them",
|
|
125
|
+
' Merge Always shown for review (docker-compose.yml, package.json, etc.)',
|
|
126
|
+
' Never Your code, never touched (src/**, supabase/**, .env, etc.)',
|
|
127
|
+
].join('\n'),
|
|
128
|
+
flags: [
|
|
129
|
+
{ name: 'h', boolean: true, description: 'Show this help' },
|
|
130
|
+
{ name: 'v', boolean: true, description: 'Show version' },
|
|
131
|
+
{ name: 'y', boolean: true, description: 'Auto-accept safe replacements, skip merge files' },
|
|
132
|
+
{ name: 'dry', boolean: true, description: 'Preview changes without applying' },
|
|
133
|
+
{
|
|
134
|
+
name: 'force',
|
|
135
|
+
boolean: true,
|
|
136
|
+
description: 'Replace all files (creates .upgrade-backup copies)',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
examples: [
|
|
140
|
+
{ command: 'vibecarbon upgrade -dry', description: 'preview what would change' },
|
|
141
|
+
{ command: 'vibecarbon upgrade', description: 'interactive upgrade' },
|
|
142
|
+
{
|
|
143
|
+
command: 'vibecarbon upgrade -y',
|
|
144
|
+
description: 'non-interactive: update safe files, skip merge files',
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// VARIABLE RECONSTRUCTION
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Reconstruct template variables from the project's .env.local
|
|
155
|
+
* Maps environment variable names back to template placeholder names
|
|
156
|
+
*
|
|
157
|
+
* @param {string} cwd - Project directory
|
|
158
|
+
* @returns {object} - Variables keyed by placeholder name (without braces)
|
|
159
|
+
*/
|
|
160
|
+
function reconstructVariables(cwd) {
|
|
161
|
+
const env = loadEnvVariables(cwd);
|
|
162
|
+
const manifest = loadManifest(cwd);
|
|
163
|
+
|
|
164
|
+
const variables = {
|
|
165
|
+
PROJECT_NAME: env.PROJECT_NAME || env.VITE_PROJECT_NAME || '',
|
|
166
|
+
GITHUB_OWNER: manifest.services?.cicd?.owner || env.PROJECT_NAME || '',
|
|
167
|
+
DB_PASSWORD: env.DB_PASSWORD || env.POSTGRES_PASSWORD || '',
|
|
168
|
+
JWT_SECRET: env.JWT_SECRET || '',
|
|
169
|
+
ANON_KEY: env.SUPABASE_ANON_KEY || '',
|
|
170
|
+
SERVICE_ROLE_KEY: env.SUPABASE_SERVICE_ROLE_KEY || '',
|
|
171
|
+
REALTIME_SECRET: env.REALTIME_SECRET || env.SECRET_KEY_BASE || '',
|
|
172
|
+
LOGFLARE_API_KEY: env.LOGFLARE_API_KEY || '',
|
|
173
|
+
VAULT_ENC_KEY: env.VAULT_ENC_KEY || '',
|
|
174
|
+
PG_META_CRYPTO_KEY: env.PG_META_CRYPTO_KEY || '',
|
|
175
|
+
DB_ENC_KEY: (env.DB_ENC_KEY || '').slice(0, 16), // Realtime aes_128_ecb requires exactly 16 bytes
|
|
176
|
+
SITE_URL: env.SITE_URL || 'http://localhost:5173',
|
|
177
|
+
ADMIN_EMAIL: env.ADMIN_EMAIL || '',
|
|
178
|
+
ADMIN_PASSWORD: env.ADMIN_PASSWORD || '',
|
|
179
|
+
// ADMIN_PASSWORD_HASH can't be reconstructed, but only appears in NEVER files
|
|
180
|
+
ADMIN_PASSWORD_HASH: '',
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return variables;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// TEMPLATE RESOLUTION
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if a file path has a binary extension
|
|
192
|
+
*
|
|
193
|
+
* @param {string} filePath
|
|
194
|
+
* @returns {boolean}
|
|
195
|
+
*/
|
|
196
|
+
function isBinaryFile(filePath) {
|
|
197
|
+
const ext = filePath.substring(filePath.lastIndexOf('.'));
|
|
198
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Read a template file and apply placeholder replacement
|
|
203
|
+
*
|
|
204
|
+
* @param {string} templateRelPath - Relative path within TEMPLATE_DIR
|
|
205
|
+
* @param {object} variables - Template variables
|
|
206
|
+
* @returns {string} - Resolved content
|
|
207
|
+
*/
|
|
208
|
+
function resolveTemplate(templateRelPath, variables) {
|
|
209
|
+
const fullPath = join(TEMPLATE_DIR, templateRelPath);
|
|
210
|
+
let content = readFileSync(fullPath, 'utf-8');
|
|
211
|
+
|
|
212
|
+
for (const [key, placeholder] of Object.entries(PLACEHOLDERS)) {
|
|
213
|
+
if (SECRET_PLACEHOLDERS.has(key)) continue;
|
|
214
|
+
if (variables[key] !== undefined && variables[key] !== '') {
|
|
215
|
+
content = content.replaceAll(placeholder, variables[key]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return content;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// DIFF DISPLAY
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Generate a simple unified-style diff between two strings
|
|
228
|
+
* Shows only changed lines with context
|
|
229
|
+
*
|
|
230
|
+
* @param {string} oldContent
|
|
231
|
+
* @param {string} newContent
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
234
|
+
function simpleDiff(oldContent, newContent) {
|
|
235
|
+
const oldLines = oldContent.split('\n');
|
|
236
|
+
const newLines = newContent.split('\n');
|
|
237
|
+
const output = [];
|
|
238
|
+
const maxLines = Math.max(oldLines.length, newLines.length);
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < maxLines; i++) {
|
|
241
|
+
const oldLine = oldLines[i];
|
|
242
|
+
const newLine = newLines[i];
|
|
243
|
+
|
|
244
|
+
if (oldLine === undefined) {
|
|
245
|
+
output.push(c.success(`+ ${newLine}`));
|
|
246
|
+
} else if (newLine === undefined) {
|
|
247
|
+
output.push(c.error(`- ${oldLine}`));
|
|
248
|
+
} else if (oldLine !== newLine) {
|
|
249
|
+
output.push(c.error(`- ${oldLine}`));
|
|
250
|
+
output.push(c.success(`+ ${newLine}`));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Limit output to avoid flooding terminal
|
|
255
|
+
if (output.length > 40) {
|
|
256
|
+
return `${output.slice(0, 40).join('\n')}\n${c.dim(`... and ${output.length - 40} more lines`)}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return output.join('\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// MAIN UPGRADE LOGIC
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
async function main(cliArgs) {
|
|
267
|
+
const { values, errors } = parseFlags(cliArgs, SPEC);
|
|
268
|
+
|
|
269
|
+
if (errors.length > 0) {
|
|
270
|
+
for (const e of errors) {
|
|
271
|
+
process.stderr.write(`${c.error('✗')} ${e}\n`);
|
|
272
|
+
}
|
|
273
|
+
process.stderr.write(`Run ${c.info('vibecarbon upgrade -h')} for usage.\n`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (values.h) {
|
|
278
|
+
process.stdout.write(renderHelp(SPEC));
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
if (values.v) {
|
|
282
|
+
console.log(`vibecarbon v${VERSION}`);
|
|
283
|
+
process.exit(0);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Build the legacy `args` shape that the orchestration code reads.
|
|
287
|
+
// dryRun (camelCase) is the historical name; preserve it.
|
|
288
|
+
const args = {
|
|
289
|
+
dryRun: !!values.dry,
|
|
290
|
+
force: !!values.force,
|
|
291
|
+
yes: !!values.y,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Detect project
|
|
295
|
+
assertInProjectDir();
|
|
296
|
+
|
|
297
|
+
// Require a paid license (Diamond or Fullerene)
|
|
298
|
+
requireLicense('upgrade');
|
|
299
|
+
|
|
300
|
+
const cwd = process.cwd();
|
|
301
|
+
const manifest = loadManifest(cwd);
|
|
302
|
+
const currentTemplateVersion = manifest.templateVersion || '0.0.0';
|
|
303
|
+
const storedChecksums = manifest.fileChecksums || {};
|
|
304
|
+
const isBootstrap = currentTemplateVersion === '0.0.0';
|
|
305
|
+
|
|
306
|
+
printBanner();
|
|
307
|
+
p.intro(`${c.bold('vibecarbon upgrade')} ${c.dim(`v${VERSION}`)}`);
|
|
308
|
+
|
|
309
|
+
if (isBootstrap) {
|
|
310
|
+
p.log.info(
|
|
311
|
+
`First upgrade — no stored checksums found. Files will be compared against the current template.`,
|
|
312
|
+
);
|
|
313
|
+
} else {
|
|
314
|
+
p.log.info(`Template: ${c.dim(currentTemplateVersion)} → ${c.bold(VERSION)}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Warn about dirty git state
|
|
318
|
+
try {
|
|
319
|
+
const { execFileSync } = await import('node:child_process');
|
|
320
|
+
const gitStatus = execFileSync('git', ['status', '--porcelain'], {
|
|
321
|
+
cwd,
|
|
322
|
+
encoding: 'utf-8',
|
|
323
|
+
timeout: 5000,
|
|
324
|
+
}).trim();
|
|
325
|
+
if (gitStatus) {
|
|
326
|
+
p.log.warn(
|
|
327
|
+
'You have uncommitted changes. Consider committing first so the upgrade is easily revertable.',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
// Not a git repo or git not available — no warning needed
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Warn about running containers
|
|
335
|
+
try {
|
|
336
|
+
const { execFileSync } = await import('node:child_process');
|
|
337
|
+
const ps = execFileSync('docker', ['compose', 'ps', '--format', 'json'], {
|
|
338
|
+
cwd,
|
|
339
|
+
encoding: 'utf-8',
|
|
340
|
+
timeout: 10000,
|
|
341
|
+
}).trim();
|
|
342
|
+
if (ps) {
|
|
343
|
+
p.log.warn(
|
|
344
|
+
`Docker containers are running. Consider running ${c.info('vibecarbon down')} first.`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// Docker not available or not running — fine
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Reconstruct template variables
|
|
352
|
+
const s = p.spinner();
|
|
353
|
+
s.start('Scanning infrastructure files');
|
|
354
|
+
|
|
355
|
+
const variables = reconstructVariables(cwd);
|
|
356
|
+
const upgradeableFiles = getUpgradeableFiles(TEMPLATE_DIR);
|
|
357
|
+
|
|
358
|
+
// Classify each file
|
|
359
|
+
const autoUpdated = []; // { path, reason: 'updated' | 'added' }
|
|
360
|
+
const unchanged = []; // files with no diff
|
|
361
|
+
const needsReview = []; // { path, oldContent, newContent }
|
|
362
|
+
|
|
363
|
+
for (const relPath of upgradeableFiles) {
|
|
364
|
+
const policy = getFilePolicy(relPath);
|
|
365
|
+
const projectFilePath = join(cwd, relPath);
|
|
366
|
+
const templateFilePath = join(TEMPLATE_DIR, relPath);
|
|
367
|
+
const fileExists = existsSync(projectFilePath);
|
|
368
|
+
|
|
369
|
+
// Skip optional-feature files that don't exist in the project yet.
|
|
370
|
+
// These are added by `vibecarbon add` or `vibecarbon deploy`, not during upgrade.
|
|
371
|
+
if (!fileExists && OPTIONAL_FILE_PREFIXES.some((prefix) => relPath.startsWith(prefix))) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Handle binary files
|
|
376
|
+
if (isBinaryFile(relPath)) {
|
|
377
|
+
if (policy === 'safe' && !fileExists) {
|
|
378
|
+
autoUpdated.push({ path: relPath, reason: 'added' });
|
|
379
|
+
if (!args.dryRun) {
|
|
380
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
381
|
+
copyFileSync(templateFilePath, projectFilePath);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Resolve template with variables (or raw-copy for files with their own {{PLACEHOLDER}} tokens)
|
|
388
|
+
let newContent;
|
|
389
|
+
try {
|
|
390
|
+
if (RAW_COPY_FILES.has(relPath)) {
|
|
391
|
+
newContent = readFileSync(join(TEMPLATE_DIR, relPath), 'utf-8');
|
|
392
|
+
} else {
|
|
393
|
+
newContent = resolveTemplate(relPath, variables);
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
// Template file can't be read — skip
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Smart-merge package.json: update template dep versions, add new template deps,
|
|
401
|
+
// preserve user-added deps and fields, merge pnpm config (overrides, etc.)
|
|
402
|
+
if (relPath === 'package.json') {
|
|
403
|
+
try {
|
|
404
|
+
const pm = detectPackageManager(cwd);
|
|
405
|
+
const templatePkg = JSON.parse(newContent);
|
|
406
|
+
const userPkg = fileExists ? JSON.parse(readFileSync(projectFilePath, 'utf-8')) : {};
|
|
407
|
+
|
|
408
|
+
const merged = fileExists ? mergePackageJson(userPkg, templatePkg) : templatePkg;
|
|
409
|
+
|
|
410
|
+
if (variables.PROJECT_NAME) merged.name = variables.PROJECT_NAME;
|
|
411
|
+
merged.packageManager = getPackageManagerVersion(pm);
|
|
412
|
+
if (pm !== 'pnpm' && merged.scripts) {
|
|
413
|
+
const run = pm === 'npm' ? 'npm run' : pm;
|
|
414
|
+
for (const [key, value] of Object.entries(merged.scripts)) {
|
|
415
|
+
if (typeof value === 'string' && value.includes('pnpm ')) {
|
|
416
|
+
merged.scripts[key] = value.replaceAll('pnpm ', `${run} `);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
newContent = `${JSON.stringify(merged, null, 2)}\n`;
|
|
421
|
+
} catch {
|
|
422
|
+
// If merge fails, use the raw resolved template content
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const newHash = hashContent(newContent);
|
|
427
|
+
|
|
428
|
+
if (!fileExists) {
|
|
429
|
+
// New file in template — add it
|
|
430
|
+
if (policy === 'safe') {
|
|
431
|
+
autoUpdated.push({ path: relPath, reason: 'added' });
|
|
432
|
+
if (!args.dryRun) {
|
|
433
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
434
|
+
writeFileSync(projectFilePath, newContent);
|
|
435
|
+
if (relPath.endsWith('.sh')) chmodSync(projectFilePath, 0o755);
|
|
436
|
+
}
|
|
437
|
+
} else if (policy === 'merge') {
|
|
438
|
+
needsReview.push({ path: relPath, oldContent: null, newContent, reason: 'new' });
|
|
439
|
+
}
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// File exists — compare
|
|
444
|
+
const currentContent = readFileSync(projectFilePath, 'utf-8');
|
|
445
|
+
const currentHash = hashContent(currentContent);
|
|
446
|
+
|
|
447
|
+
// No change needed
|
|
448
|
+
if (currentHash === newHash) {
|
|
449
|
+
unchanged.push(relPath);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const storedHash = storedChecksums[relPath];
|
|
454
|
+
const userModified = storedHash ? currentHash !== storedHash : true;
|
|
455
|
+
|
|
456
|
+
if (args.force) {
|
|
457
|
+
// Force mode: replace everything, backup modified files
|
|
458
|
+
if (userModified) {
|
|
459
|
+
if (!args.dryRun) {
|
|
460
|
+
writeFileSync(`${projectFilePath}.upgrade-backup`, currentContent);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
autoUpdated.push({ path: relPath, reason: 'updated' });
|
|
464
|
+
if (!args.dryRun) {
|
|
465
|
+
writeFileSync(projectFilePath, newContent);
|
|
466
|
+
if (relPath.endsWith('.sh')) chmodSync(projectFilePath, 0o755);
|
|
467
|
+
}
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (policy === 'safe') {
|
|
472
|
+
if (!userModified) {
|
|
473
|
+
// Not modified by user — safe to auto-replace
|
|
474
|
+
autoUpdated.push({ path: relPath, reason: 'updated' });
|
|
475
|
+
if (!args.dryRun) {
|
|
476
|
+
writeFileSync(projectFilePath, newContent);
|
|
477
|
+
if (relPath.endsWith('.sh')) chmodSync(projectFilePath, 0o755);
|
|
478
|
+
}
|
|
479
|
+
} else if (isBootstrap) {
|
|
480
|
+
// Bootstrap: can't tell if user modified — compare with template
|
|
481
|
+
// If the current content differs, prompt for safe files too
|
|
482
|
+
needsReview.push({
|
|
483
|
+
path: relPath,
|
|
484
|
+
oldContent: currentContent,
|
|
485
|
+
newContent,
|
|
486
|
+
reason: 'unknown',
|
|
487
|
+
});
|
|
488
|
+
} else {
|
|
489
|
+
// User modified a safe file — still needs review
|
|
490
|
+
needsReview.push({
|
|
491
|
+
path: relPath,
|
|
492
|
+
oldContent: currentContent,
|
|
493
|
+
newContent,
|
|
494
|
+
reason: 'modified',
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
} else if (policy === 'merge') {
|
|
498
|
+
// Merge files always need review
|
|
499
|
+
needsReview.push({
|
|
500
|
+
path: relPath,
|
|
501
|
+
oldContent: currentContent,
|
|
502
|
+
newContent,
|
|
503
|
+
reason: 'modified',
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
s.stop('Scan complete');
|
|
509
|
+
|
|
510
|
+
// Display results
|
|
511
|
+
if (autoUpdated.length > 0) {
|
|
512
|
+
p.log.success(`${c.bold('AUTO-UPDATED')} (unmodified by you):`);
|
|
513
|
+
for (const { path: filePath, reason } of autoUpdated) {
|
|
514
|
+
const icon = reason === 'added' ? '+' : '\u2713';
|
|
515
|
+
console.log(` ${c.success(icon)} ${filePath}${args.dryRun ? c.dim(' (dry run)') : ''}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (unchanged.length > 0) {
|
|
520
|
+
p.log.info(`${unchanged.length} files already up to date`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Handle files that need review
|
|
524
|
+
let reviewedCount = 0;
|
|
525
|
+
let skippedReviewCount = 0;
|
|
526
|
+
let packageJsonUpdated = autoUpdated.some((f) => f.path === 'package.json');
|
|
527
|
+
|
|
528
|
+
if (needsReview.length > 0 && !args.dryRun) {
|
|
529
|
+
if (args.yes) {
|
|
530
|
+
// Non-interactive: skip all merge/review files
|
|
531
|
+
p.log.info(`${needsReview.length} files need review — skipped (non-interactive mode)`);
|
|
532
|
+
for (const { path: filePath, newContent } of needsReview) {
|
|
533
|
+
const projectFilePath = join(cwd, filePath);
|
|
534
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
535
|
+
writeFileSync(`${projectFilePath}.upgrade-new`, newContent);
|
|
536
|
+
}
|
|
537
|
+
p.log.info(
|
|
538
|
+
`New versions saved as ${c.info('.upgrade-new')} files. Review and apply manually.`,
|
|
539
|
+
);
|
|
540
|
+
skippedReviewCount = needsReview.length;
|
|
541
|
+
} else {
|
|
542
|
+
// Interactive: prompt for each file
|
|
543
|
+
p.log.step(`${c.bold('NEEDS REVIEW')} (${needsReview.length} files):`);
|
|
544
|
+
|
|
545
|
+
for (const { path: filePath, oldContent, newContent, reason } of needsReview) {
|
|
546
|
+
const projectFilePath = join(cwd, filePath);
|
|
547
|
+
const reasonText =
|
|
548
|
+
reason === 'new'
|
|
549
|
+
? c.info('new in template')
|
|
550
|
+
: reason === 'unknown'
|
|
551
|
+
? c.dim('no stored checksum')
|
|
552
|
+
: c.dim('you modified this');
|
|
553
|
+
|
|
554
|
+
const action = await p.select({
|
|
555
|
+
message: `${filePath} (${reasonText})`,
|
|
556
|
+
options: [
|
|
557
|
+
{ value: 'diff', label: 'View diff first' },
|
|
558
|
+
{ value: 'replace', label: 'Replace (backup as .upgrade-backup)' },
|
|
559
|
+
{ value: 'new', label: 'Save new version as .upgrade-new' },
|
|
560
|
+
{ value: 'skip', label: 'Skip' },
|
|
561
|
+
],
|
|
562
|
+
initialValue: 'new',
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
if (p.isCancel(action)) {
|
|
566
|
+
p.cancel('Operation cancelled.');
|
|
567
|
+
process.exit(0);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (action === 'diff') {
|
|
571
|
+
// Show diff, then re-prompt
|
|
572
|
+
if (oldContent) {
|
|
573
|
+
console.log(`\n${c.dim(`--- ${filePath} (current)`)}`);
|
|
574
|
+
console.log(`${c.dim(`+++ ${filePath} (template)`)}`);
|
|
575
|
+
console.log(simpleDiff(oldContent, newContent));
|
|
576
|
+
console.log();
|
|
577
|
+
} else {
|
|
578
|
+
console.log(`\n${c.dim('(new file)')}`);
|
|
579
|
+
const preview = newContent.split('\n').slice(0, 20).join('\n');
|
|
580
|
+
console.log(preview);
|
|
581
|
+
if (newContent.split('\n').length > 20) {
|
|
582
|
+
console.log(c.dim(`... ${newContent.split('\n').length - 20} more lines`));
|
|
583
|
+
}
|
|
584
|
+
console.log();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Re-prompt after viewing diff
|
|
588
|
+
const action2 = await p.select({
|
|
589
|
+
message: `${filePath}`,
|
|
590
|
+
options: [
|
|
591
|
+
{ value: 'replace', label: 'Replace (backup as .upgrade-backup)' },
|
|
592
|
+
{ value: 'new', label: 'Save new version as .upgrade-new' },
|
|
593
|
+
{ value: 'skip', label: 'Skip' },
|
|
594
|
+
],
|
|
595
|
+
initialValue: 'replace',
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
if (p.isCancel(action2)) {
|
|
599
|
+
p.cancel('Operation cancelled.');
|
|
600
|
+
process.exit(0);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (action2 === 'replace') {
|
|
604
|
+
if (oldContent) writeFileSync(`${projectFilePath}.upgrade-backup`, oldContent);
|
|
605
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
606
|
+
writeFileSync(projectFilePath, newContent);
|
|
607
|
+
if (filePath.endsWith('.sh')) chmodSync(projectFilePath, 0o755);
|
|
608
|
+
if (filePath === 'package.json') packageJsonUpdated = true;
|
|
609
|
+
reviewedCount++;
|
|
610
|
+
} else if (action2 === 'new') {
|
|
611
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
612
|
+
writeFileSync(`${projectFilePath}.upgrade-new`, newContent);
|
|
613
|
+
skippedReviewCount++;
|
|
614
|
+
} else {
|
|
615
|
+
skippedReviewCount++;
|
|
616
|
+
}
|
|
617
|
+
} else if (action === 'replace') {
|
|
618
|
+
if (oldContent) writeFileSync(`${projectFilePath}.upgrade-backup`, oldContent);
|
|
619
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
620
|
+
writeFileSync(projectFilePath, newContent);
|
|
621
|
+
if (filePath.endsWith('.sh')) chmodSync(projectFilePath, 0o755);
|
|
622
|
+
if (filePath === 'package.json') packageJsonUpdated = true;
|
|
623
|
+
reviewedCount++;
|
|
624
|
+
} else if (action === 'new') {
|
|
625
|
+
mkdirSync(dirname(projectFilePath), { recursive: true });
|
|
626
|
+
writeFileSync(`${projectFilePath}.upgrade-new`, newContent);
|
|
627
|
+
skippedReviewCount++;
|
|
628
|
+
} else {
|
|
629
|
+
skippedReviewCount++;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} else if (needsReview.length > 0 && args.dryRun) {
|
|
634
|
+
p.log.step(`${c.bold('NEEDS REVIEW')} (${needsReview.length} files):`);
|
|
635
|
+
for (const { path: filePath, reason } of needsReview) {
|
|
636
|
+
const reasonText =
|
|
637
|
+
reason === 'new'
|
|
638
|
+
? 'new in template'
|
|
639
|
+
: reason === 'unknown'
|
|
640
|
+
? 'no stored checksum'
|
|
641
|
+
: 'modified by you';
|
|
642
|
+
console.log(` ${c.dim('?')} ${filePath} ${c.dim(`(${reasonText})`)}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Re-apply package manager adaptations to upgraded files.
|
|
647
|
+
// The template is always pnpm-based; bun/npm projects need re-adaptation.
|
|
648
|
+
if (!args.dryRun) {
|
|
649
|
+
const pm = detectPackageManager(cwd);
|
|
650
|
+
if (pm !== 'pnpm') {
|
|
651
|
+
adaptDockerfileForPackageManager(cwd, pm);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Regenerate the lockfile so it stays in sync with the updated package.json.
|
|
656
|
+
// Without this, `--frozen-lockfile` in the Dockerfile will fail on deploy.
|
|
657
|
+
if (!args.dryRun && packageJsonUpdated) {
|
|
658
|
+
const pm = detectPackageManager(cwd);
|
|
659
|
+
const installCmd = {
|
|
660
|
+
pnpm: ['pnpm', 'install'],
|
|
661
|
+
npm: ['npm', 'install'],
|
|
662
|
+
bun: ['bun', 'install'],
|
|
663
|
+
}[pm] || ['pnpm', 'install'];
|
|
664
|
+
|
|
665
|
+
const s2 = p.spinner();
|
|
666
|
+
s2.start('Regenerating lockfile…');
|
|
667
|
+
try {
|
|
668
|
+
await runCommandAsync(installCmd, { cwd, silent: true });
|
|
669
|
+
s2.stop(`${c.success('✓')} Lockfile updated`);
|
|
670
|
+
} catch {
|
|
671
|
+
s2.stop('');
|
|
672
|
+
p.log.warn('Failed to regenerate lockfile — run install manually before deploying');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Update manifest with new checksums and template version (unless dry run)
|
|
677
|
+
if (!args.dryRun) {
|
|
678
|
+
const newChecksums = {};
|
|
679
|
+
|
|
680
|
+
// Recompute checksums for all upgradeable files that now exist
|
|
681
|
+
for (const relPath of upgradeableFiles) {
|
|
682
|
+
if (isBinaryFile(relPath)) continue;
|
|
683
|
+
const projectFilePath = join(cwd, relPath);
|
|
684
|
+
if (existsSync(projectFilePath)) {
|
|
685
|
+
try {
|
|
686
|
+
newChecksums[relPath] = hashFile(projectFilePath);
|
|
687
|
+
} catch {
|
|
688
|
+
// Skip files that can't be read
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
manifest.templateVersion = VERSION;
|
|
694
|
+
manifest.fileChecksums = newChecksums;
|
|
695
|
+
saveManifest(manifest, cwd);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Summary
|
|
699
|
+
const totalUpdated = autoUpdated.length + reviewedCount;
|
|
700
|
+
const totalReview = skippedReviewCount;
|
|
701
|
+
|
|
702
|
+
if (args.dryRun) {
|
|
703
|
+
p.outro(
|
|
704
|
+
`Dry run complete — ${autoUpdated.length} would update, ${needsReview.length} need review, ${unchanged.length} unchanged`,
|
|
705
|
+
);
|
|
706
|
+
} else {
|
|
707
|
+
p.outro(
|
|
708
|
+
`Upgrade complete (${totalUpdated} updated, ${totalReview} need review, ${unchanged.length} unchanged)\n Template: ${currentTemplateVersion} → ${VERSION}`,
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export async function run(args) {
|
|
714
|
+
await main(args);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ============================================================================
|
|
718
|
+
// EXPORTS FOR TESTING
|
|
719
|
+
// ============================================================================
|
|
720
|
+
|
|
721
|
+
export { reconstructVariables, resolveTemplate, SPEC };
|