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,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Start Docker Compose services based on enabled features
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/docker-up.js # Start dev services
|
|
7
|
+
* node scripts/docker-up.js --prod # Start production services
|
|
8
|
+
* node scripts/docker-up.js --dry-run # Show command without running
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import {
|
|
14
|
+
buildComposeCommand,
|
|
15
|
+
discoverOptionalServices,
|
|
16
|
+
getEnabledServices,
|
|
17
|
+
loadManifest,
|
|
18
|
+
parseArgs,
|
|
19
|
+
} from './lib/manifest.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Read environment value from .env files
|
|
23
|
+
*/
|
|
24
|
+
function getEnvValue(key) {
|
|
25
|
+
const envFiles = ['.env.local', '.env'];
|
|
26
|
+
for (const file of envFiles) {
|
|
27
|
+
if (existsSync(file)) {
|
|
28
|
+
const content = readFileSync(file, 'utf-8');
|
|
29
|
+
const match = content.match(new RegExp(`^${key}=["']?([^"'\\n]+)["']?`, 'm'));
|
|
30
|
+
if (match) return match[1];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Calculate port with offset, respecting explicit overrides
|
|
38
|
+
*/
|
|
39
|
+
function getPort(envKey, defaultPort, offset) {
|
|
40
|
+
const explicit = getEnvValue(envKey);
|
|
41
|
+
if (explicit) return explicit;
|
|
42
|
+
return String(defaultPort + offset);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build environment variables for port configuration
|
|
47
|
+
* Applies DEV_PORT_OFFSET to all configurable ports
|
|
48
|
+
*/
|
|
49
|
+
function getPortEnvVars() {
|
|
50
|
+
const portOffset = Number.parseInt(getEnvValue('DEV_PORT_OFFSET') || '0', 10);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
DEV_DB_PORT: getPort('DEV_DB_PORT', 5432, portOffset),
|
|
54
|
+
DEV_KONG_PORT: getPort('DEV_KONG_PORT', 8000, portOffset),
|
|
55
|
+
DEV_KONG_SSL_PORT: getPort('DEV_KONG_SSL_PORT', 8443, portOffset),
|
|
56
|
+
DEV_TRAEFIK_PORT: getPort('DEV_TRAEFIK_PORT', 80, portOffset),
|
|
57
|
+
DEV_PROMETHEUS_PORT: getPort('DEV_PROMETHEUS_PORT', 9190, portOffset),
|
|
58
|
+
DEV_GRAFANA_PORT: getPort('DEV_GRAFANA_PORT', 3002, portOffset),
|
|
59
|
+
DEV_LOKI_PORT: getPort('DEV_LOKI_PORT', 3100, portOffset),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run DB init scripts for optional services.
|
|
65
|
+
* These scripts are idempotent (IF NOT EXISTS, ALTER ROLE) so they're safe
|
|
66
|
+
* to run on every startup. This handles the case where a service is added
|
|
67
|
+
* after the DB volume already exists — Postgres only runs entrypoint scripts
|
|
68
|
+
* on first volume initialization.
|
|
69
|
+
*/
|
|
70
|
+
function runServiceDbInit(manifest, { all, verbose }) {
|
|
71
|
+
let serviceNames;
|
|
72
|
+
if (all) {
|
|
73
|
+
// Extract service names from compose file names: docker-compose.metabase.yml → metabase
|
|
74
|
+
serviceNames = discoverOptionalServices().map((f) => f.replace('docker-compose.', '').replace('.yml', ''));
|
|
75
|
+
} else {
|
|
76
|
+
serviceNames = getEnabledServices(manifest);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const scripts = serviceNames
|
|
80
|
+
.map((name) => ({ name, path: `volumes/db/${name}-init.sh` }))
|
|
81
|
+
.filter(({ path }) => existsSync(path));
|
|
82
|
+
|
|
83
|
+
if (scripts.length === 0) return;
|
|
84
|
+
|
|
85
|
+
if (verbose) console.log('Phase 1.5 — running DB init scripts for services:', scripts.map((s) => s.name).join(', '));
|
|
86
|
+
|
|
87
|
+
for (const { name, path } of scripts) {
|
|
88
|
+
if (verbose) console.log(` Running DB init for ${name}...`);
|
|
89
|
+
try {
|
|
90
|
+
execSync(`docker compose -f docker-compose.yml exec -T db bash < ${path}`, {
|
|
91
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
92
|
+
env: { ...process.env, ...getPortEnvVars() },
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn(`Warning: DB init for ${name} failed (${error.message}). The service may not connect.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const args = parseArgs(process.argv.slice(2));
|
|
101
|
+
|
|
102
|
+
if (args.help) {
|
|
103
|
+
console.log(`
|
|
104
|
+
Usage: node scripts/docker-up.js [options]
|
|
105
|
+
|
|
106
|
+
Options:
|
|
107
|
+
--all Include all optional services (ignores .vibecarbon.json)
|
|
108
|
+
--prod Include production overlay (docker-compose.prod.yml)
|
|
109
|
+
--dry-run Print the command without executing it
|
|
110
|
+
-v Verbose output
|
|
111
|
+
-h, --help Show this help message
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
node scripts/docker-up.js # Start services from .vibecarbon.json
|
|
115
|
+
node scripts/docker-up.js --all # Start ALL optional services
|
|
116
|
+
node scripts/docker-up.js --prod # Start with production config
|
|
117
|
+
pnpm docker:up # Via package.json script
|
|
118
|
+
pnpm docker:up -- --all # Start all services via pnpm
|
|
119
|
+
`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const manifest = loadManifest();
|
|
124
|
+
|
|
125
|
+
// Get port configuration with offset applied
|
|
126
|
+
const portEnv = getPortEnvVars();
|
|
127
|
+
const execOpts = { stdio: 'inherit', env: { ...process.env, ...portEnv } };
|
|
128
|
+
|
|
129
|
+
// Check if any optional services (metabase, n8n, observability, etc.) are enabled.
|
|
130
|
+
// These can have slow health checks (e.g. Metabase JVM takes 2-4 min), so we start
|
|
131
|
+
// core Supabase services first with --wait, then launch optional services in the
|
|
132
|
+
// background without blocking.
|
|
133
|
+
const hasOptionalServices = args.all || getEnabledServices(manifest).length > 0;
|
|
134
|
+
|
|
135
|
+
if (hasOptionalServices) {
|
|
136
|
+
const coreCmd = buildComposeCommand('up -d --wait', { services: {} }, { prod: args.prod });
|
|
137
|
+
const fullCmd = buildComposeCommand('up -d', manifest, { prod: args.prod, all: args.all });
|
|
138
|
+
|
|
139
|
+
if (args.dryRun) {
|
|
140
|
+
console.log('Phase 1 (core, blocking):', coreCmd);
|
|
141
|
+
console.log('Phase 1.5 (service DB init): run idempotent init scripts via exec -T db bash');
|
|
142
|
+
console.log('Phase 2 (optional, background):', fullCmd);
|
|
143
|
+
console.log('With port env:', portEnv);
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Phase 1: Start core services and wait until healthy (~17s)
|
|
148
|
+
if (args.verbose) console.log('Phase 1 — core services:', coreCmd);
|
|
149
|
+
try {
|
|
150
|
+
execSync(coreCmd, execOpts);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
diagnoseFailure();
|
|
153
|
+
process.exit(error.status || 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Phase 1.5: Run idempotent DB init scripts for services that need their own database/user.
|
|
157
|
+
// Postgres skips /docker-entrypoint-initdb.d/ when the data volume already exists,
|
|
158
|
+
// so we pipe the scripts into the running container to ensure roles/databases exist.
|
|
159
|
+
runServiceDbInit(manifest, { all: args.all, verbose: args.verbose });
|
|
160
|
+
|
|
161
|
+
// Phase 2: Start optional services in background (no --wait)
|
|
162
|
+
// Core services are already running so Docker skips them (idempotent up -d)
|
|
163
|
+
if (args.verbose) console.log('Phase 2 — optional services:', fullCmd);
|
|
164
|
+
console.log('Starting optional services in background...');
|
|
165
|
+
try {
|
|
166
|
+
execSync(fullCmd, execOpts);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// Non-fatal: optional services failing to launch shouldn't block dev
|
|
169
|
+
console.warn('Warning: some optional services may have failed to start.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
} else {
|
|
173
|
+
const cmd = buildComposeCommand('up -d --wait', manifest, { prod: args.prod, all: args.all });
|
|
174
|
+
|
|
175
|
+
if (args.dryRun) {
|
|
176
|
+
console.log('Would run:', cmd);
|
|
177
|
+
console.log('With port env:', portEnv);
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (args.verbose) console.log('Running:', cmd);
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
execSync(cmd, execOpts);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
diagnoseFailure();
|
|
187
|
+
process.exit(error.status || 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check unhealthy containers for common issues and print helpful messages.
|
|
193
|
+
*/
|
|
194
|
+
function diagnoseFailure() {
|
|
195
|
+
try {
|
|
196
|
+
const logs = execSync('docker compose logs --tail 20 auth rest storage 2>&1', {
|
|
197
|
+
encoding: 'utf-8',
|
|
198
|
+
env: { ...process.env, ...getPortEnvVars() },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (logs.includes('password authentication failed')) {
|
|
202
|
+
console.error(`
|
|
203
|
+
\x1b[33m┌──────────────────────────────────────────────────────────┐
|
|
204
|
+
│ Database credentials mismatch detected │
|
|
205
|
+
│ │
|
|
206
|
+
│ The database volume was initialized with different │
|
|
207
|
+
│ credentials than your current .env.local. │
|
|
208
|
+
│ │
|
|
209
|
+
│ This happens when you recreate a project without │
|
|
210
|
+
│ removing the old Docker volumes. │
|
|
211
|
+
│ │
|
|
212
|
+
│ Fix: vibecarbon reset │
|
|
213
|
+
│ │
|
|
214
|
+
│ This will remove volumes and reinitialize the database. │
|
|
215
|
+
│ ⚠ All local database data will be lost. │
|
|
216
|
+
└──────────────────────────────────────────────────────────┘\x1b[0m
|
|
217
|
+
`);
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// Diagnostic check failed, don't block the error exit
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Generate dev config files from templates
|
|
4
|
+
# Replaces {{PLACEHOLDERS}} with hardcoded dev values
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./generate-dev-configs.sh # Only generate missing files
|
|
8
|
+
# ./generate-dev-configs.sh --force # Regenerate all files
|
|
9
|
+
#
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
CARBON_DIR="$(dirname "$SCRIPT_DIR")"
|
|
14
|
+
|
|
15
|
+
FORCE=false
|
|
16
|
+
if [[ "${1:-}" == "--force" ]]; then
|
|
17
|
+
FORCE=true
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
cd "$CARBON_DIR"
|
|
21
|
+
|
|
22
|
+
# Dev value substitutions (add new placeholders here).
|
|
23
|
+
# Note: ANON_KEY and SERVICE_ROLE_KEY are signed at runtime by
|
|
24
|
+
# scripts/_dev-jwt.mjs against JWT_SECRET. Committing literal JWT
|
|
25
|
+
# strings tripped GitHub's secret-scanner on every push; signing on
|
|
26
|
+
# the fly keeps the dev workflow deterministic without leaking
|
|
27
|
+
# service-role-shaped values into git history.
|
|
28
|
+
JWT_SECRET_VALUE="local-dev-jwt-secret-must-be-at-least-32-chars-padded-padded"
|
|
29
|
+
ANON_KEY_VALUE=$(JWT_SECRET="$JWT_SECRET_VALUE" node "$SCRIPT_DIR/_dev-jwt.mjs" anon)
|
|
30
|
+
SERVICE_ROLE_KEY_VALUE=$(JWT_SECRET="$JWT_SECRET_VALUE" node "$SCRIPT_DIR/_dev-jwt.mjs" service_role)
|
|
31
|
+
|
|
32
|
+
declare -A SUBSTITUTIONS=(
|
|
33
|
+
["{{PROJECT_NAME}}"]="vibecarbon"
|
|
34
|
+
["{{ANON_KEY}}"]="$ANON_KEY_VALUE"
|
|
35
|
+
["{{SERVICE_ROLE_KEY}}"]="$SERVICE_ROLE_KEY_VALUE"
|
|
36
|
+
["{{JWT_SECRET}}"]="$JWT_SECRET_VALUE"
|
|
37
|
+
["{{DB_PASSWORD}}"]="local-postgres-password-for-dev-only"
|
|
38
|
+
["{{ADMIN_EMAIL}}"]="admin@localhost"
|
|
39
|
+
["{{ADMIN_PASSWORD}}"]="local-admin-password"
|
|
40
|
+
["{{GRAFANA_PASSWORD}}"]="admin"
|
|
41
|
+
["{{N8N_PASSWORD}}"]="local-n8n-password"
|
|
42
|
+
["{{REALTIME_SECRET}}"]="local-realtime-secret-padded"
|
|
43
|
+
["{{VAULT_ENC_KEY}}"]="local-vault-encryption-key-1234567890"
|
|
44
|
+
["{{DB_ENC_KEY}}"]="devdbencryptkey1"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Files that need dev variants (relative to carbon/)
|
|
48
|
+
# Format: "template_path:dev_path"
|
|
49
|
+
declare -a CONFIG_PAIRS=(
|
|
50
|
+
# Grafana dashboards
|
|
51
|
+
"volumes/grafana/provisioning/dashboards/dashboards.yml:volumes/grafana/provisioning/dashboards/dashboards.dev.yml"
|
|
52
|
+
# Database initialization (only super-admin needs dev variant — roles/services use runtime env vars)
|
|
53
|
+
"volumes/db/super-admin.sql:volumes/db/super-admin.dev.sql"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Directories where all files need dev variants
|
|
57
|
+
declare -a CONFIG_DIRS=(
|
|
58
|
+
"volumes/grafana/dashboards:volumes/grafana/dashboards.dev"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
generate_dev_file() {
|
|
62
|
+
local src="$1"
|
|
63
|
+
local dest="$2"
|
|
64
|
+
|
|
65
|
+
if [[ ! -f "$src" ]]; then
|
|
66
|
+
echo " SKIP: $src (template not found)"
|
|
67
|
+
return
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [[ -f "$dest" && "$FORCE" == "false" ]]; then
|
|
71
|
+
echo " SKIP: $dest (exists, use --force to regenerate)"
|
|
72
|
+
return
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Start with source content
|
|
76
|
+
local content
|
|
77
|
+
content=$(<"$src")
|
|
78
|
+
|
|
79
|
+
# Apply all substitutions
|
|
80
|
+
for placeholder in "${!SUBSTITUTIONS[@]}"; do
|
|
81
|
+
local value="${SUBSTITUTIONS[$placeholder]}"
|
|
82
|
+
content="${content//${placeholder}/${value}}"
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
# Write dev file
|
|
86
|
+
mkdir -p "$(dirname "$dest")"
|
|
87
|
+
echo "$content" > "$dest"
|
|
88
|
+
|
|
89
|
+
if [[ "$FORCE" == "true" ]]; then
|
|
90
|
+
echo " REGENERATED: $dest"
|
|
91
|
+
else
|
|
92
|
+
echo " CREATED: $dest"
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
echo "Generating dev config files..."
|
|
97
|
+
echo ""
|
|
98
|
+
|
|
99
|
+
# Process individual file pairs
|
|
100
|
+
echo "Config files:"
|
|
101
|
+
for pair in "${CONFIG_PAIRS[@]}"; do
|
|
102
|
+
src="${pair%%:*}"
|
|
103
|
+
dest="${pair##*:}"
|
|
104
|
+
generate_dev_file "$src" "$dest"
|
|
105
|
+
done
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
|
|
109
|
+
# Process directories
|
|
110
|
+
echo "Config directories:"
|
|
111
|
+
for dir_pair in "${CONFIG_DIRS[@]}"; do
|
|
112
|
+
src_dir="${dir_pair%%:*}"
|
|
113
|
+
dest_dir="${dir_pair##*:}"
|
|
114
|
+
|
|
115
|
+
if [[ ! -d "$src_dir" ]]; then
|
|
116
|
+
echo " SKIP: $src_dir (not found)"
|
|
117
|
+
continue
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
mkdir -p "$dest_dir"
|
|
121
|
+
|
|
122
|
+
for src_file in "$src_dir"/*; do
|
|
123
|
+
if [[ -f "$src_file" ]]; then
|
|
124
|
+
filename=$(basename "$src_file")
|
|
125
|
+
generate_dev_file "$src_file" "$dest_dir/$filename"
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
echo "Done. Remember to verify the generated files before committing."
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time RSS feed generator.
|
|
3
|
+
*
|
|
4
|
+
* Run: npx tsx scripts/generate-rss.ts
|
|
5
|
+
*
|
|
6
|
+
* Reads blog post frontmatter from content/blog/*.mdx and generates an RSS feed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
10
|
+
import { dirname, resolve } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
interface PostMeta {
|
|
16
|
+
slug: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
date: string;
|
|
20
|
+
author?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function loadSiteUrl(): string {
|
|
24
|
+
try {
|
|
25
|
+
const envContent = readFileSync(resolve(__dirname, '../.env.local'), 'utf-8');
|
|
26
|
+
const match = envContent.match(/^SITE_URL=["']?(.+?)["']?\s*$/m);
|
|
27
|
+
if (match) return match[1];
|
|
28
|
+
} catch {
|
|
29
|
+
// .env.local may not exist in CI
|
|
30
|
+
}
|
|
31
|
+
return process.env.SITE_URL || 'http://localhost:5173';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadProjectName(): string {
|
|
35
|
+
try {
|
|
36
|
+
const pkgContent = readFileSync(resolve(__dirname, '../package.json'), 'utf-8');
|
|
37
|
+
const pkg = JSON.parse(pkgContent);
|
|
38
|
+
return pkg.name || 'My SaaS';
|
|
39
|
+
} catch {
|
|
40
|
+
return 'My SaaS';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
45
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
46
|
+
if (!match) return {};
|
|
47
|
+
const fm: Record<string, string> = {};
|
|
48
|
+
for (const line of match[1].split('\n')) {
|
|
49
|
+
const [key, ...rest] = line.split(':');
|
|
50
|
+
if (key && rest.length) {
|
|
51
|
+
fm[key.trim()] = rest.join(':').trim().replace(/^["']|["']$/g, '');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return fm;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadPosts(): PostMeta[] {
|
|
58
|
+
const blogDir = resolve(__dirname, '../content/blog');
|
|
59
|
+
const files = readdirSync(blogDir).filter((f) => f.endsWith('.mdx'));
|
|
60
|
+
|
|
61
|
+
return files
|
|
62
|
+
.map((file) => {
|
|
63
|
+
const content = readFileSync(resolve(blogDir, file), 'utf-8');
|
|
64
|
+
const fm = parseFrontmatter(content);
|
|
65
|
+
return {
|
|
66
|
+
slug: file.replace('.mdx', ''),
|
|
67
|
+
title: fm.title || file.replace('.mdx', ''),
|
|
68
|
+
description: fm.description || '',
|
|
69
|
+
date: fm.date || new Date().toISOString(),
|
|
70
|
+
author: fm.author,
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function generateRss(posts: PostMeta[], siteUrl: string, projectName: string): string {
|
|
77
|
+
const items = posts
|
|
78
|
+
.map(
|
|
79
|
+
(post) => ` <item>
|
|
80
|
+
<title>${escapeXml(post.title)}</title>
|
|
81
|
+
<link>${siteUrl}/blog/${post.slug}</link>
|
|
82
|
+
<description>${escapeXml(post.description)}</description>
|
|
83
|
+
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
|
|
84
|
+
<guid>${siteUrl}/blog/${post.slug}</guid>
|
|
85
|
+
</item>`,
|
|
86
|
+
)
|
|
87
|
+
.join('\n');
|
|
88
|
+
|
|
89
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
90
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
91
|
+
<channel>
|
|
92
|
+
<title>${escapeXml(projectName)} Blog</title>
|
|
93
|
+
<link>${siteUrl}/blog</link>
|
|
94
|
+
<description>Updates, guides, and insights from ${escapeXml(projectName)}.</description>
|
|
95
|
+
<language>en-us</language>
|
|
96
|
+
<atom:link href="${siteUrl}/rss.xml" rel="self" type="application/rss+xml"/>
|
|
97
|
+
${items}
|
|
98
|
+
</channel>
|
|
99
|
+
</rss>
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function escapeXml(str: string): string {
|
|
104
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const siteUrl = loadSiteUrl();
|
|
108
|
+
const projectName = loadProjectName();
|
|
109
|
+
const posts = loadPosts();
|
|
110
|
+
const rss = generateRss(posts, siteUrl, projectName);
|
|
111
|
+
|
|
112
|
+
const outDir = resolve(__dirname, '../dist/client');
|
|
113
|
+
mkdirSync(outDir, { recursive: true });
|
|
114
|
+
writeFileSync(resolve(outDir, 'rss.xml'), rss);
|
|
115
|
+
|
|
116
|
+
console.log(`RSS feed generated with ${posts.length} posts → dist/client/rss.xml`);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time sitemap generator.
|
|
3
|
+
*
|
|
4
|
+
* Run: npx tsx scripts/generate-sitemap.ts
|
|
5
|
+
*
|
|
6
|
+
* Reads SITE_URL from .env.local and outputs sitemap.xml to dist/client/.
|
|
7
|
+
* Includes static routes and dynamic blog post routes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { dirname, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
// Public routes that should be indexed
|
|
17
|
+
const PUBLIC_ROUTES = ['/', '/login', '/signup', '/blog', '/changelog', '/docs'];
|
|
18
|
+
|
|
19
|
+
function loadSiteUrl(): string {
|
|
20
|
+
try {
|
|
21
|
+
const envContent = readFileSync(resolve(__dirname, '../.env.local'), 'utf-8');
|
|
22
|
+
const match = envContent.match(/^SITE_URL=["']?(.+?)["']?\s*$/m);
|
|
23
|
+
if (match) return match[1];
|
|
24
|
+
} catch {
|
|
25
|
+
// .env.local may not exist in CI
|
|
26
|
+
}
|
|
27
|
+
return process.env.SITE_URL || 'http://localhost:5173';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getBlogSlugs(): string[] {
|
|
31
|
+
try {
|
|
32
|
+
const blogDir = resolve(__dirname, '../content/blog');
|
|
33
|
+
return readdirSync(blogDir)
|
|
34
|
+
.filter((f) => f.endsWith('.mdx'))
|
|
35
|
+
.map((f) => f.replace('.mdx', ''));
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getChangelogSlugs(): string[] {
|
|
42
|
+
try {
|
|
43
|
+
const changelogDir = resolve(__dirname, '../content/changelog');
|
|
44
|
+
return readdirSync(changelogDir)
|
|
45
|
+
.filter((f) => f.endsWith('.mdx'))
|
|
46
|
+
.map((f) => f.replace('.mdx', ''));
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getDocsSlugs(): string[] {
|
|
53
|
+
try {
|
|
54
|
+
const docsDir = resolve(__dirname, '../content/docs');
|
|
55
|
+
return readdirSync(docsDir)
|
|
56
|
+
.filter((f) => f.endsWith('.mdx'))
|
|
57
|
+
.map((f) => f.replace('.mdx', ''));
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generateSitemap(siteUrl: string): string {
|
|
64
|
+
const today = new Date().toISOString().split('T')[0];
|
|
65
|
+
const blogSlugs = getBlogSlugs();
|
|
66
|
+
const changelogSlugs = getChangelogSlugs();
|
|
67
|
+
const docsSlugs = getDocsSlugs();
|
|
68
|
+
const allRoutes = [
|
|
69
|
+
...PUBLIC_ROUTES,
|
|
70
|
+
...blogSlugs.map((s) => `/blog/${s}`),
|
|
71
|
+
...changelogSlugs.map((s) => `/changelog/${s}`),
|
|
72
|
+
...docsSlugs.map((s) => `/docs/${s}`),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const urls = allRoutes
|
|
76
|
+
.map(
|
|
77
|
+
(route) => ` <url>
|
|
78
|
+
<loc>${siteUrl}${route === '/' ? '' : route}</loc>
|
|
79
|
+
<lastmod>${today}</lastmod>
|
|
80
|
+
</url>`,
|
|
81
|
+
)
|
|
82
|
+
.join('\n');
|
|
83
|
+
|
|
84
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
85
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
86
|
+
${urls}
|
|
87
|
+
</urlset>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const siteUrl = loadSiteUrl();
|
|
92
|
+
const sitemap = generateSitemap(siteUrl);
|
|
93
|
+
|
|
94
|
+
const outDir = resolve(__dirname, '../dist/client');
|
|
95
|
+
mkdirSync(outDir, { recursive: true });
|
|
96
|
+
writeFileSync(resolve(outDir, 'sitemap.xml'), sitemap);
|
|
97
|
+
|
|
98
|
+
const blogCount = getBlogSlugs().length;
|
|
99
|
+
const changelogCount = getChangelogSlugs().length;
|
|
100
|
+
const docsCount = getDocsSlugs().length;
|
|
101
|
+
const totalUrls = PUBLIC_ROUTES.length + blogCount + changelogCount + docsCount;
|
|
102
|
+
console.log(`Sitemap generated with ${totalUrls} URLs (${blogCount} blog, ${changelogCount} changelog, ${docsCount} docs) → dist/client/sitemap.xml`);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Apply Kubernetes manifests using Kustomize
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/k8s-apply.js # Apply local overlay
|
|
7
|
+
* node scripts/k8s-apply.js production-nbg1 # Apply specific overlay
|
|
8
|
+
* node scripts/k8s-apply.js --dry-run # Show what would be applied
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { loadManifest, getEnabledFeatures, parseArgs } from './lib/manifest.js';
|
|
15
|
+
|
|
16
|
+
const args = parseArgs(process.argv.slice(2));
|
|
17
|
+
|
|
18
|
+
if (args.help) {
|
|
19
|
+
console.log(`
|
|
20
|
+
Usage: node scripts/k8s-apply.js [overlay] [options]
|
|
21
|
+
|
|
22
|
+
Arguments:
|
|
23
|
+
overlay Kustomize overlay to apply (default: local)
|
|
24
|
+
Available: local, production-nbg1, production-hel1
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--dry-run Show what would be applied without executing
|
|
28
|
+
--verbose Verbose output
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
node scripts/k8s-apply.js # Apply local overlay
|
|
33
|
+
node scripts/k8s-apply.js production-nbg1 # Apply production overlay
|
|
34
|
+
pnpm k8s:apply # Via package.json script
|
|
35
|
+
`);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const manifest = loadManifest();
|
|
40
|
+
const overlay = args.positional[0] || 'local';
|
|
41
|
+
const overlayPath = join(process.cwd(), 'k8s', 'overlays', overlay);
|
|
42
|
+
|
|
43
|
+
if (!existsSync(overlayPath)) {
|
|
44
|
+
console.error(`Error: Overlay '${overlay}' not found at ${overlayPath}`);
|
|
45
|
+
console.error('Available overlays: local, production-nbg1, production-hel1');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const features = getEnabledFeatures(manifest);
|
|
50
|
+
if (args.verbose && features.length > 0) {
|
|
51
|
+
console.log('Enabled features:', features.join(', '));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build the kubectl apply command
|
|
55
|
+
const dryRunFlag = args.dryRun ? ' --dry-run=client' : '';
|
|
56
|
+
const cmd = `kubectl apply -k ${overlayPath}${dryRunFlag}`;
|
|
57
|
+
|
|
58
|
+
if (args.dryRun) {
|
|
59
|
+
console.log('Would run:', cmd);
|
|
60
|
+
console.log('\nDry run output:');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (args.verbose) {
|
|
64
|
+
console.log('Running:', cmd);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
process.exit(error.status || 1);
|
|
71
|
+
}
|