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,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Tracker — step-level timing and persistent log files for CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const tracker = createTracker('deploy', { environment: 'prod' });
|
|
6
|
+
* const s = tracker.spinner();
|
|
7
|
+
* s.start('Provisioning cluster');
|
|
8
|
+
* s.stop('Cluster provisioned');
|
|
9
|
+
* const { formatted } = tracker.finish();
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createWriteStream, existsSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import * as p from '@clack/prompts';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format milliseconds into a human-readable duration string.
|
|
18
|
+
* @param {number} ms
|
|
19
|
+
* @returns {string} e.g. "3s", "2m 14s"
|
|
20
|
+
*/
|
|
21
|
+
function formatDuration(ms) {
|
|
22
|
+
const totalSecs = Math.round(ms / 1000);
|
|
23
|
+
const mins = Math.floor(totalSecs / 60);
|
|
24
|
+
const secs = totalSecs % 60;
|
|
25
|
+
return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format a timestamp for log lines (HH:MM:SS)
|
|
30
|
+
* @param {Date} date
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function formatTime(date) {
|
|
34
|
+
return date.toISOString().slice(11, 19);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Right-pad a duration string to a fixed width for aligned output.
|
|
39
|
+
* @param {string} str
|
|
40
|
+
* @param {number} width
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function padDuration(str, width = 8) {
|
|
44
|
+
return str.padStart(width);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a CommandTracker for a CLI command.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} command - Command name (e.g. 'deploy', 'destroy')
|
|
51
|
+
* @param {object} [meta] - Key/value metadata written to the log header
|
|
52
|
+
* @returns {CommandTracker}
|
|
53
|
+
*/
|
|
54
|
+
export function createTracker(command, meta = {}) {
|
|
55
|
+
return new CommandTracker(command, meta);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a prefixed tracker for parallel operations (e.g., HA deploys).
|
|
60
|
+
* Instead of real @clack spinners (which corrupt when two run concurrently),
|
|
61
|
+
* this uses p.log.info with a region/label prefix for each step.
|
|
62
|
+
*
|
|
63
|
+
* Duck-type compatible with CommandTracker — can be passed as options.tracker.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} label - Prefix label (e.g., "fsn1", "nbg1")
|
|
66
|
+
* @param {object} [opts] - Options
|
|
67
|
+
* @param {CommandTracker} [opts.parent] - Parent tracker for log file writes
|
|
68
|
+
* @returns {object} Tracker-compatible object
|
|
69
|
+
*/
|
|
70
|
+
export function createPrefixedTracker(label, opts = {}) {
|
|
71
|
+
const parent = opts.parent;
|
|
72
|
+
const dim = (s) => `\x1b[2m${s}\x1b[22m`;
|
|
73
|
+
const prefix = dim(`[${label}]`);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
spinner() {
|
|
77
|
+
let stepStart = null;
|
|
78
|
+
let stepLabel = null;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
start(msg) {
|
|
82
|
+
stepStart = performance.now();
|
|
83
|
+
stepLabel = msg;
|
|
84
|
+
p.log.step(`${prefix} ${msg}`);
|
|
85
|
+
parent?.log?.(`[${label}] START ${msg}`);
|
|
86
|
+
},
|
|
87
|
+
stop(msg) {
|
|
88
|
+
const elapsed = stepStart ? formatDuration(performance.now() - stepStart) : '';
|
|
89
|
+
const display = msg || stepLabel;
|
|
90
|
+
p.log.info(`${prefix} ${display} ${dim(`(${elapsed})`)}`);
|
|
91
|
+
parent?.log?.(`[${label}] DONE ${display} (${elapsed})`);
|
|
92
|
+
stepStart = null;
|
|
93
|
+
stepLabel = null;
|
|
94
|
+
},
|
|
95
|
+
message(msg) {
|
|
96
|
+
p.log.info(`${prefix} ${msg}`);
|
|
97
|
+
parent?.log?.(`[${label}] UPDATE ${msg}`);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
log(msg) {
|
|
102
|
+
parent?.log?.(`[${label}] ${msg}`);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class CommandTracker {
|
|
108
|
+
constructor(command, meta) {
|
|
109
|
+
this.command = command;
|
|
110
|
+
this.meta = meta;
|
|
111
|
+
this.startTime = performance.now();
|
|
112
|
+
this.steps = []; // { label, durationMs }
|
|
113
|
+
this._currentStep = null; // { label, startTime }
|
|
114
|
+
this._stream = null;
|
|
115
|
+
|
|
116
|
+
// Open log file
|
|
117
|
+
const logsDir = join(process.cwd(), '.vibecarbon', 'logs');
|
|
118
|
+
try {
|
|
119
|
+
if (!existsSync(logsDir)) {
|
|
120
|
+
mkdirSync(logsDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
123
|
+
const envSuffix = meta.environment ? `-${meta.environment}` : '';
|
|
124
|
+
const filename = `${command}${envSuffix}-${timestamp}.log`;
|
|
125
|
+
this.logPath = join(logsDir, filename);
|
|
126
|
+
this._stream = createWriteStream(this.logPath, { flags: 'a' });
|
|
127
|
+
|
|
128
|
+
// Write header
|
|
129
|
+
this._write(`=== vibecarbon ${command} ===`);
|
|
130
|
+
this._write(`Started: ${new Date().toISOString()}`);
|
|
131
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
132
|
+
if (value != null) {
|
|
133
|
+
this._write(` ${key}: ${value}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this._write('');
|
|
137
|
+
} catch {
|
|
138
|
+
// If we can't create the log file (e.g. no .vibecarbon dir), continue without logging
|
|
139
|
+
this.logPath = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a spinner wrapper that records timing and writes to the log.
|
|
145
|
+
* Duck-type compatible with p.spinner().
|
|
146
|
+
* @returns {{ start(msg: string): void, stop(msg: string, code?: number): void, message(msg: string): void }}
|
|
147
|
+
*/
|
|
148
|
+
spinner() {
|
|
149
|
+
let realSpinner = p.spinner();
|
|
150
|
+
const tracker = this;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
start(msg) {
|
|
154
|
+
// If a previous step wasn't stopped, record it as-is
|
|
155
|
+
if (tracker._currentStep) {
|
|
156
|
+
const elapsed = performance.now() - tracker._currentStep.startTime;
|
|
157
|
+
tracker.steps.push({ label: tracker._currentStep.label, durationMs: elapsed });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
tracker._currentStep = { label: msg, startTime: performance.now() };
|
|
161
|
+
tracker._write(`[${formatTime(new Date())}] START ${msg}`);
|
|
162
|
+
// Create a fresh spinner — clack spinners don't cleanly restart after stop()
|
|
163
|
+
realSpinner = p.spinner();
|
|
164
|
+
realSpinner.start(msg);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
stop(msg, code) {
|
|
168
|
+
if (tracker._currentStep) {
|
|
169
|
+
const elapsed = performance.now() - tracker._currentStep.startTime;
|
|
170
|
+
tracker.steps.push({ label: msg || tracker._currentStep.label, durationMs: elapsed });
|
|
171
|
+
tracker._write(
|
|
172
|
+
`[${formatTime(new Date())}] DONE ${msg || tracker._currentStep.label} (${formatDuration(elapsed)})`,
|
|
173
|
+
);
|
|
174
|
+
tracker._currentStep = null;
|
|
175
|
+
}
|
|
176
|
+
realSpinner.stop(msg, code);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
message(msg) {
|
|
180
|
+
tracker._write(`[${formatTime(new Date())}] UPDATE ${msg}`);
|
|
181
|
+
realSpinner.message(msg);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Write an arbitrary line to the log file.
|
|
188
|
+
* @param {string} msg
|
|
189
|
+
*/
|
|
190
|
+
log(msg) {
|
|
191
|
+
this._write(`[${formatTime(new Date())}] LOG ${msg}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Finalize tracking: write summary to log, print timing table to console.
|
|
196
|
+
* @returns {{ elapsed: number, formatted: string, logPath: string | null }}
|
|
197
|
+
*/
|
|
198
|
+
finish() {
|
|
199
|
+
const totalMs = performance.now() - this.startTime;
|
|
200
|
+
const formatted = formatDuration(totalMs);
|
|
201
|
+
|
|
202
|
+
// Write step summary to log
|
|
203
|
+
if (this.steps.length > 0) {
|
|
204
|
+
this._write('');
|
|
205
|
+
this._write('=== Step Summary ===');
|
|
206
|
+
for (const step of this.steps) {
|
|
207
|
+
this._write(` ${padDuration(formatDuration(step.durationMs))} ${step.label}`);
|
|
208
|
+
}
|
|
209
|
+
this._write(` ${padDuration(formatted)} TOTAL`);
|
|
210
|
+
}
|
|
211
|
+
this._write('');
|
|
212
|
+
this._write(`Finished: ${new Date().toISOString()}`);
|
|
213
|
+
|
|
214
|
+
// Close stream
|
|
215
|
+
if (this._stream) {
|
|
216
|
+
this._stream.end();
|
|
217
|
+
this._stream = null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Print timing stats table to console (only if >1 step)
|
|
221
|
+
if (this.steps.length > 1) {
|
|
222
|
+
const lines = this.steps.map(
|
|
223
|
+
(step) => `${padDuration(formatDuration(step.durationMs))} ${step.label}`,
|
|
224
|
+
);
|
|
225
|
+
lines.push(`${padDuration(formatted)} Total`);
|
|
226
|
+
p.note(lines.join('\n'), 'Stats');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { elapsed: totalMs, formatted, logPath: this.logPath };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Write a line to the log file stream.
|
|
234
|
+
* @param {string} line
|
|
235
|
+
* @private
|
|
236
|
+
*/
|
|
237
|
+
_write(line) {
|
|
238
|
+
if (this._stream) {
|
|
239
|
+
this._stream.write(`${line}\n`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade policy — classifies template files into safe / merge / never categories
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readdirSync } from 'node:fs';
|
|
6
|
+
import { join, relative } from 'node:path';
|
|
7
|
+
|
|
8
|
+
// Files that can be auto-replaced when unmodified by the user
|
|
9
|
+
const SAFE_PATTERNS = [
|
|
10
|
+
'Dockerfile',
|
|
11
|
+
'docker-entrypoint.sh',
|
|
12
|
+
'.dockerignore',
|
|
13
|
+
'docker-compose.prod.yml',
|
|
14
|
+
'docker-compose.observability.yml',
|
|
15
|
+
'docker-compose.observability.prod.yml',
|
|
16
|
+
'docker-compose.observability.override.yml',
|
|
17
|
+
'docker-compose.metabase.yml',
|
|
18
|
+
'docker-compose.metabase.override.yml',
|
|
19
|
+
'docker-compose.n8n.yml',
|
|
20
|
+
'docker-compose.n8n.override.yml',
|
|
21
|
+
'docker-compose.dev-init.yml',
|
|
22
|
+
'k8s/base/**',
|
|
23
|
+
'k8s/infra/**',
|
|
24
|
+
'k8s/flux/**',
|
|
25
|
+
'cloud-init/**',
|
|
26
|
+
'.github/workflows/**',
|
|
27
|
+
'biome.json',
|
|
28
|
+
'tsconfig.json',
|
|
29
|
+
'tsconfig.server.json',
|
|
30
|
+
'components.json',
|
|
31
|
+
'backup/**',
|
|
32
|
+
'scripts/**',
|
|
33
|
+
'volumes/kong/**',
|
|
34
|
+
'volumes/grafana/**',
|
|
35
|
+
'volumes/prometheus/**',
|
|
36
|
+
'volumes/loki/**',
|
|
37
|
+
'volumes/promtail/**',
|
|
38
|
+
'volumes/traefik/**',
|
|
39
|
+
'AGENTS.md',
|
|
40
|
+
'CLAUDE.md',
|
|
41
|
+
'PRODUCTION.md',
|
|
42
|
+
'DEVELOPMENT.md',
|
|
43
|
+
'.windsurfrules',
|
|
44
|
+
'.github/copilot-instructions.md',
|
|
45
|
+
'.cursor/rules/**',
|
|
46
|
+
'.claude/settings.json',
|
|
47
|
+
'.claude/hooks/**',
|
|
48
|
+
'.claude/agents/**',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Files that always require user review
|
|
52
|
+
const MERGE_PATTERNS = [
|
|
53
|
+
'docker-compose.yml',
|
|
54
|
+
'docker-compose.override.yml',
|
|
55
|
+
'package.json',
|
|
56
|
+
'vite.config.ts',
|
|
57
|
+
'k8s/overlays/**',
|
|
58
|
+
'.gitignore',
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Files that should never be touched
|
|
62
|
+
const NEVER_PATTERNS = [
|
|
63
|
+
'src/**',
|
|
64
|
+
'content/**',
|
|
65
|
+
'supabase/**',
|
|
66
|
+
'.env',
|
|
67
|
+
'.env.local',
|
|
68
|
+
'.env.example',
|
|
69
|
+
'README.md',
|
|
70
|
+
'volumes/db/**',
|
|
71
|
+
'node_modules/**',
|
|
72
|
+
'dist/**',
|
|
73
|
+
'pnpm-lock.yaml',
|
|
74
|
+
'package-lock.json',
|
|
75
|
+
'bun.lock',
|
|
76
|
+
'bun.lockb',
|
|
77
|
+
'.git/**',
|
|
78
|
+
'.claude/agent-memory/**',
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert a simple glob pattern to a RegExp.
|
|
83
|
+
* Supports `**` (any path depth) and `*` (any filename chars).
|
|
84
|
+
*
|
|
85
|
+
* @param {string} pattern
|
|
86
|
+
* @returns {RegExp}
|
|
87
|
+
*/
|
|
88
|
+
function globToRegex(pattern) {
|
|
89
|
+
const re = pattern
|
|
90
|
+
// Escape regex-special chars except * and /
|
|
91
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
92
|
+
// ** matches any number of path segments (including zero)
|
|
93
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}')
|
|
94
|
+
// * matches anything except path separator
|
|
95
|
+
.replace(/\*/g, '[^/]*')
|
|
96
|
+
.replace(/\{\{GLOBSTAR\}\}/g, '.*');
|
|
97
|
+
return new RegExp(`^${re}$`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Test if a path matches any pattern in a list
|
|
102
|
+
*
|
|
103
|
+
* @param {string} filePath
|
|
104
|
+
* @param {string[]} patterns
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function matchesAny(filePath, patterns) {
|
|
108
|
+
return patterns.some((p) => globToRegex(p).test(filePath));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Classify a file path into an upgrade policy category
|
|
113
|
+
*
|
|
114
|
+
* @param {string} relativePath - Path relative to project root
|
|
115
|
+
* @returns {'safe' | 'merge' | 'never'}
|
|
116
|
+
*/
|
|
117
|
+
export function getFilePolicy(relativePath) {
|
|
118
|
+
if (matchesAny(relativePath, NEVER_PATTERNS)) return 'never';
|
|
119
|
+
if (matchesAny(relativePath, MERGE_PATTERNS)) return 'merge';
|
|
120
|
+
if (matchesAny(relativePath, SAFE_PATTERNS)) return 'safe';
|
|
121
|
+
// Default: files not explicitly classified are never touched
|
|
122
|
+
return 'never';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Walk a directory tree and return all file paths relative to rootDir
|
|
127
|
+
*
|
|
128
|
+
* @param {string} rootDir - Root directory to walk
|
|
129
|
+
* @returns {string[]} - Array of relative file paths
|
|
130
|
+
*/
|
|
131
|
+
function walkDir(rootDir) {
|
|
132
|
+
const results = [];
|
|
133
|
+
|
|
134
|
+
function walk(dir) {
|
|
135
|
+
let entries;
|
|
136
|
+
try {
|
|
137
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
138
|
+
} catch {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const fullPath = join(dir, entry.name);
|
|
143
|
+
if (entry.isDirectory()) {
|
|
144
|
+
// Skip node_modules and .git in template
|
|
145
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
146
|
+
walk(fullPath);
|
|
147
|
+
} else {
|
|
148
|
+
results.push(relative(rootDir, fullPath));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
walk(rootDir);
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get all upgradeable files from the template directory
|
|
159
|
+
* Returns files that are classified as 'safe' or 'merge'
|
|
160
|
+
*
|
|
161
|
+
* @param {string} templateDir - Path to the carbon/ template directory
|
|
162
|
+
* @returns {string[]} - Array of relative file paths
|
|
163
|
+
*/
|
|
164
|
+
export function getUpgradeableFiles(templateDir) {
|
|
165
|
+
const allFiles = walkDir(templateDir);
|
|
166
|
+
return allFiles.filter((f) => {
|
|
167
|
+
const policy = getFilePolicy(f);
|
|
168
|
+
return policy === 'safe' || policy === 'merge';
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validators shared across create, cli, restore, backup, configure.
|
|
3
|
+
*
|
|
4
|
+
* Every validator returns `undefined` on success and a human-readable error
|
|
5
|
+
* string on failure. This matches @clack/prompts' `validate` contract and
|
|
6
|
+
* lets the same function work for both interactive prompts and non-interactive
|
|
7
|
+
* CLI argument validation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { basename } from 'node:path';
|
|
11
|
+
|
|
12
|
+
const RESERVED_PROJECT_NAMES = new Set(['node_modules', 'dist', 'build', 'coverage', '__tests__']);
|
|
13
|
+
|
|
14
|
+
const PROJECT_NAME_RE = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validate a project name. Must be a bare basename, lowercase DNS-safe,
|
|
18
|
+
* not reserved, max 63 characters.
|
|
19
|
+
* Returns undefined on success, error string on failure.
|
|
20
|
+
*/
|
|
21
|
+
export function validateProjectName(name) {
|
|
22
|
+
if (!name) return 'Project name is required';
|
|
23
|
+
if (name !== basename(name) || name.startsWith('.')) {
|
|
24
|
+
return 'Project name must be a basename (no slashes, no ../, no dot-names)';
|
|
25
|
+
}
|
|
26
|
+
if (name.length > 63) return 'Project name must be 63 characters or fewer';
|
|
27
|
+
if (RESERVED_PROJECT_NAMES.has(name.toLowerCase())) {
|
|
28
|
+
return `"${name}" is a reserved name`;
|
|
29
|
+
}
|
|
30
|
+
if (!PROJECT_NAME_RE.test(name)) {
|
|
31
|
+
return 'Project name must be lowercase, start with a letter, and contain only letters, digits, and hyphens';
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const EMAIL_RE = /^[A-Za-z0-9._+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate an admin email address.
|
|
40
|
+
* Returns undefined on success, error string on failure.
|
|
41
|
+
*/
|
|
42
|
+
export function validateAdminEmail(email) {
|
|
43
|
+
if (!email) return 'Email is required';
|
|
44
|
+
if (!EMAIL_RE.test(email)) {
|
|
45
|
+
return 'Email must be a simple address (letters, digits, . _ + -, single @)';
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PRINTABLE_ASCII_RE = /^[\x20-\x7e]+$/;
|
|
51
|
+
// Block chars that break single-quote escaping or cause expansion if quoting is
|
|
52
|
+
// accidentally skipped. Other shell-special chars (| ; & etc.) are safe inside
|
|
53
|
+
// shEscape's single-quoted output, so they're intentionally not blocked here.
|
|
54
|
+
const SHELL_METACHARS_RE = /['"`$\\]/;
|
|
55
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — rejecting control chars
|
|
56
|
+
const CONTROL_CHARS_RE = /[\u0000-\u001f\u007f]/;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate an admin password. Must be printable ASCII, no shell metacharacters.
|
|
60
|
+
* Returns undefined on success, error string on failure.
|
|
61
|
+
*/
|
|
62
|
+
export function validateAdminPassword(password) {
|
|
63
|
+
if (!password || password.length < 8) return 'Password must be at least 8 characters';
|
|
64
|
+
if (CONTROL_CHARS_RE.test(password)) return 'Password must not contain control characters';
|
|
65
|
+
if (SHELL_METACHARS_RE.test(password)) {
|
|
66
|
+
return `Password must not contain ', ", backtick, $, or \\`;
|
|
67
|
+
}
|
|
68
|
+
if (!PRINTABLE_ASCII_RE.test(password)) return 'Password must be printable ASCII only';
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const DOMAIN_LABEL_RE = /^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate a domain name. Must be a valid DNS hostname, max 253 characters.
|
|
76
|
+
* Returns undefined on success, error string on failure.
|
|
77
|
+
*/
|
|
78
|
+
export function validateDomain(domain) {
|
|
79
|
+
if (!domain) return 'Domain is required';
|
|
80
|
+
if (domain.length > 253) return 'Domain must be 253 characters or fewer';
|
|
81
|
+
if (domain.startsWith('.') || domain.endsWith('.')) {
|
|
82
|
+
return 'Domain must not start or end with a dot';
|
|
83
|
+
}
|
|
84
|
+
const labels = domain.split('.');
|
|
85
|
+
if (labels.length < 2) return 'Domain must contain at least one dot';
|
|
86
|
+
for (const label of labels) {
|
|
87
|
+
if (!DOMAIN_LABEL_RE.test(label)) return `"${label}" is not a valid domain label`;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const BACKUP_FILENAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*\.(tar\.gz|sql\.gz|tar|sql)$/;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate a backup filename. Must be a safe basename with a recognised extension.
|
|
96
|
+
* Returns undefined on success, error string on failure.
|
|
97
|
+
*/
|
|
98
|
+
export function validateBackupFilename(name) {
|
|
99
|
+
if (!name) return 'Backup filename is required';
|
|
100
|
+
if (name !== basename(name)) return 'Backup filename must be a basename (no paths)';
|
|
101
|
+
if (!BACKUP_FILENAME_RE.test(name)) {
|
|
102
|
+
return 'Backup filename must match <safe-name>.(tar|sql)[.gz]';
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|