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,53 @@
|
|
|
1
|
+
import { Client } from 'pg';
|
|
2
|
+
import { env } from '../src/server/lib/env';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CI/CD Security Check
|
|
6
|
+
* Verifies that Row Level Security (RLS) is enabled on ALL tables in the 'public' schema.
|
|
7
|
+
*
|
|
8
|
+
* This ensures that no table is accidentally left open to public access
|
|
9
|
+
* (especially now that we rely on RLS as the primary defense mechanism).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
console.log('🔒 Verifying Row Level Security (RLS) policies...');
|
|
14
|
+
|
|
15
|
+
// Use connection string from env or default to local dev
|
|
16
|
+
const connectionString =
|
|
17
|
+
process.env.DATABASE_URL ||
|
|
18
|
+
`postgres://postgres:${process.env.POSTGRES_PASSWORD || 'postgres'}@localhost:5432/postgres`;
|
|
19
|
+
|
|
20
|
+
const client = new Client({ connectionString });
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
await client.connect();
|
|
24
|
+
|
|
25
|
+
// Query finding tables in public schema that have RLS disabled
|
|
26
|
+
const result = await client.query(`
|
|
27
|
+
SELECT tablename
|
|
28
|
+
FROM pg_tables
|
|
29
|
+
WHERE schemaname = 'public'
|
|
30
|
+
AND rowsecurity = false;
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
if (result.rows.length > 0) {
|
|
34
|
+
console.error('\n❌ CRITICAL SECURITY FAILURE: The following tables do not have RLS enabled:');
|
|
35
|
+
result.rows.forEach(row => {
|
|
36
|
+
console.error(` - ${row.tablename}`);
|
|
37
|
+
});
|
|
38
|
+
console.error('\nResult: Security check FAILED. All public tables must have RLS enabled.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('✅ Success: All tables in public schema have RLS enabled.');
|
|
43
|
+
process.exit(0);
|
|
44
|
+
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('Error running security check:', err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
} finally {
|
|
49
|
+
await client.end();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main();
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate .env and .env.local for developing directly in the carbon/ template.
|
|
4
|
+
*
|
|
5
|
+
* Uses the same hardcoded dev values as generate-dev-configs.sh
|
|
6
|
+
* so everything works out of the box without running `vibecarbon create`.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/dev-init.js # Generate if missing
|
|
10
|
+
* node scripts/dev-init.js --force # Overwrite existing files
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const force = process.argv.includes('--force');
|
|
16
|
+
|
|
17
|
+
// Dev values matching generate-dev-configs.sh.
|
|
18
|
+
// Note: ANON_KEY / SERVICE_ROLE_KEY are NOT hardcoded here — they're
|
|
19
|
+
// signed at runtime against JWT_SECRET below. Committing literal JWT
|
|
20
|
+
// strings tripped GitHub's secret-scanner on every push; signing on
|
|
21
|
+
// the fly keeps the dev workflow deterministic without leaking
|
|
22
|
+
// service-role-shaped values into git history.
|
|
23
|
+
const DEV = {
|
|
24
|
+
PROJECT_NAME: 'vibecarbon',
|
|
25
|
+
JWT_SECRET: 'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded',
|
|
26
|
+
DB_ENC_KEY: 'devdbencryptkey1',
|
|
27
|
+
VAULT_ENC_KEY: 'local-vault-encryption-key-1234567890',
|
|
28
|
+
REALTIME_SECRET: 'local-realtime-secret-pad',
|
|
29
|
+
DB_PASSWORD: 'local-postgres-password-for-dev-only',
|
|
30
|
+
ANON_KEY: signDevJwt('anon', 'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded'),
|
|
31
|
+
SERVICE_ROLE_KEY: signDevJwt(
|
|
32
|
+
'service_role',
|
|
33
|
+
'local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded',
|
|
34
|
+
),
|
|
35
|
+
ADMIN_EMAIL: 'admin@example.com',
|
|
36
|
+
ADMIN_PASSWORD: 'local-admin-password',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function signDevJwt(role, secret) {
|
|
40
|
+
const { createHmac } = require('node:crypto');
|
|
41
|
+
const b64url = (buf) =>
|
|
42
|
+
Buffer.from(buf)
|
|
43
|
+
.toString('base64')
|
|
44
|
+
.replace(/=+$/, '')
|
|
45
|
+
.replace(/\+/g, '-')
|
|
46
|
+
.replace(/\//g, '_');
|
|
47
|
+
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
|
48
|
+
const payload = b64url(
|
|
49
|
+
JSON.stringify({
|
|
50
|
+
iss: 'supabase-demo',
|
|
51
|
+
role,
|
|
52
|
+
exp: Math.floor(Date.now() / 1000) + 100 * 365 * 24 * 60 * 60,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
const sig = b64url(createHmac('sha256', secret).update(`${header}.${payload}`).digest());
|
|
56
|
+
return `${header}.${payload}.${sig}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const envContent = `# =============================================================================
|
|
60
|
+
# DEV ENVIRONMENT - Generated by \`pnpm dev:init\`
|
|
61
|
+
# Matches hardcoded values in generate-dev-configs.sh
|
|
62
|
+
# DO NOT COMMIT THIS FILE
|
|
63
|
+
# =============================================================================
|
|
64
|
+
|
|
65
|
+
# Project
|
|
66
|
+
PROJECT_NAME="${DEV.PROJECT_NAME}"
|
|
67
|
+
VITE_PROJECT_NAME="${DEV.PROJECT_NAME}"
|
|
68
|
+
|
|
69
|
+
# Supabase URLs (base ports — DEV_PORT_OFFSET is applied dynamically at runtime
|
|
70
|
+
# by src/server/lib/env.ts and vite.config.ts, so these stay at default 8000)
|
|
71
|
+
SUPABASE_URL="http://localhost:8000"
|
|
72
|
+
VITE_SUPABASE_URL="http://localhost:8000"
|
|
73
|
+
|
|
74
|
+
# Supabase Keys (standard Supabase demo keys)
|
|
75
|
+
SUPABASE_ANON_KEY="${DEV.ANON_KEY}"
|
|
76
|
+
VITE_SUPABASE_ANON_KEY="${DEV.ANON_KEY}"
|
|
77
|
+
SUPABASE_SERVICE_ROLE_KEY="${DEV.SERVICE_ROLE_KEY}"
|
|
78
|
+
|
|
79
|
+
# JWT Secret
|
|
80
|
+
JWT_SECRET="${DEV.JWT_SECRET}"
|
|
81
|
+
|
|
82
|
+
# Database
|
|
83
|
+
DB_PASSWORD="${DEV.DB_PASSWORD}"
|
|
84
|
+
POSTGRES_PASSWORD="${DEV.DB_PASSWORD}"
|
|
85
|
+
|
|
86
|
+
# Encryption keys (DB_ENC_KEY must be exactly 16 bytes for Realtime AES-128-ECB)
|
|
87
|
+
DB_ENC_KEY="${DEV.DB_ENC_KEY}"
|
|
88
|
+
VAULT_ENC_KEY="${DEV.VAULT_ENC_KEY}"
|
|
89
|
+
REALTIME_SECRET="${DEV.REALTIME_SECRET}"
|
|
90
|
+
|
|
91
|
+
# Admin credentials (matches super-admin.dev.sql)
|
|
92
|
+
ADMIN_EMAIL="${DEV.ADMIN_EMAIL}"
|
|
93
|
+
ADMIN_PASSWORD="${DEV.ADMIN_PASSWORD}"
|
|
94
|
+
|
|
95
|
+
# Site URL (base port — offset applied dynamically, see above)
|
|
96
|
+
SITE_URL="http://localhost:5173"
|
|
97
|
+
|
|
98
|
+
# OAuth (disabled for dev)
|
|
99
|
+
GOOGLE_ENABLED="false"
|
|
100
|
+
GOOGLE_CLIENT_ID=""
|
|
101
|
+
GOOGLE_CLIENT_SECRET=""
|
|
102
|
+
|
|
103
|
+
MICROSOFT_ENABLED="false"
|
|
104
|
+
MICROSOFT_CLIENT_ID=""
|
|
105
|
+
MICROSOFT_CLIENT_SECRET=""
|
|
106
|
+
MICROSOFT_TENANT_ID=""
|
|
107
|
+
|
|
108
|
+
# External services (disabled for dev)
|
|
109
|
+
STRIPE_SECRET_KEY=""
|
|
110
|
+
STRIPE_WEBHOOK_SECRET=""
|
|
111
|
+
SMTP_HOST=""
|
|
112
|
+
SMTP_PORT="587"
|
|
113
|
+
SMTP_USER=""
|
|
114
|
+
SMTP_PASS=""
|
|
115
|
+
# SMTP_ADMIN_EMAIL=""
|
|
116
|
+
SMTP_SENDER_NAME="${DEV.PROJECT_NAME}"
|
|
117
|
+
|
|
118
|
+
# Optional services
|
|
119
|
+
VITE_N8N_ENABLED="false"
|
|
120
|
+
VITE_OBSERVABILITY_ENABLED="false"
|
|
121
|
+
|
|
122
|
+
# Analytics (Plausible - leave empty to disable)
|
|
123
|
+
VITE_PLAUSIBLE_DOMAIN=""
|
|
124
|
+
VITE_PLAUSIBLE_SCRIPT_URL=""
|
|
125
|
+
|
|
126
|
+
# Observability
|
|
127
|
+
GRAFANA_ADMIN_USER="admin"
|
|
128
|
+
GRAFANA_ADMIN_PASSWORD="admin"
|
|
129
|
+
|
|
130
|
+
# Ports
|
|
131
|
+
DEV_PORT_OFFSET="0"
|
|
132
|
+
|
|
133
|
+
# Environment
|
|
134
|
+
NODE_ENV="development"
|
|
135
|
+
PORT="3000"
|
|
136
|
+
VITE_ENVIRONMENT="development"
|
|
137
|
+
VITE_APP_VERSION="0.1.0"
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
function writeEnv(filename) {
|
|
141
|
+
if (existsSync(filename) && !force) {
|
|
142
|
+
console.log(` SKIP: ${filename} (exists, use --force to overwrite)`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
writeFileSync(filename, envContent, { mode: 0o600 });
|
|
146
|
+
console.log(` ${force && existsSync(filename) ? 'OVERWRITTEN' : 'CREATED'}: ${filename}`);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(' Generating dev environment files...');
|
|
152
|
+
console.log('');
|
|
153
|
+
|
|
154
|
+
const wrote1 = writeEnv('.env');
|
|
155
|
+
const wrote2 = writeEnv('.env.local');
|
|
156
|
+
|
|
157
|
+
// Generate super-admin SQL with real credentials from the dev template.
|
|
158
|
+
// The template has ADMIN_EMAIL and ADMIN_PASSWORD_HASH placeholders that
|
|
159
|
+
// `vibecarbon create` replaces for real projects. For local CLI development we
|
|
160
|
+
// generate a separate file so the template stays clean (tracked by git), plus a
|
|
161
|
+
// docker-compose override that mounts the generated file into the db container.
|
|
162
|
+
// The hash is pre-computed for the fixed dev password — no bcrypt dependency needed.
|
|
163
|
+
const ADMIN_PASSWORD_HASH = '$2b$10$OWzZo/eSlFAb2S9oX4Ps5.prj7LzVfAWo7N1cFrL3N8Z3HLUZtvOm';
|
|
164
|
+
const sqlTemplatePath = 'volumes/db/super-admin.dev.sql';
|
|
165
|
+
const sqlGeneratedPath = 'volumes/db/super-admin.generated.sql';
|
|
166
|
+
if (existsSync(sqlTemplatePath)) {
|
|
167
|
+
const sql = readFileSync(sqlTemplatePath, 'utf-8')
|
|
168
|
+
.replace(/\{\{ADMIN_EMAIL\}\}/g, DEV.ADMIN_EMAIL)
|
|
169
|
+
.replace(/\{\{ADMIN_PASSWORD_HASH\}\}/g, ADMIN_PASSWORD_HASH);
|
|
170
|
+
writeFileSync(sqlGeneratedPath, sql);
|
|
171
|
+
console.log(` CREATED: ${sqlGeneratedPath} (dev admin credentials)`);
|
|
172
|
+
|
|
173
|
+
// Write a compose override that mounts the generated SQL instead of the template
|
|
174
|
+
const composeOverride = `# Generated by dev-init.js — DO NOT COMMIT
|
|
175
|
+
services:
|
|
176
|
+
db:
|
|
177
|
+
volumes:
|
|
178
|
+
- ./volumes/db/super-admin.generated.sql:/docker-entrypoint-initdb.d/post-init/super-admin.sql:Z
|
|
179
|
+
`;
|
|
180
|
+
writeFileSync('docker-compose.dev-init.yml', composeOverride);
|
|
181
|
+
console.log(' CREATED: docker-compose.dev-init.yml (mounts generated admin SQL)');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log('');
|
|
185
|
+
if (wrote1 || wrote2) {
|
|
186
|
+
console.log(` Admin login: ${DEV.ADMIN_EMAIL} / ${DEV.ADMIN_PASSWORD}`);
|
|
187
|
+
console.log(' Run `pnpm dev:start` to start all services.');
|
|
188
|
+
} else {
|
|
189
|
+
console.log(' No files written. Use --force to overwrite existing files.');
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Development server script with proper signal handling.
|
|
4
|
+
* Spawns both the API server (tsx watch) and Vite dev server,
|
|
5
|
+
* and ensures clean shutdown on Ctrl+C.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { createServer } from 'node:net';
|
|
11
|
+
|
|
12
|
+
const isWindows = process.platform === 'win32';
|
|
13
|
+
|
|
14
|
+
// Read project name from package.json
|
|
15
|
+
let projectName = 'your app';
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
|
|
18
|
+
projectName = pkg.name || projectName;
|
|
19
|
+
} catch {
|
|
20
|
+
// Ignore errors, use default
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Read port configuration from .env.local
|
|
24
|
+
function getPortConfig() {
|
|
25
|
+
const defaults = { vite: 5173, api: 3000 };
|
|
26
|
+
try {
|
|
27
|
+
const envFiles = ['.env.local', '.env'];
|
|
28
|
+
for (const file of envFiles) {
|
|
29
|
+
if (existsSync(file)) {
|
|
30
|
+
const content = readFileSync(file, 'utf-8');
|
|
31
|
+
const getEnvValue = (key) => {
|
|
32
|
+
const match = content.match(new RegExp(`^${key}=["']?([^"'\\n]+)["']?`, 'm'));
|
|
33
|
+
return match ? match[1] : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const portOffset = Number.parseInt(getEnvValue('DEV_PORT_OFFSET') || '0', 10);
|
|
37
|
+
const vitePort = getEnvValue('DEV_VITE_PORT') || String(5173 + portOffset);
|
|
38
|
+
const apiPort = getEnvValue('DEV_API_PORT') || String(3000 + portOffset);
|
|
39
|
+
|
|
40
|
+
return { vite: Number.parseInt(vitePort, 10), api: Number.parseInt(apiPort, 10) };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Fall through to defaults
|
|
45
|
+
}
|
|
46
|
+
return defaults;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Resolves to true if no listener can grab the port on 0.0.0.0. */
|
|
50
|
+
function isPortFree(port) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const probe = createServer();
|
|
53
|
+
probe.once('error', () => resolve(false));
|
|
54
|
+
probe.once('listening', () => probe.close(() => resolve(true)));
|
|
55
|
+
probe.listen(port, '0.0.0.0');
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Best-effort `command/pid` lookup for whatever owns a busy port. */
|
|
60
|
+
function describePortHolder(port) {
|
|
61
|
+
if (isWindows) return null;
|
|
62
|
+
// execFileSync (no shell) — port is already numeric, but using execFile
|
|
63
|
+
// keeps us aligned with the codebase's no-shell convention.
|
|
64
|
+
if (!Number.isInteger(port)) return null;
|
|
65
|
+
try {
|
|
66
|
+
const out = execFileSync('lsof', [`-iTCP:${port}`, '-sTCP:LISTEN', '-P', '-n'], {
|
|
67
|
+
encoding: 'utf-8',
|
|
68
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
69
|
+
});
|
|
70
|
+
const dataLines = out.trim().split('\n').slice(1);
|
|
71
|
+
if (dataLines.length === 0) return null;
|
|
72
|
+
const [command, pid] = dataLines[0].split(/\s+/);
|
|
73
|
+
return { command, pid };
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ports = getPortConfig();
|
|
80
|
+
|
|
81
|
+
// ANSI colors
|
|
82
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
83
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
84
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
85
|
+
|
|
86
|
+
// Pre-flight: fail fast if either port is already in use. Without this,
|
|
87
|
+
// `tsx watch` swallows the EADDRINUSE from the API server, Vite keeps
|
|
88
|
+
// serving on its port, and `/api/*` requests return 502 with empty bodies —
|
|
89
|
+
// surfacing in the UI as "Unexpected end of JSON input".
|
|
90
|
+
const portChecks = await Promise.all(
|
|
91
|
+
[
|
|
92
|
+
{ name: 'API', port: ports.api },
|
|
93
|
+
{ name: 'Vite', port: ports.vite },
|
|
94
|
+
].map(async (p) => ({ ...p, free: await isPortFree(p.port) })),
|
|
95
|
+
);
|
|
96
|
+
const conflicts = portChecks.filter((c) => !c.free);
|
|
97
|
+
if (conflicts.length > 0) {
|
|
98
|
+
console.error(`\n ${yellow('✗')} Cannot start dev servers — port conflict:\n`);
|
|
99
|
+
for (const { name, port } of conflicts) {
|
|
100
|
+
const holder = describePortHolder(port);
|
|
101
|
+
const who = holder ? ` (held by ${holder.command} PID ${holder.pid})` : '';
|
|
102
|
+
console.error(` ${name} port ${bold(String(port))} is in use${who}`);
|
|
103
|
+
}
|
|
104
|
+
console.error(
|
|
105
|
+
`\n An orphaned dev session is likely still running. To recover:\n` +
|
|
106
|
+
` ${cyan('pkill -f "scripts/dev.js"')} ${cyan('# kill the previous dev-server tree')}\n` +
|
|
107
|
+
` ${cyan('pnpm dev:start')} ${cyan('# restart')}\n`,
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`
|
|
113
|
+
${cyan('✦')} Starting ${projectName} services...
|
|
114
|
+
|
|
115
|
+
${cyan('→')} ${bold('Frontend:')} ${cyan(`http://localhost:${ports.vite}`)}
|
|
116
|
+
${cyan('→')} ${bold('API:')} ${cyan(`http://localhost:${ports.api}`)}
|
|
117
|
+
${cyan('→')} ${bold('Studio:')} ${cyan('http://studio.localhost')} (requires login)
|
|
118
|
+
${cyan('→')} ${bold('Traefik:')} ${cyan('http://traefik.localhost')} (requires login)
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
const children = [];
|
|
122
|
+
|
|
123
|
+
// Spawn server process with its own process group for clean shutdown
|
|
124
|
+
const server = spawn('npx', ['tsx', 'watch', '--env-file=.env.local', 'src/server/index.ts'], {
|
|
125
|
+
stdio: 'inherit',
|
|
126
|
+
shell: isWindows,
|
|
127
|
+
detached: !isWindows, // Create process group on Unix for clean group kill
|
|
128
|
+
});
|
|
129
|
+
children.push(server);
|
|
130
|
+
|
|
131
|
+
// Spawn client process
|
|
132
|
+
const client = spawn('npx', ['vite'], {
|
|
133
|
+
stdio: 'inherit',
|
|
134
|
+
shell: isWindows,
|
|
135
|
+
detached: !isWindows,
|
|
136
|
+
});
|
|
137
|
+
children.push(client);
|
|
138
|
+
|
|
139
|
+
// Track if we're shutting down
|
|
140
|
+
let shuttingDown = false;
|
|
141
|
+
|
|
142
|
+
function shutdown(exitCode = 0) {
|
|
143
|
+
if (shuttingDown) return;
|
|
144
|
+
shuttingDown = true;
|
|
145
|
+
|
|
146
|
+
console.log('\n Shutting down...');
|
|
147
|
+
|
|
148
|
+
// Kill all child process groups
|
|
149
|
+
for (const child of children) {
|
|
150
|
+
if (child.exitCode === null) {
|
|
151
|
+
if (isWindows) {
|
|
152
|
+
spawn('taskkill', ['/pid', child.pid, '/f', '/t']);
|
|
153
|
+
} else {
|
|
154
|
+
// Kill the entire process group (negative PID)
|
|
155
|
+
try {
|
|
156
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
157
|
+
} catch {
|
|
158
|
+
// Fallback: kill just the process
|
|
159
|
+
try {
|
|
160
|
+
child.kill('SIGTERM');
|
|
161
|
+
} catch {
|
|
162
|
+
// Process already dead
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Exit immediately - don't wait
|
|
170
|
+
process.exit(exitCode);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Handle signals
|
|
174
|
+
process.on('SIGINT', () => shutdown(0));
|
|
175
|
+
process.on('SIGTERM', () => shutdown(0));
|
|
176
|
+
|
|
177
|
+
// If either child exits while the session is meant to be running, take the
|
|
178
|
+
// rest down with it. Otherwise an API crash leaves Vite happily serving a
|
|
179
|
+
// half-dead app where /api/* returns 502s.
|
|
180
|
+
for (const child of children) {
|
|
181
|
+
child.on('exit', (code, signal) => {
|
|
182
|
+
if (shuttingDown) return;
|
|
183
|
+
const failed = code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGINT';
|
|
184
|
+
if (failed) {
|
|
185
|
+
console.error(
|
|
186
|
+
`\n ${yellow('✗')} Dev process exited unexpectedly (code=${code}, signal=${signal}). Stopping remaining processes.`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
shutdown(failed ? (code ?? 1) : 0);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop Docker Compose services based on enabled features
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/docker-down.js # Stop services
|
|
7
|
+
* node scripts/docker-down.js -v # Stop and remove volumes
|
|
8
|
+
* node scripts/docker-down.js --dry-run # Show command without running
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { buildComposeCommand, loadManifest, parseArgs } from './lib/manifest.js';
|
|
13
|
+
|
|
14
|
+
const rawArgs = process.argv.slice(2);
|
|
15
|
+
const args = parseArgs(rawArgs);
|
|
16
|
+
|
|
17
|
+
// Check for -v flag (volumes) - different from --verbose
|
|
18
|
+
const removeVolumes = rawArgs.includes('-v') && !rawArgs.includes('--verbose');
|
|
19
|
+
const removeImages = rawArgs.includes('--rmi');
|
|
20
|
+
|
|
21
|
+
if (args.help) {
|
|
22
|
+
console.log(`
|
|
23
|
+
Usage: node scripts/docker-down.js [options]
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--all Include all optional services (ignores .vibecarbon.json)
|
|
27
|
+
-v Remove volumes (docker compose down -v)
|
|
28
|
+
--prod Include production overlay
|
|
29
|
+
--rmi Remove locally-built images (docker compose down --rmi local)
|
|
30
|
+
--dry-run Print the command without executing it
|
|
31
|
+
--verbose Verbose output
|
|
32
|
+
-h, --help Show this help message
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
node scripts/docker-down.js # Stop services from .vibecarbon.json
|
|
36
|
+
node scripts/docker-down.js --all # Stop ALL services
|
|
37
|
+
node scripts/docker-down.js -v # Stop and remove volumes
|
|
38
|
+
node scripts/docker-down.js -v --rmi # Stop, remove volumes and built images
|
|
39
|
+
pnpm docker:down # Via package.json script
|
|
40
|
+
`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const manifest = loadManifest();
|
|
45
|
+
let action = 'down';
|
|
46
|
+
if (removeVolumes) action += ' -v';
|
|
47
|
+
if (removeImages) action += ' --rmi local';
|
|
48
|
+
const cmd = buildComposeCommand(action, manifest, { prod: args.prod, all: args.all });
|
|
49
|
+
|
|
50
|
+
if (args.dryRun) {
|
|
51
|
+
console.log('Would run:', cmd);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (args.verbose) {
|
|
56
|
+
console.log('Running:', cmd);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
process.exit(error.status || 1);
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* View Docker Compose logs for enabled services
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/docker-logs.js # Follow all logs
|
|
7
|
+
* node scripts/docker-logs.js app # Follow logs for specific service
|
|
8
|
+
* node scripts/docker-logs.js --dry-run # Show command without running
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { buildComposeCommand, loadManifest, parseArgs } from './lib/manifest.js';
|
|
13
|
+
|
|
14
|
+
const args = parseArgs(process.argv.slice(2));
|
|
15
|
+
|
|
16
|
+
if (args.help) {
|
|
17
|
+
console.log(`
|
|
18
|
+
Usage: node scripts/docker-logs.js [service] [options]
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
service Optional service name to filter logs
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--prod Include production overlay
|
|
25
|
+
--dry-run Print the command without executing it
|
|
26
|
+
--verbose Verbose output
|
|
27
|
+
-h, --help Show this help message
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
node scripts/docker-logs.js # Follow all logs
|
|
31
|
+
node scripts/docker-logs.js app # Follow app service logs
|
|
32
|
+
node scripts/docker-logs.js db # Follow database logs
|
|
33
|
+
pnpm docker:logs # Via package.json script
|
|
34
|
+
`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const manifest = loadManifest();
|
|
39
|
+
const service = args.positional[0] || '';
|
|
40
|
+
const action = `logs -f ${service}`.trim();
|
|
41
|
+
const cmd = buildComposeCommand(action, manifest, { prod: args.prod });
|
|
42
|
+
|
|
43
|
+
if (args.dryRun) {
|
|
44
|
+
console.log('Would run:', cmd);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (args.verbose) {
|
|
49
|
+
console.log('Running:', cmd);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Logs can be interrupted with Ctrl+C, which is expected
|
|
56
|
+
if (error.status !== 130) {
|
|
57
|
+
process.exit(error.status || 1);
|
|
58
|
+
}
|
|
59
|
+
}
|