alepha 0.21.2 → 0.23.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/README.md +0 -1
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +393 -403
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +25 -56
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +31 -1
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +313 -208
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +152 -42
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +2 -2
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +282 -285
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +39 -33
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +217 -222
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +188 -195
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/oauth/index.d.ts +71 -76
- package/dist/api/oauth/index.d.ts.map +1 -1
- package/dist/api/oauth/index.js.map +1 -1
- package/dist/api/organizations/index.browser.js.map +1 -1
- package/dist/api/organizations/index.d.ts +104 -109
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/organizations/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +43 -16
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +488 -344
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +175 -35
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +396 -402
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +644 -652
- package/dist/api/subscriptions/index.d.ts.map +1 -1
- package/dist/api/subscriptions/index.js +1 -1
- package/dist/api/subscriptions/index.js.map +1 -1
- package/dist/api/users/index.browser.js +7 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +1106 -1005
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +307 -64
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.browser.js.map +1 -1
- package/dist/api/verifications/index.d.ts +137 -143
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/background/index.d.ts +95 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +121 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/index.workerd.js +110 -0
- package/dist/background/index.workerd.js.map +1 -0
- package/dist/batch/index.d.ts +5 -7
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/bin/index.js.map +1 -1
- package/dist/bucket/index.d.ts +76 -54
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +58 -11
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +200 -5
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +7 -10
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +22 -26
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/database/index.js.map +1 -1
- package/dist/cache/redis/index.d.ts +4 -7
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/captcha/index.d.ts +3 -6
- package/dist/captcha/index.d.ts.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +458 -249
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +372 -660
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -5
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/i18n/index.d.ts +20 -17
- package/dist/cli/i18n/index.d.ts.map +1 -1
- package/dist/cli/i18n/index.js +45 -11
- package/dist/cli/i18n/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +126 -1342
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +136 -2374
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/platform-lib/index.d.ts +1472 -0
- package/dist/cli/platform-lib/index.d.ts.map +1 -0
- package/dist/cli/platform-lib/index.js +2660 -0
- package/dist/cli/platform-lib/index.js.map +1 -0
- package/dist/cli/vendor/index.d.ts +17 -21
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts +20 -19
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +39 -10
- package/dist/command/index.js.map +1 -1
- package/dist/{containers → container}/core/index.d.ts +13 -15
- package/dist/container/core/index.d.ts.map +1 -0
- package/dist/{containers → container}/core/index.js +23 -14
- package/dist/container/core/index.js.map +1 -0
- package/dist/{containers → container}/core/index.workerd.js +37 -22
- package/dist/container/core/index.workerd.js.map +1 -0
- package/dist/core/index.browser.js +27 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +48 -24
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.browser.js.map +1 -1
- package/dist/crypto/index.d.ts +5 -8
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -4
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/brevo/index.d.ts +2 -4
- package/dist/email/brevo/index.d.ts.map +1 -1
- package/dist/email/brevo/index.js.map +1 -1
- package/dist/email/cloudflare/index.d.ts +20 -7
- package/dist/email/cloudflare/index.d.ts.map +1 -1
- package/dist/email/cloudflare/index.js +46 -9
- package/dist/email/cloudflare/index.js.map +1 -1
- package/dist/email/core/index.d.ts +6 -9
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.d.ts +10 -13
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/email/smtp/index.js +107 -32
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.d.ts +1 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +9 -14
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.d.ts +2 -4
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +105 -76
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +196 -174
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +25 -20
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +23 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +19 -1
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +76 -62
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +20 -2
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +28 -20
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +12 -15
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/core/index.js.map +1 -1
- package/dist/queue/core/index.workerd.js.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -5
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +9 -2
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +14 -9
- package/dist/react/auth/index.d.ts.map +1 -1
- package/dist/react/auth/index.js +9 -2
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +7 -8
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +6 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +2 -5
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +16 -15
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.d.ts +2 -4
- package/dist/react/head/index.d.ts.map +1 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +90 -11
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +147 -11
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.d.ts +1 -2
- package/dist/react/intro/index.d.ts.map +1 -1
- package/dist/react/intro/index.js +2 -2
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +193 -24
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +434 -222
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +249 -35
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/sitemap/index.browser.js +35 -0
- package/dist/react/sitemap/index.browser.js.map +1 -0
- package/dist/react/sitemap/index.d.ts +92 -0
- package/dist/react/sitemap/index.d.ts.map +1 -0
- package/dist/react/sitemap/index.js +131 -0
- package/dist/react/sitemap/index.js.map +1 -0
- package/dist/react/testing/index.d.ts +1 -2
- package/dist/react/testing/index.d.ts.map +1 -1
- package/dist/react/testing/index.js +16 -17
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +20 -25
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/redis/index.bun.js.map +1 -1
- package/dist/redis/index.d.ts +17 -19
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +2 -4
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +10 -13
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +45 -48
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.browser.js.map +1 -1
- package/dist/server/auth/index.d.ts +272 -173
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1608 -15
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.d.ts +20 -7
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +22 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +106 -73
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +44 -0
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +11 -14
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/etag/index.d.ts +6 -9
- package/dist/server/etag/index.d.ts.map +1 -1
- package/dist/server/etag/index.js.map +1 -1
- package/dist/server/health/index.d.ts +18 -21
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/links/index.browser.js +2 -0
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +63 -67
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +2 -0
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +5 -7
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -5
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +10 -13
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +3 -5
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +5 -8
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -5
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +2 -4
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.d.ts +4 -6
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.d.ts +5 -8
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/topic/redis/index.js.map +1 -1
- package/package.json +59 -23
- package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
- package/src/api/audits/controllers/AdminAuditController.ts +14 -0
- package/src/api/audits/services/AuditService.ts +21 -88
- package/src/api/files/__tests__/FileService.spec.ts +207 -2
- package/src/api/files/index.ts +3 -0
- package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
- package/src/api/files/schemas/fileResourceSchema.ts +10 -1
- package/src/api/files/services/FileService.ts +170 -72
- package/src/api/jobs/__tests__/$job.spec.ts +24 -1
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/primitives/$job.ts +7 -3
- package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
- package/src/api/jobs/providers/JobProvider.ts +53 -24
- package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
- package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
- package/src/api/parameters/audits/ParameterAudits.ts +17 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
- package/src/api/parameters/index.ts +3 -0
- package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
- package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
- package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
- package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
- package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
- package/src/api/parameters/services/ParameterProvider.ts +69 -6
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
- package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
- package/src/api/users/audits/SessionAudits.ts +33 -0
- package/src/api/users/audits/UserAudits.ts +19 -43
- package/src/api/users/controllers/AdminUserController.ts +66 -1
- package/src/api/users/controllers/RealmController.ts +1 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/entities/users.ts +2 -0
- package/src/api/users/index.ts +9 -1
- package/src/api/users/primitives/$realm.ts +29 -0
- package/src/api/users/providers/RealmProvider.ts +15 -0
- package/src/api/users/schemas/realmConfigSchema.ts +14 -0
- package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
- package/src/api/users/schemas/updateUserSchema.ts +1 -8
- package/src/api/users/schemas/userQuerySchema.ts +7 -0
- package/src/api/users/services/CredentialService.ts +15 -6
- package/src/api/users/services/IdentityService.ts +2 -1
- package/src/api/users/services/RegistrationService.ts +2 -1
- package/src/api/users/services/SessionCrudService.ts +19 -2
- package/src/api/users/services/SessionService.ts +39 -19
- package/src/api/users/services/UserService.ts +106 -8
- package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
- package/src/background/index.ts +37 -0
- package/src/background/index.workerd.ts +28 -0
- package/src/background/providers/BackgroundTaskProvider.ts +70 -0
- package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
- package/src/bucket/__tests__/$bucket.spec.ts +18 -0
- package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
- package/src/bucket/__tests__/shared.ts +30 -0
- package/src/bucket/index.ts +5 -5
- package/src/bucket/index.workerd.ts +11 -4
- package/src/bucket/primitives/$bucket.ts +27 -0
- package/src/bucket/providers/FileStorageProvider.ts +13 -0
- package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
- package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
- package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
- package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
- package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
- package/src/cli/core/__tests__/init.spec.ts +0 -219
- package/src/cli/core/atoms/buildOptions.ts +0 -12
- package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
- package/src/cli/core/commands/build.ts +105 -37
- package/src/cli/core/commands/init.ts +0 -12
- package/src/cli/core/commands/pack.ts +133 -0
- package/src/cli/core/index.ts +3 -3
- package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
- package/src/cli/core/services/PackageManagerUtils.ts +0 -16
- package/src/cli/core/services/ProjectScaffolder.ts +29 -291
- package/src/cli/core/tasks/BuildCloudflareTask.ts +382 -56
- package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
- package/src/cli/core/tasks/BuildPrerenderTask.ts +44 -7
- package/src/cli/core/tasks/BuildTask.ts +34 -0
- package/src/cli/core/templates/apiIndexTs.ts +1 -22
- package/src/cli/core/templates/mainCss.ts +0 -1
- package/src/cli/core/templates/webAppRouterTs.ts +0 -99
- package/src/cli/core/templates/webIndexTs.ts +1 -22
- package/src/cli/i18n/__tests__/I18nCheckService.spec.ts +48 -0
- package/src/cli/i18n/services/I18nCheckService.ts +65 -11
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
- package/src/cli/platform/commands/SecretsCommand.ts +8 -6
- package/src/cli/platform/commands/platform.ts +192 -46
- package/src/cli/platform/index.ts +12 -52
- package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
- package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
- package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
- package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +519 -190
- package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
- package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
- package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
- package/src/cli/platform-lib/index.ts +67 -0
- package/src/cli/platform-lib/services/NamingService.ts +136 -0
- package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
- package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
- package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
- package/src/command/__tests__/Runner.spec.ts +20 -0
- package/src/command/helpers/EnvUtils.ts +19 -3
- package/src/command/helpers/Runner.ts +12 -2
- package/src/command/providers/CliProvider.ts +34 -1
- package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
- package/src/{containers → container}/core/index.ts +4 -4
- package/src/{containers → container}/core/index.workerd.ts +19 -3
- package/src/{containers → container}/core/primitives/$container.ts +1 -1
- package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
- package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
- package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
- package/src/core/Alepha.ts +49 -1
- package/src/core/__tests__/$env.spec.ts +42 -0
- package/src/core/__tests__/dump.spec.ts +47 -0
- package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
- package/src/email/cloudflare/index.ts +14 -5
- package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
- package/src/logger/__tests__/Logger.spec.ts +55 -0
- package/src/logger/index.ts +13 -0
- package/src/logger/services/Logger.ts +31 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +71 -0
- package/src/mcp/providers/McpServerProvider.ts +55 -0
- package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
- package/src/orm/core/interfaces/PgQuery.ts +4 -1
- package/src/orm/core/services/Repository.ts +27 -11
- package/src/react/auth/hooks/useAuth.ts +10 -5
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
- package/src/react/core/hooks/useAction.ts +14 -3
- package/src/react/core/hooks/useQuery.ts +24 -4
- package/src/react/form/__tests__/FormModel-submit-loading.spec.ts +71 -0
- package/src/react/form/__tests__/form-submitting-reactive.browser.spec.tsx +96 -0
- package/src/react/form/services/FormModel.ts +57 -39
- package/src/react/i18n/__tests__/I18nProvider.spec.ts +89 -0
- package/src/react/i18n/__tests__/locale-routing.spec.ts +107 -0
- package/src/react/i18n/components/Translate.tsx +47 -0
- package/src/react/i18n/index.ts +2 -0
- package/src/react/i18n/providers/I18nProvider.ts +171 -12
- package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
- package/src/react/router/__tests__/$page.spec.tsx +3 -2
- package/src/react/router/__tests__/RouterLocaleProvider.spec.ts +127 -0
- package/src/react/router/__tests__/page-can.spec.ts +18 -13
- package/src/react/router/hooks/useQueryParams.ts +114 -14
- package/src/react/router/index.browser.ts +4 -0
- package/src/react/router/index.shared.ts +1 -0
- package/src/react/router/index.ts +9 -0
- package/src/react/router/primitives/$page.ts +85 -4
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +18 -8
- package/src/react/router/providers/ReactPageProvider.ts +12 -1
- package/src/react/router/providers/ReactServerProvider.ts +96 -14
- package/src/react/router/providers/RootComponentsProvider.ts +13 -0
- package/src/react/router/providers/RouterLocaleProvider.ts +125 -0
- package/src/react/router/providers/__tests__/RootComponentsProvider.spec.ts +15 -0
- package/src/react/router/providers/__tests__/rootComponents.ssr.browser.spec.tsx +67 -0
- package/src/react/sitemap/__tests__/$sitemap.spec.ts +131 -0
- package/src/react/sitemap/index.browser.ts +21 -0
- package/src/react/sitemap/index.ts +25 -0
- package/src/react/sitemap/primitives/$sitemap.browser.ts +26 -0
- package/src/react/sitemap/primitives/$sitemap.ts +196 -0
- package/src/react/ui/services/SchemaControl.ts +3 -4
- package/src/server/auth/__tests__/appleClientSecret.spec.ts +34 -0
- package/src/server/auth/__tests__/authFederationClient.spec.ts +40 -0
- package/src/server/auth/__tests__/federationAssertion.spec.ts +146 -0
- package/src/server/auth/__tests__/federationRedirectReplay.spec.ts +44 -0
- package/src/server/auth/helpers/appleClientSecret.ts +24 -0
- package/src/server/auth/helpers/federationAssertion.ts +74 -0
- package/src/server/auth/helpers/jtiReplayGuard.ts +41 -0
- package/src/server/auth/helpers/safeRedirectPath.ts +19 -0
- package/src/server/auth/index.ts +4 -0
- package/src/server/auth/primitives/$authFederationBroker.ts +273 -0
- package/src/server/auth/primitives/$authFederationClient.ts +89 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +18 -4
- package/src/server/cookies/__tests__/ServerCookiesProvider.spec.ts +70 -0
- package/src/server/cookies/providers/ServerCookiesProvider.ts +23 -3
- package/src/server/core/interfaces/ServerRequest.ts +8 -0
- package/src/server/core/primitives/$route.ts +27 -0
- package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
- package/src/server/links/providers/LinkProvider.ts +10 -0
- package/dist/containers/core/index.d.ts.map +0 -1
- package/dist/containers/core/index.js.map +0 -1
- package/dist/containers/core/index.workerd.js.map +0 -1
- package/src/cli/core/tasks/BuildSitemapTask.ts +0 -130
- package/src/cli/core/templates/componentsJsonTs.ts +0 -39
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
- package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
- package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
- package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
- package/src/cli/platform/services/NamingService.ts +0 -54
- /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
- /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
- /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
- /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
- /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
- /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
|
@@ -1,2169 +1,8 @@
|
|
|
1
|
-
import { $
|
|
2
|
-
import { AlephaCli,
|
|
3
|
-
import {
|
|
4
|
-
import { $command,
|
|
1
|
+
import { $context, $inject, $module, $state, AlephaError, t } from "alepha";
|
|
2
|
+
import { AlephaCli, AppEntryProvider, ViteBuildProvider } from "alepha/cli";
|
|
3
|
+
import { AlephaPlatformLibPlugin, CloudflareAdapter, GitHubSecretStore, NamingService, PlatformInspector, PlatformOrchestrator, SecretFilterService, VercelAdapter, platformOptions, resolveTenant } from "alepha/cli/platform-lib";
|
|
4
|
+
import { $command, EnvUtils } from "alepha/command";
|
|
5
5
|
import { $logger, ConsoleColorProvider } from "alepha/logger";
|
|
6
|
-
import { FileSystemProvider, ShellProvider } from "alepha/system";
|
|
7
|
-
import { S3mini } from "s3mini";
|
|
8
|
-
import { DateTimeProvider } from "alepha/datetime";
|
|
9
|
-
import { homedir, platform as platform$1 } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
//#region ../../src/cli/platform/providers/PlatformCacheProvider.ts
|
|
12
|
-
/**
|
|
13
|
-
* Caches cloud provider login state to avoid slow auth checks.
|
|
14
|
-
*
|
|
15
|
-
* Stored in node_modules/.alepha/platform.json (gitignored, project-scoped).
|
|
16
|
-
* TTL: 4 hours.
|
|
17
|
-
*/
|
|
18
|
-
var PlatformCacheProvider = class PlatformCacheProvider {
|
|
19
|
-
static TTL_MS = 14400 * 1e3;
|
|
20
|
-
fs = $inject(FileSystemProvider);
|
|
21
|
-
dateTime = $inject(DateTimeProvider);
|
|
22
|
-
cachePath(root) {
|
|
23
|
-
return this.fs.join(root, "node_modules", ".alepha", "platform.json");
|
|
24
|
-
}
|
|
25
|
-
async isLoginFresh(root, adapter) {
|
|
26
|
-
const entry = (await this.readCache(root))[adapter];
|
|
27
|
-
if (!entry) return false;
|
|
28
|
-
return this.dateTime.nowMillis() - entry.lastLoginCheck < PlatformCacheProvider.TTL_MS;
|
|
29
|
-
}
|
|
30
|
-
async getAccountId(root, adapter) {
|
|
31
|
-
return (await this.readCache(root))[adapter]?.accountId;
|
|
32
|
-
}
|
|
33
|
-
async recordLogin(root, adapter, accountId) {
|
|
34
|
-
const cache = await this.readCache(root);
|
|
35
|
-
cache[adapter] = {
|
|
36
|
-
lastLoginCheck: this.dateTime.nowMillis(),
|
|
37
|
-
accountId
|
|
38
|
-
};
|
|
39
|
-
await this.writeCache(root, cache);
|
|
40
|
-
}
|
|
41
|
-
async readCache(root) {
|
|
42
|
-
const path = this.cachePath(root);
|
|
43
|
-
try {
|
|
44
|
-
return await this.fs.readJsonFile(path);
|
|
45
|
-
} catch {
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
async writeCache(root, cache) {
|
|
50
|
-
const path = this.cachePath(root);
|
|
51
|
-
const lastSlash = path.lastIndexOf("/");
|
|
52
|
-
const dir = lastSlash > 0 ? path.slice(0, lastSlash) : path;
|
|
53
|
-
await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
|
|
54
|
-
await this.fs.writeFile(path, JSON.stringify(cache, null, 2));
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region ../../src/cli/platform/schemas/cloudflare.ts
|
|
59
|
-
const cloudflareAccountSchema = t.object({
|
|
60
|
-
id: t.string(),
|
|
61
|
-
name: t.string()
|
|
62
|
-
});
|
|
63
|
-
const cloudflareD1Schema = t.object({
|
|
64
|
-
uuid: t.string(),
|
|
65
|
-
name: t.string()
|
|
66
|
-
});
|
|
67
|
-
const cloudflareKVSchema = t.object({
|
|
68
|
-
id: t.string(),
|
|
69
|
-
title: t.string()
|
|
70
|
-
});
|
|
71
|
-
const cloudflareR2Schema = t.object({
|
|
72
|
-
name: t.string(),
|
|
73
|
-
creation_date: t.optional(t.string())
|
|
74
|
-
});
|
|
75
|
-
const cloudflareR2ListSchema = t.object({ buckets: t.array(cloudflareR2Schema) });
|
|
76
|
-
const cloudflareQueueSchema = t.object({
|
|
77
|
-
queue_id: t.string(),
|
|
78
|
-
queue_name: t.string()
|
|
79
|
-
});
|
|
80
|
-
const cloudflareQueueConsumerSchema = t.object({
|
|
81
|
-
consumer_id: t.string(),
|
|
82
|
-
service: t.string(),
|
|
83
|
-
environment: t.optional(t.string())
|
|
84
|
-
});
|
|
85
|
-
const cloudflareHyperdriveOriginSchema = t.object({ host: t.string() });
|
|
86
|
-
const cloudflareHyperdriveSchema = t.object({
|
|
87
|
-
id: t.string(),
|
|
88
|
-
name: t.string(),
|
|
89
|
-
origin: cloudflareHyperdriveOriginSchema
|
|
90
|
-
});
|
|
91
|
-
const cloudflareWorkerSchema = t.object({
|
|
92
|
-
id: t.string(),
|
|
93
|
-
created_on: t.string(),
|
|
94
|
-
modified_on: t.string()
|
|
95
|
-
});
|
|
96
|
-
const cloudflareDeploymentVersionSchema = t.object({
|
|
97
|
-
version_id: t.string(),
|
|
98
|
-
percentage: t.number()
|
|
99
|
-
});
|
|
100
|
-
const cloudflareDeploymentSchema = t.object({
|
|
101
|
-
id: t.string(),
|
|
102
|
-
versions: t.array(cloudflareDeploymentVersionSchema),
|
|
103
|
-
created_on: t.string()
|
|
104
|
-
});
|
|
105
|
-
const cloudflareDeploymentListSchema = t.object({ deployments: t.array(cloudflareDeploymentSchema) });
|
|
106
|
-
const cloudflareVersionSchema = t.object({
|
|
107
|
-
id: t.string(),
|
|
108
|
-
metadata: t.object({ created_on: t.string() }),
|
|
109
|
-
annotations: t.optional(t.record(t.string(), t.string()))
|
|
110
|
-
});
|
|
111
|
-
const cloudflareVersionListSchema = t.object({ items: t.array(cloudflareVersionSchema) });
|
|
112
|
-
const cloudflareSecretSchema = t.object({
|
|
113
|
-
name: t.string(),
|
|
114
|
-
type: t.string()
|
|
115
|
-
});
|
|
116
|
-
const createD1BodySchema = t.object({
|
|
117
|
-
name: t.string(),
|
|
118
|
-
primary_location_hint: t.optional(t.string()),
|
|
119
|
-
jurisdiction: t.optional(t.string())
|
|
120
|
-
});
|
|
121
|
-
const createKVBodySchema = t.object({ title: t.string() });
|
|
122
|
-
const createR2BodySchema = t.object({ name: t.string() });
|
|
123
|
-
const cloudflareR2TokenSchema = t.object({
|
|
124
|
-
id: t.string(),
|
|
125
|
-
accessKeyId: t.string(),
|
|
126
|
-
secretAccessKey: t.string()
|
|
127
|
-
});
|
|
128
|
-
const createR2TokenBodySchema = t.object({
|
|
129
|
-
name: t.string(),
|
|
130
|
-
policies: t.array(t.object({
|
|
131
|
-
effect: t.string(),
|
|
132
|
-
permissions: t.array(t.string()),
|
|
133
|
-
buckets: t.optional(t.array(t.string()))
|
|
134
|
-
}))
|
|
135
|
-
});
|
|
136
|
-
const createQueueBodySchema = t.object({ queue_name: t.string() });
|
|
137
|
-
const createHyperdriveOriginSchema = t.object({
|
|
138
|
-
scheme: t.string(),
|
|
139
|
-
host: t.string(),
|
|
140
|
-
port: t.number(),
|
|
141
|
-
database: t.string(),
|
|
142
|
-
user: t.string(),
|
|
143
|
-
password: t.string()
|
|
144
|
-
});
|
|
145
|
-
const createHyperdriveBodySchema = t.object({
|
|
146
|
-
name: t.string(),
|
|
147
|
-
origin: createHyperdriveOriginSchema
|
|
148
|
-
});
|
|
149
|
-
const putSecretBodySchema = t.object({
|
|
150
|
-
name: t.string(),
|
|
151
|
-
text: t.string(),
|
|
152
|
-
type: t.string()
|
|
153
|
-
});
|
|
154
|
-
const cloudflareApiErrorSchema = t.object({
|
|
155
|
-
code: t.number(),
|
|
156
|
-
message: t.string()
|
|
157
|
-
});
|
|
158
|
-
//#endregion
|
|
159
|
-
//#region ../../src/cli/platform/services/WranglerApi.ts
|
|
160
|
-
/**
|
|
161
|
-
* Wraps wrangler CLI commands that are kept as shell-outs.
|
|
162
|
-
*
|
|
163
|
-
* Only used for operations where wrangler provides value
|
|
164
|
-
* beyond a raw API call: OAuth login, worker deploy (bundling/upload),
|
|
165
|
-
* D1 migrations, and secret bulk push.
|
|
166
|
-
*/
|
|
167
|
-
var WranglerApi = class {
|
|
168
|
-
log = $logger();
|
|
169
|
-
shell = $inject(ShellProvider);
|
|
170
|
-
utils = $inject(AlephaCliUtils);
|
|
171
|
-
pm = $inject(PackageManagerUtils);
|
|
172
|
-
runner = $inject(Runner);
|
|
173
|
-
async runShell(command, options = {}) {
|
|
174
|
-
const capture = options.capture;
|
|
175
|
-
const output = await this.shell.run(command, {
|
|
176
|
-
...options,
|
|
177
|
-
capture: capture ?? this.runner.useDynamicLogger
|
|
178
|
-
});
|
|
179
|
-
if (capture && !this.runner.useDynamicLogger) this.log.info(output);
|
|
180
|
-
return output;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Ensure wrangler is installed in the project.
|
|
184
|
-
*/
|
|
185
|
-
async ensureInstalled(root, run) {
|
|
186
|
-
await this.pm.ensureDependency(root, "wrangler", {
|
|
187
|
-
dev: true,
|
|
188
|
-
exec: async (cmd, opts) => {
|
|
189
|
-
run.pause();
|
|
190
|
-
try {
|
|
191
|
-
await this.utils.exec(cmd, opts);
|
|
192
|
-
} finally {
|
|
193
|
-
run.resume();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Check if the user is authenticated. Returns the whoami output.
|
|
200
|
-
*/
|
|
201
|
-
async whoami() {
|
|
202
|
-
return await this.runShell("wrangler whoami", {
|
|
203
|
-
resolve: true,
|
|
204
|
-
capture: true
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Open the browser-based OAuth login flow.
|
|
209
|
-
*/
|
|
210
|
-
async login() {
|
|
211
|
-
await this.runShell("wrangler login", { resolve: true });
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Get the current auth token from wrangler (auto-refreshes if expired).
|
|
215
|
-
*/
|
|
216
|
-
async getAuthToken() {
|
|
217
|
-
const output = await this.shell.run("wrangler auth token --json", {
|
|
218
|
-
resolve: true,
|
|
219
|
-
capture: true
|
|
220
|
-
});
|
|
221
|
-
return JSON.parse(output).token;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Deploy a worker via wrangler (handles bundling and upload).
|
|
225
|
-
*
|
|
226
|
-
* Returns the workers.dev URL if found in the output.
|
|
227
|
-
*/
|
|
228
|
-
async deploy(workerName, configPath) {
|
|
229
|
-
return (await this.runShell(`wrangler deploy --name=${workerName} --no-bundle --config=${configPath}`, {
|
|
230
|
-
resolve: true,
|
|
231
|
-
capture: true
|
|
232
|
-
})).match(/https:\/\/[^\s]*\.workers\.dev/)?.[0];
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Apply D1 migrations remotely.
|
|
236
|
-
*/
|
|
237
|
-
async d1MigrationsApply(dbName, configPath) {
|
|
238
|
-
await this.runShell(`wrangler d1 migrations apply ${dbName} --remote --config=${configPath}`, {
|
|
239
|
-
resolve: true,
|
|
240
|
-
env: { CI: "1" }
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
//#endregion
|
|
245
|
-
//#region ../../src/cli/platform/services/CloudflareApi.ts
|
|
246
|
-
/**
|
|
247
|
-
* Thin wrapper over the Cloudflare REST API.
|
|
248
|
-
*
|
|
249
|
-
* Uses `wrangler auth token` to obtain credentials,
|
|
250
|
-
* then calls `fetch()` directly for all CRUD operations.
|
|
251
|
-
*/
|
|
252
|
-
var CloudflareApi = class CloudflareApi {
|
|
253
|
-
static BASE = "https://api.cloudflare.com/client/v4";
|
|
254
|
-
log = $logger();
|
|
255
|
-
alepha = $inject(Alepha);
|
|
256
|
-
wrangler = $inject(WranglerApi);
|
|
257
|
-
token;
|
|
258
|
-
accountId;
|
|
259
|
-
jurisdiction;
|
|
260
|
-
/**
|
|
261
|
-
* Set the Cloudflare data jurisdiction for R2 and D1 resources.
|
|
262
|
-
*
|
|
263
|
-
* R2 buckets and D1 databases created under a jurisdiction live in a
|
|
264
|
-
* separate namespace — every R2 API call (list/create/delete) must include
|
|
265
|
-
* the `cf-r2-jurisdiction` header, and D1 create must include the field
|
|
266
|
-
* in the request body. Omit / pass `undefined` for the default (global).
|
|
267
|
-
*/
|
|
268
|
-
setJurisdiction(jurisdiction) {
|
|
269
|
-
this.jurisdiction = jurisdiction;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Override the Cloudflare account ID (from platform config).
|
|
273
|
-
*
|
|
274
|
-
* When unset, `resolveAccountId` falls back to `CLOUDFLARE_ACCOUNT_ID` env
|
|
275
|
-
* var or the token's single account.
|
|
276
|
-
*/
|
|
277
|
-
setAccountId(accountId) {
|
|
278
|
-
this.accountId = accountId;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Obtain the current auth token from wrangler.
|
|
282
|
-
*/
|
|
283
|
-
async resolveToken() {
|
|
284
|
-
if (this.token) return this.token;
|
|
285
|
-
this.token = await this.wrangler.getAuthToken();
|
|
286
|
-
return this.token;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Resolve the Cloudflare account ID.
|
|
290
|
-
*
|
|
291
|
-
* Calls /accounts and picks the first one. Cached after first call.
|
|
292
|
-
*/
|
|
293
|
-
async resolveAccountId() {
|
|
294
|
-
if (this.accountId) return this.accountId;
|
|
295
|
-
const fromEnv = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
296
|
-
if (fromEnv) {
|
|
297
|
-
this.accountId = fromEnv;
|
|
298
|
-
return this.accountId;
|
|
299
|
-
}
|
|
300
|
-
const res = await this.fetch("/accounts", { schema: t.array(cloudflareAccountSchema) });
|
|
301
|
-
if (res.length === 0) throw new AlephaError("No Cloudflare accounts found for this token.");
|
|
302
|
-
if (res.length > 1) {
|
|
303
|
-
const list = res.map((a) => ` - ${a.id} ${a.name}`).join("\n");
|
|
304
|
-
throw new AlephaError(`Cloudflare token has access to ${res.length} accounts; set \`CLOUDFLARE_ACCOUNT_ID\` or the \`accountId\` field in your platform config to pick one:\n${list}`);
|
|
305
|
-
}
|
|
306
|
-
this.accountId = res[0].id;
|
|
307
|
-
return this.accountId;
|
|
308
|
-
}
|
|
309
|
-
async listD1() {
|
|
310
|
-
const accountId = await this.resolveAccountId();
|
|
311
|
-
return await this.paginate(`/accounts/${accountId}/d1/database`, cloudflareD1Schema);
|
|
312
|
-
}
|
|
313
|
-
async createD1(name, location = "weur") {
|
|
314
|
-
const accountId = await this.resolveAccountId();
|
|
315
|
-
const body = { name };
|
|
316
|
-
if (this.jurisdiction) body.jurisdiction = this.jurisdiction;
|
|
317
|
-
else body.primary_location_hint = location;
|
|
318
|
-
return await this.fetch(`/accounts/${accountId}/d1/database`, {
|
|
319
|
-
method: "POST",
|
|
320
|
-
body,
|
|
321
|
-
bodySchema: createD1BodySchema,
|
|
322
|
-
schema: cloudflareD1Schema
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
async deleteD1(databaseId) {
|
|
326
|
-
const accountId = await this.resolveAccountId();
|
|
327
|
-
await this.fetch(`/accounts/${accountId}/d1/database/${databaseId}`, { method: "DELETE" });
|
|
328
|
-
}
|
|
329
|
-
async listKV() {
|
|
330
|
-
const accountId = await this.resolveAccountId();
|
|
331
|
-
return await this.paginate(`/accounts/${accountId}/storage/kv/namespaces`, cloudflareKVSchema, 100);
|
|
332
|
-
}
|
|
333
|
-
async createKV(title) {
|
|
334
|
-
const accountId = await this.resolveAccountId();
|
|
335
|
-
return await this.fetch(`/accounts/${accountId}/storage/kv/namespaces`, {
|
|
336
|
-
method: "POST",
|
|
337
|
-
body: { title },
|
|
338
|
-
bodySchema: createKVBodySchema,
|
|
339
|
-
schema: cloudflareKVSchema
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
async deleteKV(namespaceId) {
|
|
343
|
-
const accountId = await this.resolveAccountId();
|
|
344
|
-
await this.fetch(`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}`, { method: "DELETE" });
|
|
345
|
-
}
|
|
346
|
-
async listR2() {
|
|
347
|
-
const accountId = await this.resolveAccountId();
|
|
348
|
-
return await this.paginateCursor(`/accounts/${accountId}/r2/buckets`, "buckets", cloudflareR2Schema);
|
|
349
|
-
}
|
|
350
|
-
async createR2(name) {
|
|
351
|
-
const accountId = await this.resolveAccountId();
|
|
352
|
-
await this.fetch(`/accounts/${accountId}/r2/buckets`, {
|
|
353
|
-
method: "POST",
|
|
354
|
-
body: { name },
|
|
355
|
-
bodySchema: createR2BodySchema
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
async deleteR2(name) {
|
|
359
|
-
const accountId = await this.resolveAccountId();
|
|
360
|
-
await this.fetch(`/accounts/${accountId}/r2/buckets/${name}`, { method: "DELETE" });
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Mint a bucket-scoped R2 API token (S3 access key + secret) using the
|
|
364
|
-
* current bearer token. Used by teardown to wipe a bucket over the S3
|
|
365
|
-
* protocol without requiring users to pre-create R2 access keys.
|
|
366
|
-
*
|
|
367
|
-
* The returned token should be revoked with `deleteR2Token` as soon as the
|
|
368
|
-
* wipe is done.
|
|
369
|
-
*/
|
|
370
|
-
async createR2Token(name, bucket) {
|
|
371
|
-
const accountId = await this.resolveAccountId();
|
|
372
|
-
return await this.fetch(`/accounts/${accountId}/r2/api-tokens`, {
|
|
373
|
-
method: "POST",
|
|
374
|
-
body: {
|
|
375
|
-
name,
|
|
376
|
-
policies: [{
|
|
377
|
-
effect: "allow",
|
|
378
|
-
permissions: ["admin-read-write"],
|
|
379
|
-
buckets: [bucket]
|
|
380
|
-
}]
|
|
381
|
-
},
|
|
382
|
-
bodySchema: createR2TokenBodySchema,
|
|
383
|
-
schema: cloudflareR2TokenSchema
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
async deleteR2Token(tokenId) {
|
|
387
|
-
const accountId = await this.resolveAccountId();
|
|
388
|
-
await this.fetch(`/accounts/${accountId}/r2/api-tokens/${tokenId}`, { method: "DELETE" });
|
|
389
|
-
}
|
|
390
|
-
async listQueues() {
|
|
391
|
-
const accountId = await this.resolveAccountId();
|
|
392
|
-
return await this.paginate(`/accounts/${accountId}/queues`, cloudflareQueueSchema);
|
|
393
|
-
}
|
|
394
|
-
async createQueue(name) {
|
|
395
|
-
const accountId = await this.resolveAccountId();
|
|
396
|
-
return await this.fetch(`/accounts/${accountId}/queues`, {
|
|
397
|
-
method: "POST",
|
|
398
|
-
body: { queue_name: name },
|
|
399
|
-
bodySchema: createQueueBodySchema,
|
|
400
|
-
schema: cloudflareQueueSchema
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
async deleteQueue(queueId) {
|
|
404
|
-
const accountId = await this.resolveAccountId();
|
|
405
|
-
await this.fetch(`/accounts/${accountId}/queues/${queueId}`, { method: "DELETE" });
|
|
406
|
-
}
|
|
407
|
-
async listQueueConsumers(queueId) {
|
|
408
|
-
const accountId = await this.resolveAccountId();
|
|
409
|
-
return await this.paginate(`/accounts/${accountId}/queues/${queueId}/consumers`, cloudflareQueueConsumerSchema);
|
|
410
|
-
}
|
|
411
|
-
async deleteQueueConsumer(queueId, consumerService) {
|
|
412
|
-
const accountId = await this.resolveAccountId();
|
|
413
|
-
const consumer = (await this.listQueueConsumers(queueId)).find((c) => c.service === consumerService);
|
|
414
|
-
if (!consumer) return;
|
|
415
|
-
await this.fetch(`/accounts/${accountId}/queues/${queueId}/consumers/${consumer.consumer_id}`, { method: "DELETE" });
|
|
416
|
-
}
|
|
417
|
-
async listHyperdrive() {
|
|
418
|
-
const accountId = await this.resolveAccountId();
|
|
419
|
-
return await this.paginate(`/accounts/${accountId}/hyperdrive/configs`, cloudflareHyperdriveSchema);
|
|
420
|
-
}
|
|
421
|
-
async createHyperdrive(name, connectionString) {
|
|
422
|
-
const accountId = await this.resolveAccountId();
|
|
423
|
-
return await this.fetch(`/accounts/${accountId}/hyperdrive/configs`, {
|
|
424
|
-
method: "POST",
|
|
425
|
-
body: {
|
|
426
|
-
name,
|
|
427
|
-
origin: this.parseConnectionString(connectionString)
|
|
428
|
-
},
|
|
429
|
-
bodySchema: createHyperdriveBodySchema,
|
|
430
|
-
schema: cloudflareHyperdriveSchema
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
async deleteHyperdrive(configId) {
|
|
434
|
-
const accountId = await this.resolveAccountId();
|
|
435
|
-
await this.fetch(`/accounts/${accountId}/hyperdrive/configs/${configId}`, { method: "DELETE" });
|
|
436
|
-
}
|
|
437
|
-
async getWorker(scriptName) {
|
|
438
|
-
const accountId = await this.resolveAccountId();
|
|
439
|
-
try {
|
|
440
|
-
return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, { schema: cloudflareWorkerSchema });
|
|
441
|
-
} catch {
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
async deleteWorker(scriptName) {
|
|
446
|
-
const accountId = await this.resolveAccountId();
|
|
447
|
-
await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, {
|
|
448
|
-
method: "DELETE",
|
|
449
|
-
query: { force: "true" }
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
async listDeployments(scriptName) {
|
|
453
|
-
const accountId = await this.resolveAccountId();
|
|
454
|
-
return (await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/deployments`, {
|
|
455
|
-
schema: cloudflareDeploymentListSchema,
|
|
456
|
-
query: { per_page: "100" }
|
|
457
|
-
})).deployments;
|
|
458
|
-
}
|
|
459
|
-
async listVersions(scriptName) {
|
|
460
|
-
const accountId = await this.resolveAccountId();
|
|
461
|
-
return await this.paginateCursor(`/accounts/${accountId}/workers/scripts/${scriptName}/versions`, "items", cloudflareVersionSchema);
|
|
462
|
-
}
|
|
463
|
-
async listSecrets(scriptName) {
|
|
464
|
-
const accountId = await this.resolveAccountId();
|
|
465
|
-
return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, { schema: t.array(cloudflareSecretSchema) });
|
|
466
|
-
}
|
|
467
|
-
async putSecret(scriptName, name, value) {
|
|
468
|
-
const accountId = await this.resolveAccountId();
|
|
469
|
-
await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, {
|
|
470
|
-
method: "PUT",
|
|
471
|
-
body: {
|
|
472
|
-
name,
|
|
473
|
-
text: value,
|
|
474
|
-
type: "secret_text"
|
|
475
|
-
},
|
|
476
|
-
bodySchema: putSecretBodySchema
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Fetch the current worker bindings via the script-settings endpoint.
|
|
481
|
-
* Used to merge new secrets into the existing binding set in one PATCH
|
|
482
|
-
* (avoids the per-secret `putSecret` calls, each of which creates a
|
|
483
|
-
* Cloudflare deployment — pushing 7 secrets meant 7 deployment rows).
|
|
484
|
-
*
|
|
485
|
-
* Secret bindings come back with `name` + `type` but no `text` (they're
|
|
486
|
-
* write-only on Cloudflare's side); to preserve them across a PATCH we
|
|
487
|
-
* forward each one as `{ type, name }` and Cloudflare keeps the stored
|
|
488
|
-
* value.
|
|
489
|
-
*/
|
|
490
|
-
async getWorkerSettings(scriptName) {
|
|
491
|
-
const accountId = await this.resolveAccountId();
|
|
492
|
-
return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/settings`);
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Replace the worker's binding set in one call (= one Cloudflare
|
|
496
|
-
* deployment, regardless of how many secrets are being updated).
|
|
497
|
-
*
|
|
498
|
-
* The endpoint expects multipart FormData with a `settings` field whose
|
|
499
|
-
* value is a JSON-encoded `{ bindings: [...] }` — the `fetch` helper
|
|
500
|
-
* above is JSON-only, so this one bypasses it and calls `globalThis.fetch`
|
|
501
|
-
* directly. Mirrors what `wrangler secret bulk` does internally.
|
|
502
|
-
*/
|
|
503
|
-
async patchWorkerBindings(scriptName, bindings) {
|
|
504
|
-
const accountId = await this.resolveAccountId();
|
|
505
|
-
const token = await this.resolveToken();
|
|
506
|
-
const url = `${CloudflareApi.BASE}/accounts/${accountId}/workers/scripts/${scriptName}/settings`;
|
|
507
|
-
const form = new FormData();
|
|
508
|
-
form.set("settings", JSON.stringify({ bindings }));
|
|
509
|
-
const response = await globalThis.fetch(url, {
|
|
510
|
-
method: "PATCH",
|
|
511
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
512
|
-
body: form
|
|
513
|
-
});
|
|
514
|
-
const json = await response.json().catch(() => null);
|
|
515
|
-
if (!response.ok || !json?.success) {
|
|
516
|
-
const messages = json?.errors?.map((e) => e.message).join(", ");
|
|
517
|
-
throw new AlephaError(`Cloudflare API error (PATCH /workers/scripts/${scriptName}/settings): ${messages ?? response.statusText}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
async fetch(path, options = {}) {
|
|
521
|
-
const token = await this.resolveToken();
|
|
522
|
-
const { method = "GET", body, query } = options;
|
|
523
|
-
let url = `${CloudflareApi.BASE}${path}`;
|
|
524
|
-
if (query) {
|
|
525
|
-
const params = new URLSearchParams(query);
|
|
526
|
-
url += `?${params.toString()}`;
|
|
527
|
-
}
|
|
528
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
529
|
-
if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
|
|
530
|
-
const init = {
|
|
531
|
-
method,
|
|
532
|
-
headers
|
|
533
|
-
};
|
|
534
|
-
if (body) {
|
|
535
|
-
headers["Content-Type"] = "application/json";
|
|
536
|
-
const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
|
|
537
|
-
init.body = JSON.stringify(validated);
|
|
538
|
-
}
|
|
539
|
-
const json = await (await globalThis.fetch(url, init)).json();
|
|
540
|
-
if (!json.success) throw new AlephaError(`Cloudflare API error (${method} ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
|
|
541
|
-
if (options.schema) return this.alepha.codec.validate(options.schema, json.result);
|
|
542
|
-
return json.result;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Paginate a page-based list endpoint (`result_info.total_pages`).
|
|
546
|
-
*
|
|
547
|
-
* Cloudflare defaults to `per_page=20`; we push it to 1000 (max on most
|
|
548
|
-
* list endpoints) and loop if more pages exist. Each page is validated
|
|
549
|
-
* against the item schema.
|
|
550
|
-
*/
|
|
551
|
-
async paginate(path, itemSchema, perPage = 1e3) {
|
|
552
|
-
const results = [];
|
|
553
|
-
let page = 1;
|
|
554
|
-
while (true) {
|
|
555
|
-
const token = await this.resolveToken();
|
|
556
|
-
const url = `${CloudflareApi.BASE}${path}?per_page=${perPage}&page=${page}`;
|
|
557
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
558
|
-
if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
|
|
559
|
-
const json = await (await globalThis.fetch(url, {
|
|
560
|
-
method: "GET",
|
|
561
|
-
headers
|
|
562
|
-
})).json();
|
|
563
|
-
if (!json.success) throw new AlephaError(`Cloudflare API error (GET ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
|
|
564
|
-
const validated = this.alepha.codec.validate(t.array(itemSchema), json.result);
|
|
565
|
-
results.push(...validated);
|
|
566
|
-
const totalPages = json.result_info?.total_pages;
|
|
567
|
-
if (!totalPages || page >= totalPages || validated.length === 0) break;
|
|
568
|
-
page++;
|
|
569
|
-
}
|
|
570
|
-
return results;
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Paginate a cursor-based list endpoint where `result` is an object
|
|
574
|
-
* containing both the items array and a `cursor` field (R2 buckets,
|
|
575
|
-
* Workers versions). Returns the flattened item array.
|
|
576
|
-
*/
|
|
577
|
-
async paginateCursor(path, itemsKey, itemSchema, perPage = 1e3) {
|
|
578
|
-
const results = [];
|
|
579
|
-
let cursor;
|
|
580
|
-
while (true) {
|
|
581
|
-
const query = { per_page: String(perPage) };
|
|
582
|
-
if (cursor) query.cursor = cursor;
|
|
583
|
-
const res = await this.fetch(path, { query });
|
|
584
|
-
const items = res[itemsKey] ?? [];
|
|
585
|
-
const validated = this.alepha.codec.validate(t.array(itemSchema), items);
|
|
586
|
-
results.push(...validated);
|
|
587
|
-
const nextCursor = res.cursor;
|
|
588
|
-
if (!nextCursor || validated.length === 0) break;
|
|
589
|
-
cursor = nextCursor;
|
|
590
|
-
}
|
|
591
|
-
return results;
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Parse a postgres:// connection string into Hyperdrive origin fields.
|
|
595
|
-
*/
|
|
596
|
-
parseConnectionString(connectionString) {
|
|
597
|
-
const url = new URL(connectionString);
|
|
598
|
-
return {
|
|
599
|
-
scheme: "postgres",
|
|
600
|
-
host: url.hostname,
|
|
601
|
-
port: Number(url.port) || 5432,
|
|
602
|
-
database: url.pathname.slice(1),
|
|
603
|
-
user: decodeURIComponent(url.username),
|
|
604
|
-
password: decodeURIComponent(url.password)
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
//#endregion
|
|
609
|
-
//#region ../../src/cli/platform/adapters/PlatformAdapter.ts
|
|
610
|
-
/**
|
|
611
|
-
* Abstract platform adapter.
|
|
612
|
-
*
|
|
613
|
-
* Each cloud provider (Cloudflare, AKS, docker-compose) implements this.
|
|
614
|
-
* The PlatformOrchestrator calls these methods in the correct order.
|
|
615
|
-
*/
|
|
616
|
-
var PlatformAdapter = class {
|
|
617
|
-
/**
|
|
618
|
-
* Create/ensure cloud resources exist (DB, buckets, queues).
|
|
619
|
-
* Not all adapters provision -- AKS defers to Helm.
|
|
620
|
-
*/
|
|
621
|
-
async provision(_ctx, _run) {}
|
|
622
|
-
/**
|
|
623
|
-
* Run database migrations.
|
|
624
|
-
*/
|
|
625
|
-
async migrate(_ctx, _run) {}
|
|
626
|
-
/**
|
|
627
|
-
* Push runtime secrets to the deployed worker(s).
|
|
628
|
-
*
|
|
629
|
-
* Reads secrets from `.env.{env}` files (parsed, not from process.env),
|
|
630
|
-
* filters out vars already handled by bindings (DATABASE_URL, R2, etc.),
|
|
631
|
-
* and pushes the rest via the platform's secret management.
|
|
632
|
-
*/
|
|
633
|
-
async secrets(_ctx, _run) {}
|
|
634
|
-
};
|
|
635
|
-
//#endregion
|
|
636
|
-
//#region ../../src/cli/platform/adapters/CloudflareAdapter.ts
|
|
637
|
-
/**
|
|
638
|
-
* Cloudflare Workers adapter.
|
|
639
|
-
*
|
|
640
|
-
* Uses the Cloudflare REST API (via CloudflareApi) for resource provisioning
|
|
641
|
-
* and teardown, and wrangler CLI (via WranglerApi) for login, deploy,
|
|
642
|
-
* D1 migrations, and secret bulk push.
|
|
643
|
-
*/
|
|
644
|
-
var CloudflareAdapter = class CloudflareAdapter extends PlatformAdapter {
|
|
645
|
-
log = $logger();
|
|
646
|
-
fs = $inject(FileSystemProvider);
|
|
647
|
-
shell = $inject(ShellProvider);
|
|
648
|
-
cache = $inject(PlatformCacheProvider);
|
|
649
|
-
alepha = $inject(Alepha);
|
|
650
|
-
envUtils = $inject(EnvUtils);
|
|
651
|
-
api = $inject(CloudflareApi);
|
|
652
|
-
wrangler = $inject(WranglerApi);
|
|
653
|
-
runner = $inject(Runner);
|
|
654
|
-
provisionedD1Id;
|
|
655
|
-
provisionedHyperdriveId;
|
|
656
|
-
provisionedKVIds = /* @__PURE__ */ new Map();
|
|
657
|
-
/**
|
|
658
|
-
* Check if the user's DATABASE_URL points to an external Postgres database.
|
|
659
|
-
* If so, we use Hyperdrive instead of D1.
|
|
660
|
-
*
|
|
661
|
-
* Reads from `.env.{env}` first, falls back to `process.env`.
|
|
662
|
-
*/
|
|
663
|
-
async isPostgres(ctx) {
|
|
664
|
-
return !!((await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL)?.startsWith("postgres:");
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* Propagate the environment's data-jurisdiction setting to the API client.
|
|
668
|
-
*
|
|
669
|
-
* Must be invoked at the top of every entry point (authenticate, build,
|
|
670
|
-
* deploy, secrets, provision, migrate, inspect, teardown) because
|
|
671
|
-
* CloudflareApi is a singleton reused across env invocations.
|
|
672
|
-
*/
|
|
673
|
-
configureApi(ctx) {
|
|
674
|
-
this.api.setJurisdiction(ctx.envConfig.jurisdiction);
|
|
675
|
-
this.api.setAccountId(ctx.envConfig.accountId);
|
|
676
|
-
}
|
|
677
|
-
async runShell(command, options = {}) {
|
|
678
|
-
const capture = options.capture;
|
|
679
|
-
const output = await this.shell.run(command, {
|
|
680
|
-
...options,
|
|
681
|
-
capture: capture ?? this.runner.useDynamicLogger
|
|
682
|
-
});
|
|
683
|
-
if (capture && !this.runner.useDynamicLogger) this.log.info(output);
|
|
684
|
-
return output;
|
|
685
|
-
}
|
|
686
|
-
async authenticate(ctx, run) {
|
|
687
|
-
this.configureApi(ctx);
|
|
688
|
-
await run({
|
|
689
|
-
name: "authenticate",
|
|
690
|
-
handler: async () => {
|
|
691
|
-
await this.wrangler.ensureInstalled(ctx.root, run);
|
|
692
|
-
let needsLogin = false;
|
|
693
|
-
try {
|
|
694
|
-
await this.wrangler.getAuthToken();
|
|
695
|
-
} catch {
|
|
696
|
-
needsLogin = true;
|
|
697
|
-
}
|
|
698
|
-
if (needsLogin) {
|
|
699
|
-
run.pause();
|
|
700
|
-
await this.wrangler.login();
|
|
701
|
-
run.resume();
|
|
702
|
-
}
|
|
703
|
-
if (await this.cache.isLoginFresh(ctx.root, "cloudflare")) return;
|
|
704
|
-
try {
|
|
705
|
-
const accountId = await this.api.resolveAccountId();
|
|
706
|
-
await this.cache.recordLogin(ctx.root, "cloudflare", accountId);
|
|
707
|
-
} catch {
|
|
708
|
-
await this.cache.recordLogin(ctx.root, "cloudflare");
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
async build(ctx, run) {
|
|
714
|
-
this.configureApi(ctx);
|
|
715
|
-
const appDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path) : ctx.root;
|
|
716
|
-
const env = {};
|
|
717
|
-
if (ctx.app.resources.hasDatabase) {
|
|
718
|
-
if (this.provisionedHyperdriveId) {
|
|
719
|
-
env.HYPERDRIVE_ID = this.provisionedHyperdriveId;
|
|
720
|
-
const pgSchema = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
|
|
721
|
-
if (pgSchema) env.POSTGRES_SCHEMA = pgSchema;
|
|
722
|
-
} else if (this.provisionedD1Id) env.DATABASE_URL = `d1://${ctx.naming.d1()}:${this.provisionedD1Id}`;
|
|
723
|
-
}
|
|
724
|
-
if (ctx.app.resources.hasBucket) env.R2_BUCKET_NAME = ctx.naming.r2();
|
|
725
|
-
if (ctx.app.resources.hasKV) {
|
|
726
|
-
const kvName = ctx.naming.kv();
|
|
727
|
-
env.CLOUDFLARE_KV_NAME = kvName;
|
|
728
|
-
const kvId = this.provisionedKVIds.get(kvName);
|
|
729
|
-
if (kvId) env.CLOUDFLARE_KV_ID = kvId;
|
|
730
|
-
}
|
|
731
|
-
if (ctx.app.resources.hasQueue) env.CLOUDFLARE_QUEUE_NAME = ctx.naming.queue();
|
|
732
|
-
if (ctx.envConfig.domain) {
|
|
733
|
-
if (ctx.envConfig.domain.includes("*") && !ctx.envConfig.zone) throw new AlephaError(`Wildcard domain "${ctx.envConfig.domain}" requires "zone" to be set in the environment config (the Cloudflare zone name, e.g. "alepha.dev").`);
|
|
734
|
-
env.CLOUDFLARE_DOMAIN = ctx.envConfig.domain;
|
|
735
|
-
if (ctx.envConfig.zone) env.CLOUDFLARE_ZONE = ctx.envConfig.zone;
|
|
736
|
-
}
|
|
737
|
-
if (ctx.envConfig.jurisdiction) env.CLOUDFLARE_JURISDICTION = ctx.envConfig.jurisdiction;
|
|
738
|
-
const cmd = ctx.prebuilt ? "alepha build -t cloudflare --prebuilt" : "alepha build -t cloudflare";
|
|
739
|
-
await run({
|
|
740
|
-
name: cmd,
|
|
741
|
-
handler: async () => {
|
|
742
|
-
await this.runShell(cmd, {
|
|
743
|
-
root: appDir,
|
|
744
|
-
env
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
async deploy(ctx, run) {
|
|
750
|
-
this.configureApi(ctx);
|
|
751
|
-
const workerName = ctx.naming.worker();
|
|
752
|
-
const distDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path, "dist") : this.fs.join(ctx.root, "dist");
|
|
753
|
-
let url;
|
|
754
|
-
await run({
|
|
755
|
-
name: `deploy worker ${ctx.app.name}`,
|
|
756
|
-
handler: async () => {
|
|
757
|
-
url = await this.wrangler.deploy(workerName, `${distDir}/wrangler.jsonc`);
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
return url;
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* Vars that are handled by wrangler bindings or build config.
|
|
764
|
-
* These should not be pushed as secrets.
|
|
765
|
-
*/
|
|
766
|
-
static EXCLUDED_SECRET_KEYS = new Set([
|
|
767
|
-
"DATABASE_URL",
|
|
768
|
-
"R2_BUCKET_NAME",
|
|
769
|
-
"CLOUDFLARE_DOMAIN",
|
|
770
|
-
"CLOUDFLARE_ZONE",
|
|
771
|
-
"CLOUDFLARE_JURISDICTION",
|
|
772
|
-
"HYPERDRIVE_ID",
|
|
773
|
-
"POSTGRES_SCHEMA",
|
|
774
|
-
"NODE_ENV"
|
|
775
|
-
]);
|
|
776
|
-
async secrets(ctx, run) {
|
|
777
|
-
this.configureApi(ctx);
|
|
778
|
-
const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
|
|
779
|
-
const secrets = {};
|
|
780
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
781
|
-
if (!value) continue;
|
|
782
|
-
if (CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
|
|
783
|
-
if (key.startsWith("VITE_")) continue;
|
|
784
|
-
secrets[key] = value;
|
|
785
|
-
}
|
|
786
|
-
if (Object.keys(secrets).length === 0) return;
|
|
787
|
-
const hash = computeSecretsHash(secrets);
|
|
788
|
-
for (const _app of ctx.apps) {
|
|
789
|
-
const workerName = ctx.naming.worker();
|
|
790
|
-
await run({
|
|
791
|
-
name: `push secrets to ${workerName} (bulk)`,
|
|
792
|
-
handler: async () => {
|
|
793
|
-
const existingBindings = (await this.api.getWorkerSettings(workerName)).bindings ?? [];
|
|
794
|
-
if (existingBindings.find((b) => b.type === "plain_text" && b.name === CloudflareAdapter.SECRETS_HASH_BINDING)?.text === hash) {
|
|
795
|
-
this.log.info(`Secrets for ${workerName} unchanged (hash ${hash.slice(0, 8)}…), skipping push.`);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const overwriting = new Set(Object.keys(secrets));
|
|
799
|
-
const inherit = existingBindings.filter((b) => !(b.type === "plain_text" && b.name === CloudflareAdapter.SECRETS_HASH_BINDING) && (b.type !== "secret_text" || !overwriting.has(b.name))).map((b) => ({
|
|
800
|
-
type: b.type,
|
|
801
|
-
name: b.name
|
|
802
|
-
}));
|
|
803
|
-
const upsert = Object.entries(secrets).map(([name, text]) => ({
|
|
804
|
-
type: "secret_text",
|
|
805
|
-
name,
|
|
806
|
-
text
|
|
807
|
-
}));
|
|
808
|
-
await this.api.patchWorkerBindings(workerName, [
|
|
809
|
-
...inherit,
|
|
810
|
-
...upsert,
|
|
811
|
-
{
|
|
812
|
-
type: "plain_text",
|
|
813
|
-
name: CloudflareAdapter.SECRETS_HASH_BINDING,
|
|
814
|
-
text: hash
|
|
815
|
-
}
|
|
816
|
-
]);
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* Plain-text binding used to fingerprint the deployed secret set so the
|
|
823
|
-
* next `up` can skip the PATCH when nothing has changed.
|
|
824
|
-
*/
|
|
825
|
-
static SECRETS_HASH_BINDING = "ALEPHA_SECRETS_HASH";
|
|
826
|
-
async provision(ctx, run) {
|
|
827
|
-
this.configureApi(ctx);
|
|
828
|
-
const needsDB = ctx.apps.some((a) => a.resources.hasDatabase);
|
|
829
|
-
const needsBucket = ctx.apps.some((a) => a.resources.hasBucket);
|
|
830
|
-
const postgres = needsDB && await this.isPostgres(ctx);
|
|
831
|
-
const tasks = [];
|
|
832
|
-
if (needsDB) if (postgres) {
|
|
833
|
-
const hdName = ctx.naming.hyperdrive();
|
|
834
|
-
const dbUrl = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL;
|
|
835
|
-
tasks.push({
|
|
836
|
-
name: `provision hyperdrive (${hdName})`,
|
|
837
|
-
handler: async () => {
|
|
838
|
-
this.provisionedHyperdriveId = await this.ensureHyperdrive(hdName, dbUrl);
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
} else {
|
|
842
|
-
const dbName = ctx.naming.d1();
|
|
843
|
-
tasks.push({
|
|
844
|
-
name: `provision d1 (${dbName})`,
|
|
845
|
-
handler: async () => {
|
|
846
|
-
this.provisionedD1Id = await this.ensureD1(dbName);
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
if (needsBucket) {
|
|
851
|
-
const bucketName = ctx.naming.r2();
|
|
852
|
-
tasks.push({
|
|
853
|
-
name: `provision r2 (${bucketName})`,
|
|
854
|
-
handler: async () => {
|
|
855
|
-
await this.ensureR2(bucketName);
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
for (const app of ctx.apps) {
|
|
860
|
-
if (app.resources.hasKV) {
|
|
861
|
-
const kvName = ctx.naming.kv();
|
|
862
|
-
tasks.push({
|
|
863
|
-
name: `provision kv (${kvName})`,
|
|
864
|
-
handler: async () => {
|
|
865
|
-
this.provisionedKVIds.set(kvName, await this.ensureKV(kvName));
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
if (app.resources.hasQueue) {
|
|
870
|
-
const queueName = ctx.naming.queue();
|
|
871
|
-
tasks.push({
|
|
872
|
-
name: `provision queue (${queueName})`,
|
|
873
|
-
handler: async () => {
|
|
874
|
-
await this.ensureQueue(queueName);
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
await run(tasks);
|
|
880
|
-
}
|
|
881
|
-
async migrate(ctx, run) {
|
|
882
|
-
this.configureApi(ctx);
|
|
883
|
-
if (!ctx.apps.some((a) => a.resources.hasDatabase)) return;
|
|
884
|
-
if (await this.isPostgres(ctx)) await this.migratePostgres(ctx, run);
|
|
885
|
-
else await this.migrateD1(ctx, run);
|
|
886
|
-
}
|
|
887
|
-
async migrateD1(ctx, run) {
|
|
888
|
-
const dbName = ctx.naming.d1();
|
|
889
|
-
await run({
|
|
890
|
-
name: "migrate d1",
|
|
891
|
-
handler: async () => {
|
|
892
|
-
const migrationsDir = this.fs.join(ctx.root, "migrations", "sqlite");
|
|
893
|
-
const env = { DATABASE_URL: this.provisionedD1Id ? `d1://${dbName}:${this.provisionedD1Id}` : `d1://${dbName}` };
|
|
894
|
-
if (await this.fs.exists(migrationsDir)) await this.runShell(`alepha db migrations check --mode ${ctx.env}`, {
|
|
895
|
-
resolve: true,
|
|
896
|
-
env
|
|
897
|
-
});
|
|
898
|
-
else await this.runShell(`alepha db migrations create --mode ${ctx.env}`, {
|
|
899
|
-
resolve: true,
|
|
900
|
-
env
|
|
901
|
-
});
|
|
902
|
-
const distMigrations = this.fs.join(ctx.root, "dist", "migrations");
|
|
903
|
-
await this.fs.cp(migrationsDir, distMigrations);
|
|
904
|
-
await this.wrangler.d1MigrationsApply(dbName, "dist/wrangler.jsonc");
|
|
905
|
-
await this.fs.rm(distMigrations, { recursive: true });
|
|
906
|
-
}
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
async migratePostgres(ctx, run) {
|
|
910
|
-
await run({
|
|
911
|
-
name: "migrate postgres",
|
|
912
|
-
handler: async () => {
|
|
913
|
-
const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
|
|
914
|
-
const env = { DATABASE_URL: envVars.DATABASE_URL ?? process.env.DATABASE_URL };
|
|
915
|
-
if (envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA) env.POSTGRES_SCHEMA = envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
|
|
916
|
-
await this.runShell(`alepha db migrations apply --mode ${ctx.env}`, {
|
|
917
|
-
resolve: true,
|
|
918
|
-
env
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
async inspect(ctx, run) {
|
|
924
|
-
this.configureApi(ctx);
|
|
925
|
-
const state = {
|
|
926
|
-
workers: [],
|
|
927
|
-
databases: [],
|
|
928
|
-
buckets: [],
|
|
929
|
-
kvNamespaces: [],
|
|
930
|
-
queues: [],
|
|
931
|
-
secrets: []
|
|
932
|
-
};
|
|
933
|
-
const tasks = [];
|
|
934
|
-
for (const _app of ctx.apps) {
|
|
935
|
-
const name = ctx.naming.worker();
|
|
936
|
-
tasks.push({
|
|
937
|
-
name: `inspect worker (${name})`,
|
|
938
|
-
handler: async () => {
|
|
939
|
-
try {
|
|
940
|
-
const deployment = await this.getActiveDeployment(name);
|
|
941
|
-
if (deployment) state.workers.push({
|
|
942
|
-
name,
|
|
943
|
-
exists: true,
|
|
944
|
-
version: deployment.versionId,
|
|
945
|
-
tag: deployment.tag,
|
|
946
|
-
createdAt: deployment.createdAt
|
|
947
|
-
});
|
|
948
|
-
else state.workers.push({
|
|
949
|
-
name,
|
|
950
|
-
exists: false
|
|
951
|
-
});
|
|
952
|
-
} catch {
|
|
953
|
-
state.workers.push({
|
|
954
|
-
name,
|
|
955
|
-
exists: false
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
if (ctx.apps.some((a) => a.resources.hasDatabase)) if (await this.isPostgres(ctx)) {
|
|
962
|
-
const hdName = ctx.naming.hyperdrive();
|
|
963
|
-
tasks.push({
|
|
964
|
-
name: `inspect hyperdrive (${hdName})`,
|
|
965
|
-
handler: async () => {
|
|
966
|
-
const existing = (await this.api.listHyperdrive()).find((c) => c.name === hdName);
|
|
967
|
-
state.databases.push({
|
|
968
|
-
name: hdName,
|
|
969
|
-
exists: !!existing,
|
|
970
|
-
id: existing?.id,
|
|
971
|
-
detail: existing?.origin.host
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
});
|
|
975
|
-
} else {
|
|
976
|
-
const dbName = ctx.naming.d1();
|
|
977
|
-
tasks.push({
|
|
978
|
-
name: `inspect d1 (${dbName})`,
|
|
979
|
-
handler: async () => {
|
|
980
|
-
const existing = (await this.api.listD1()).find((db) => db.name === dbName);
|
|
981
|
-
state.databases.push({
|
|
982
|
-
name: dbName,
|
|
983
|
-
exists: !!existing,
|
|
984
|
-
id: existing?.uuid
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
if (ctx.apps.some((a) => a.resources.hasBucket)) {
|
|
990
|
-
const bucketName = ctx.naming.r2();
|
|
991
|
-
tasks.push({
|
|
992
|
-
name: `inspect r2 (${bucketName})`,
|
|
993
|
-
handler: async () => {
|
|
994
|
-
const existing = (await this.api.listR2()).find((b) => b.name === bucketName);
|
|
995
|
-
state.buckets.push({
|
|
996
|
-
name: bucketName,
|
|
997
|
-
exists: !!existing,
|
|
998
|
-
id: existing?.creation_date
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
for (const app of ctx.apps) if (app.resources.hasKV) {
|
|
1004
|
-
const kvName = ctx.naming.kv();
|
|
1005
|
-
tasks.push({
|
|
1006
|
-
name: `inspect kv (${kvName})`,
|
|
1007
|
-
handler: async () => {
|
|
1008
|
-
const existing = (await this.api.listKV()).find((ns) => ns.title === kvName);
|
|
1009
|
-
state.kvNamespaces.push({
|
|
1010
|
-
name: kvName,
|
|
1011
|
-
exists: !!existing,
|
|
1012
|
-
id: existing?.id
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
for (const app of ctx.apps) if (app.resources.hasQueue) {
|
|
1018
|
-
const queueName = ctx.naming.queue();
|
|
1019
|
-
tasks.push({
|
|
1020
|
-
name: `inspect queue (${queueName})`,
|
|
1021
|
-
handler: async () => {
|
|
1022
|
-
const existing = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
|
|
1023
|
-
state.queues.push({
|
|
1024
|
-
name: queueName,
|
|
1025
|
-
exists: !!existing,
|
|
1026
|
-
id: existing?.queue_id
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
|
|
1032
|
-
const expectedSecrets = Object.keys(envVars).filter((key) => envVars[key] && !CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
|
|
1033
|
-
if (expectedSecrets.length > 0) {
|
|
1034
|
-
const workerName = ctx.naming.worker();
|
|
1035
|
-
tasks.push({
|
|
1036
|
-
name: "inspect secrets",
|
|
1037
|
-
handler: async () => {
|
|
1038
|
-
try {
|
|
1039
|
-
const deployed = await this.api.listSecrets(workerName);
|
|
1040
|
-
const deployedNames = new Set(deployed.map((s) => s.name));
|
|
1041
|
-
for (const key of expectedSecrets) state.secrets.push({
|
|
1042
|
-
name: key,
|
|
1043
|
-
deployed: deployedNames.has(key)
|
|
1044
|
-
});
|
|
1045
|
-
} catch {
|
|
1046
|
-
for (const key of expectedSecrets) state.secrets.push({
|
|
1047
|
-
name: key,
|
|
1048
|
-
deployed: false
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
await run(tasks);
|
|
1055
|
-
return state;
|
|
1056
|
-
}
|
|
1057
|
-
async teardown(ctx, run) {
|
|
1058
|
-
this.configureApi(ctx);
|
|
1059
|
-
for (const app of ctx.apps) if (app.resources.hasQueue) {
|
|
1060
|
-
const workerName = ctx.naming.worker();
|
|
1061
|
-
const queueName = ctx.naming.queue();
|
|
1062
|
-
await run({
|
|
1063
|
-
name: `unbind queue consumer ${queueName}`,
|
|
1064
|
-
handler: async () => {
|
|
1065
|
-
try {
|
|
1066
|
-
const queue = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
|
|
1067
|
-
if (queue) await this.api.deleteQueueConsumer(queue.queue_id, workerName);
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
this.log.warn(`Failed to unbind queue consumer: ${String(error.message || "")}`);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
for (const _app of ctx.apps) {
|
|
1075
|
-
const name = ctx.naming.worker();
|
|
1076
|
-
await run({
|
|
1077
|
-
name: `delete worker ${name}`,
|
|
1078
|
-
handler: async () => {
|
|
1079
|
-
try {
|
|
1080
|
-
await this.api.deleteWorker(name);
|
|
1081
|
-
} catch (error) {
|
|
1082
|
-
this.log.warn(`Failed to delete worker ${name}: ${String(error.message || "")}`);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
for (const app of ctx.apps) if (app.resources.hasQueue) {
|
|
1088
|
-
const name = ctx.naming.queue();
|
|
1089
|
-
await run({
|
|
1090
|
-
name: `delete queue ${name}`,
|
|
1091
|
-
handler: async () => {
|
|
1092
|
-
try {
|
|
1093
|
-
const queue = (await this.api.listQueues()).find((q) => q.queue_name === name);
|
|
1094
|
-
if (!queue) {
|
|
1095
|
-
this.log.debug(`Queue ${name} not found — skipping.`);
|
|
1096
|
-
return;
|
|
1097
|
-
}
|
|
1098
|
-
await this.api.deleteQueue(queue.queue_id);
|
|
1099
|
-
} catch (error) {
|
|
1100
|
-
this.log.warn(`Failed to delete queue ${name}: ${String(error.message || "")}`);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
for (const app of ctx.apps) if (app.resources.hasKV) {
|
|
1106
|
-
const name = ctx.naming.kv();
|
|
1107
|
-
await run({
|
|
1108
|
-
name: `delete kv ${name}`,
|
|
1109
|
-
handler: async () => {
|
|
1110
|
-
try {
|
|
1111
|
-
const existing = (await this.api.listKV()).find((ns) => ns.title === name);
|
|
1112
|
-
if (!existing) {
|
|
1113
|
-
this.log.debug(`KV namespace ${name} not found — skipping.`);
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
await this.api.deleteKV(existing.id);
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
this.log.warn(`Failed to delete kv ${name}: ${String(error.message || "")}`);
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
if (ctx.apps.some((a) => a.resources.hasBucket)) {
|
|
1124
|
-
const name = ctx.naming.r2();
|
|
1125
|
-
await run({
|
|
1126
|
-
name: `delete r2 ${name}`,
|
|
1127
|
-
handler: async () => {
|
|
1128
|
-
try {
|
|
1129
|
-
await this.wipeR2Bucket(name, ctx);
|
|
1130
|
-
await this.api.deleteR2(name);
|
|
1131
|
-
} catch (error) {
|
|
1132
|
-
const msg = String(error.message || "");
|
|
1133
|
-
if (msg.includes("does not exist") || msg.includes("NoSuchBucket")) this.log.debug(`Bucket ${name} not found — skipping.`);
|
|
1134
|
-
else this.log.warn(`Failed to delete r2 ${name}: ${msg}`);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
if (ctx.apps.some((a) => a.resources.hasDatabase)) if (await this.isPostgres(ctx)) {
|
|
1140
|
-
const name = ctx.naming.hyperdrive();
|
|
1141
|
-
await run({
|
|
1142
|
-
name: `delete hyperdrive ${name}`,
|
|
1143
|
-
handler: async () => {
|
|
1144
|
-
try {
|
|
1145
|
-
const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
|
|
1146
|
-
if (!existing) {
|
|
1147
|
-
this.log.debug(`Hyperdrive ${name} not found — skipping.`);
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
await this.api.deleteHyperdrive(existing.id);
|
|
1151
|
-
} catch (error) {
|
|
1152
|
-
this.log.warn(`Failed to delete hyperdrive ${name}: ${String(error.message || "")}`);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
} else {
|
|
1157
|
-
const name = ctx.naming.d1();
|
|
1158
|
-
await run({
|
|
1159
|
-
name: `delete d1 ${name}`,
|
|
1160
|
-
handler: async () => {
|
|
1161
|
-
try {
|
|
1162
|
-
const existing = (await this.api.listD1()).find((db) => db.name === name);
|
|
1163
|
-
if (!existing) {
|
|
1164
|
-
this.log.debug(`D1 database ${name} not found — skipping.`);
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
await this.api.deleteD1(existing.uuid);
|
|
1168
|
-
} catch (error) {
|
|
1169
|
-
this.log.warn(`Failed to delete d1 ${name}: ${String(error.message || "")}`);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
async ensureD1(name) {
|
|
1176
|
-
const existing = (await this.api.listD1()).find((db) => db.name === name);
|
|
1177
|
-
if (existing) return existing.uuid;
|
|
1178
|
-
return (await this.api.createD1(name)).uuid;
|
|
1179
|
-
}
|
|
1180
|
-
async ensureHyperdrive(name, connectionString) {
|
|
1181
|
-
const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
|
|
1182
|
-
if (existing) return existing.id;
|
|
1183
|
-
return (await this.api.createHyperdrive(name, connectionString)).id;
|
|
1184
|
-
}
|
|
1185
|
-
async ensureR2(name) {
|
|
1186
|
-
if ((await this.api.listR2()).find((b) => b.name === name)) return;
|
|
1187
|
-
await this.api.createR2(name);
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Empty an R2 bucket via the S3-compatible API.
|
|
1191
|
-
*
|
|
1192
|
-
* Cloudflare's REST `DELETE /r2/buckets/:name` rejects non-empty buckets
|
|
1193
|
-
* with `BucketNotEmpty`, and the REST API has no object-level endpoints —
|
|
1194
|
-
* objects must be listed and deleted over the S3 protocol. To avoid
|
|
1195
|
-
* making users pre-create R2 access keys, we mint a short-lived
|
|
1196
|
-
* bucket-scoped API token using the wrangler bearer token, wipe the
|
|
1197
|
-
* bucket with `s3mini`, then revoke the token.
|
|
1198
|
-
*
|
|
1199
|
-
* Also aborts any pending multipart uploads — those count as bucket
|
|
1200
|
-
* contents from R2's perspective and would otherwise block the delete.
|
|
1201
|
-
*/
|
|
1202
|
-
async wipeR2Bucket(bucketName, ctx) {
|
|
1203
|
-
const tokenName = `alepha-teardown-${bucketName}-${Date.now()}`;
|
|
1204
|
-
const token = await this.api.createR2Token(tokenName, bucketName);
|
|
1205
|
-
try {
|
|
1206
|
-
const accountId = await this.api.resolveAccountId();
|
|
1207
|
-
const jur = ctx.envConfig.jurisdiction;
|
|
1208
|
-
const host = jur ? `${accountId}.${jur}.r2.cloudflarestorage.com` : `${accountId}.r2.cloudflarestorage.com`;
|
|
1209
|
-
const client = new S3mini({
|
|
1210
|
-
accessKeyId: token.accessKeyId,
|
|
1211
|
-
secretAccessKey: token.secretAccessKey,
|
|
1212
|
-
region: "auto",
|
|
1213
|
-
endpoint: `https://${host}/${bucketName}`
|
|
1214
|
-
});
|
|
1215
|
-
try {
|
|
1216
|
-
const mp = await client.listMultipartUploads();
|
|
1217
|
-
if ("listMultipartUploadsResult" in mp) {
|
|
1218
|
-
const uploads = mp.listMultipartUploadsResult.uploads ?? [];
|
|
1219
|
-
for (const upload of uploads) {
|
|
1220
|
-
const u = upload;
|
|
1221
|
-
const key = u.Key ?? u.key;
|
|
1222
|
-
const uploadId = u.UploadId ?? u.uploadId;
|
|
1223
|
-
if (key && uploadId) await client.abortMultipartUpload(key, uploadId);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
this.log.debug(`listMultipartUploads on ${bucketName} failed: ${String(error.message || "")}`);
|
|
1228
|
-
}
|
|
1229
|
-
let cursor;
|
|
1230
|
-
let total = 0;
|
|
1231
|
-
while (true) {
|
|
1232
|
-
const page = await client.listObjectsPaged(void 0, void 0, 1e3, cursor);
|
|
1233
|
-
const objects = page?.objects ?? [];
|
|
1234
|
-
if (objects.length === 0) break;
|
|
1235
|
-
await client.deleteObjects(objects.map((o) => o.Key));
|
|
1236
|
-
total += objects.length;
|
|
1237
|
-
cursor = page?.nextContinuationToken;
|
|
1238
|
-
if (!cursor) break;
|
|
1239
|
-
}
|
|
1240
|
-
if (total > 0) this.log.info(`Emptied ${total} object(s) from bucket ${bucketName}.`);
|
|
1241
|
-
} finally {
|
|
1242
|
-
try {
|
|
1243
|
-
await this.api.deleteR2Token(token.id);
|
|
1244
|
-
} catch (error) {
|
|
1245
|
-
this.log.warn(`Failed to revoke ephemeral R2 token ${token.id}: ${String(error.message || "")}`);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async ensureKV(name) {
|
|
1250
|
-
const existing = (await this.api.listKV()).find((ns) => ns.title === name);
|
|
1251
|
-
if (existing) return existing.id;
|
|
1252
|
-
return (await this.api.createKV(name)).id;
|
|
1253
|
-
}
|
|
1254
|
-
async ensureQueue(name) {
|
|
1255
|
-
if ((await this.api.listQueues()).find((q) => q.queue_name === name)) return;
|
|
1256
|
-
await this.api.createQueue(name);
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Get the currently active deployment for a worker.
|
|
1260
|
-
*/
|
|
1261
|
-
async getActiveDeployment(workerName) {
|
|
1262
|
-
const latest = [...await this.api.listDeployments(workerName)].sort((a, b) => b.created_on.localeCompare(a.created_on))[0];
|
|
1263
|
-
if (!latest?.versions?.[0]) return;
|
|
1264
|
-
const activeVersionId = latest.versions[0].version_id;
|
|
1265
|
-
const version = (await this.api.listVersions(workerName)).find((v) => v.id === activeVersionId);
|
|
1266
|
-
return {
|
|
1267
|
-
versionId: activeVersionId,
|
|
1268
|
-
tag: version?.annotations?.["workers/tag"],
|
|
1269
|
-
createdAt: version?.metadata.created_on
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
/**
|
|
1274
|
-
* Stable SHA-256 of the secret set. Keys are sorted so reordering `.env`
|
|
1275
|
-
* lines does not invalidate the cache. Used as a fingerprint by
|
|
1276
|
-
* `CloudflareAdapter.secrets` — see the comment block there.
|
|
1277
|
-
*/
|
|
1278
|
-
function computeSecretsHash(secrets) {
|
|
1279
|
-
const sorted = Object.keys(secrets).sort().map((k) => `${k}=${secrets[k]}`).join("\n");
|
|
1280
|
-
return createHash("sha256").update(sorted).digest("hex");
|
|
1281
|
-
}
|
|
1282
|
-
//#endregion
|
|
1283
|
-
//#region ../../src/cli/platform/schemas/vercel.ts
|
|
1284
|
-
const vercelProjectSchema = t.object({
|
|
1285
|
-
id: t.string(),
|
|
1286
|
-
name: t.string(),
|
|
1287
|
-
accountId: t.string()
|
|
1288
|
-
});
|
|
1289
|
-
const createProjectBodySchema = t.object({
|
|
1290
|
-
name: t.string(),
|
|
1291
|
-
framework: t.optional(t.null())
|
|
1292
|
-
});
|
|
1293
|
-
const vercelDeploymentSchema = t.object({
|
|
1294
|
-
uid: t.string(),
|
|
1295
|
-
name: t.string(),
|
|
1296
|
-
url: t.string(),
|
|
1297
|
-
state: t.optional(t.string()),
|
|
1298
|
-
readyState: t.optional(t.string()),
|
|
1299
|
-
created: t.optional(t.number()),
|
|
1300
|
-
target: t.optional(t.string()),
|
|
1301
|
-
alias: t.optional(t.array(t.string()))
|
|
1302
|
-
});
|
|
1303
|
-
const vercelEnvVarSchema = t.object({
|
|
1304
|
-
id: t.string(),
|
|
1305
|
-
key: t.string(),
|
|
1306
|
-
value: t.optional(t.string()),
|
|
1307
|
-
type: t.string(),
|
|
1308
|
-
target: t.array(t.string())
|
|
1309
|
-
});
|
|
1310
|
-
const createEnvVarBodySchema = t.object({
|
|
1311
|
-
key: t.string(),
|
|
1312
|
-
value: t.string(),
|
|
1313
|
-
type: t.string(),
|
|
1314
|
-
target: t.array(t.string())
|
|
1315
|
-
});
|
|
1316
|
-
//#endregion
|
|
1317
|
-
//#region ../../src/cli/platform/services/VercelCli.ts
|
|
1318
|
-
/**
|
|
1319
|
-
* Wraps Vercel CLI commands and token management.
|
|
1320
|
-
*
|
|
1321
|
-
* Used for operations where the Vercel CLI provides value:
|
|
1322
|
-
* OAuth login, prebuilt deploy, and auth token extraction.
|
|
1323
|
-
*/
|
|
1324
|
-
var VercelCli = class {
|
|
1325
|
-
log = $logger();
|
|
1326
|
-
shell = $inject(ShellProvider);
|
|
1327
|
-
fs = $inject(FileSystemProvider);
|
|
1328
|
-
utils = $inject(AlephaCliUtils);
|
|
1329
|
-
pm = $inject(PackageManagerUtils);
|
|
1330
|
-
runner = $inject(Runner);
|
|
1331
|
-
async runShell(command, options = {}) {
|
|
1332
|
-
const capture = options.capture;
|
|
1333
|
-
const output = await this.shell.run(command, {
|
|
1334
|
-
...options,
|
|
1335
|
-
capture: capture ?? this.runner.useDynamicLogger
|
|
1336
|
-
});
|
|
1337
|
-
if (capture && !this.runner.useDynamicLogger) this.log.info(output);
|
|
1338
|
-
return output;
|
|
1339
|
-
}
|
|
1340
|
-
/**
|
|
1341
|
-
* Ensure vercel CLI is installed in the project.
|
|
1342
|
-
*/
|
|
1343
|
-
async ensureInstalled(root, run) {
|
|
1344
|
-
await this.pm.ensureDependency(root, "vercel", {
|
|
1345
|
-
dev: true,
|
|
1346
|
-
exec: async (cmd, opts) => {
|
|
1347
|
-
run.pause();
|
|
1348
|
-
try {
|
|
1349
|
-
await this.utils.exec(cmd, opts);
|
|
1350
|
-
} finally {
|
|
1351
|
-
run.resume();
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
});
|
|
1355
|
-
}
|
|
1356
|
-
/**
|
|
1357
|
-
* Get the Vercel auth token.
|
|
1358
|
-
*
|
|
1359
|
-
* Priority:
|
|
1360
|
-
* 1. VERCEL_TOKEN environment variable (CI/CD)
|
|
1361
|
-
* 2. Vercel CLI auth.json file (local dev)
|
|
1362
|
-
*/
|
|
1363
|
-
async getAuthToken() {
|
|
1364
|
-
const envToken = process.env.VERCEL_TOKEN;
|
|
1365
|
-
if (envToken) return envToken;
|
|
1366
|
-
const authPath = this.getAuthFilePath();
|
|
1367
|
-
if (!await this.fs.exists(authPath)) throw new AlephaError("Vercel auth token not found. Run `vercel login` or set VERCEL_TOKEN.");
|
|
1368
|
-
const content = await this.fs.readFile(authPath);
|
|
1369
|
-
const parsed = JSON.parse(content.toString());
|
|
1370
|
-
if (!parsed.token) throw new AlephaError("Vercel auth.json exists but contains no token. Run `vercel login`.");
|
|
1371
|
-
return parsed.token;
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Validate the current auth token.
|
|
1375
|
-
*/
|
|
1376
|
-
async whoami() {
|
|
1377
|
-
return await this.runShell("vercel whoami", {
|
|
1378
|
-
resolve: true,
|
|
1379
|
-
capture: true
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
/**
|
|
1383
|
-
* Open the browser-based login flow.
|
|
1384
|
-
*/
|
|
1385
|
-
async login() {
|
|
1386
|
-
await this.runShell("vercel login", { resolve: true });
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Deploy a prebuilt .vercel/output/ directory.
|
|
1390
|
-
*
|
|
1391
|
-
* Returns the deployment URL.
|
|
1392
|
-
*/
|
|
1393
|
-
async deploy(distDir, options) {
|
|
1394
|
-
const args = [
|
|
1395
|
-
"vercel",
|
|
1396
|
-
"deploy",
|
|
1397
|
-
"--prebuilt"
|
|
1398
|
-
];
|
|
1399
|
-
if (options.prod) args.push("--prod");
|
|
1400
|
-
if (options.token) args.push(`--token=${options.token}`);
|
|
1401
|
-
return (await this.runShell(args.join(" "), {
|
|
1402
|
-
resolve: true,
|
|
1403
|
-
capture: true,
|
|
1404
|
-
root: distDir
|
|
1405
|
-
})).trim().split("\n").reverse().find((line) => line.trim().startsWith("https://"))?.trim();
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Resolve the path to Vercel CLI auth.json.
|
|
1409
|
-
*/
|
|
1410
|
-
getAuthFilePath() {
|
|
1411
|
-
const os = platform$1();
|
|
1412
|
-
if (os === "darwin") return join(homedir(), "Library", "Application Support", "com.vercel.cli", "auth.json");
|
|
1413
|
-
if (os === "win32") return join(homedir(), "AppData", "Roaming", "xdg.data", "com.vercel.cli", "auth.json");
|
|
1414
|
-
return join(homedir(), ".local", "share", "com.vercel.cli", "auth.json");
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
|
-
//#endregion
|
|
1418
|
-
//#region ../../src/cli/platform/services/VercelApi.ts
|
|
1419
|
-
/**
|
|
1420
|
-
* Thin wrapper over the Vercel REST API.
|
|
1421
|
-
*
|
|
1422
|
-
* Uses the auth token from VercelCli for all requests.
|
|
1423
|
-
*/
|
|
1424
|
-
var VercelApi = class VercelApi {
|
|
1425
|
-
static BASE = "https://api.vercel.com";
|
|
1426
|
-
log = $logger();
|
|
1427
|
-
alepha = $inject(Alepha);
|
|
1428
|
-
vercelCli = $inject(VercelCli);
|
|
1429
|
-
token;
|
|
1430
|
-
/**
|
|
1431
|
-
* Obtain the current auth token from the Vercel CLI.
|
|
1432
|
-
*/
|
|
1433
|
-
async resolveToken() {
|
|
1434
|
-
if (this.token) return this.token;
|
|
1435
|
-
this.token = await this.vercelCli.getAuthToken();
|
|
1436
|
-
return this.token;
|
|
1437
|
-
}
|
|
1438
|
-
async listProjects() {
|
|
1439
|
-
return (await this.fetch("/v10/projects", { schema: t.object({ projects: t.array(vercelProjectSchema) }) })).projects;
|
|
1440
|
-
}
|
|
1441
|
-
async getProject(nameOrId) {
|
|
1442
|
-
try {
|
|
1443
|
-
return await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { schema: vercelProjectSchema });
|
|
1444
|
-
} catch {
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
async createProject(name) {
|
|
1449
|
-
return await this.fetch("/v11/projects", {
|
|
1450
|
-
method: "POST",
|
|
1451
|
-
body: {
|
|
1452
|
-
name,
|
|
1453
|
-
framework: null
|
|
1454
|
-
},
|
|
1455
|
-
bodySchema: createProjectBodySchema,
|
|
1456
|
-
schema: vercelProjectSchema
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
async updateProject(nameOrId, settings) {
|
|
1460
|
-
await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, {
|
|
1461
|
-
method: "PATCH",
|
|
1462
|
-
body: settings
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
async deleteProject(nameOrId) {
|
|
1466
|
-
await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { method: "DELETE" });
|
|
1467
|
-
}
|
|
1468
|
-
async listDeployments(projectId, options) {
|
|
1469
|
-
const query = { projectId };
|
|
1470
|
-
if (options?.limit) query.limit = String(options.limit);
|
|
1471
|
-
if (options?.target) query.target = options.target;
|
|
1472
|
-
return (await this.fetch("/v6/deployments", {
|
|
1473
|
-
query,
|
|
1474
|
-
schema: t.object({ deployments: t.array(vercelDeploymentSchema) })
|
|
1475
|
-
})).deployments;
|
|
1476
|
-
}
|
|
1477
|
-
async listEnvVars(projectId) {
|
|
1478
|
-
return (await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
|
|
1479
|
-
query: { decrypt: "true" },
|
|
1480
|
-
schema: t.object({ envs: t.array(vercelEnvVarSchema) })
|
|
1481
|
-
})).envs;
|
|
1482
|
-
}
|
|
1483
|
-
async upsertEnvVars(projectId, vars) {
|
|
1484
|
-
for (const v of vars) await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
|
|
1485
|
-
method: "POST",
|
|
1486
|
-
query: { upsert: "true" },
|
|
1487
|
-
body: {
|
|
1488
|
-
key: v.key,
|
|
1489
|
-
value: v.value,
|
|
1490
|
-
type: "encrypted",
|
|
1491
|
-
target: v.target
|
|
1492
|
-
},
|
|
1493
|
-
bodySchema: createEnvVarBodySchema
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
async deleteEnvVar(projectId, envVarId) {
|
|
1497
|
-
await this.fetch(`/v9/projects/${encodeURIComponent(projectId)}/env/${envVarId}`, { method: "DELETE" });
|
|
1498
|
-
}
|
|
1499
|
-
async fetch(path, options = {}) {
|
|
1500
|
-
const token = await this.resolveToken();
|
|
1501
|
-
const { method = "GET", body, query } = options;
|
|
1502
|
-
let url = `${VercelApi.BASE}${path}`;
|
|
1503
|
-
if (query) {
|
|
1504
|
-
const params = new URLSearchParams(query);
|
|
1505
|
-
url += `?${params.toString()}`;
|
|
1506
|
-
}
|
|
1507
|
-
const headers = { Authorization: `Bearer ${token}` };
|
|
1508
|
-
const init = {
|
|
1509
|
-
method,
|
|
1510
|
-
headers
|
|
1511
|
-
};
|
|
1512
|
-
if (body) {
|
|
1513
|
-
headers["Content-Type"] = "application/json";
|
|
1514
|
-
const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
|
|
1515
|
-
init.body = JSON.stringify(validated);
|
|
1516
|
-
}
|
|
1517
|
-
const response = await globalThis.fetch(url, init);
|
|
1518
|
-
if (response.status === 204) return;
|
|
1519
|
-
const json = await response.json();
|
|
1520
|
-
if (json.error) throw new AlephaError(`Vercel API error (${method} ${path}): ${json.error.message ?? JSON.stringify(json.error)}`);
|
|
1521
|
-
if (!response.ok) throw new AlephaError(`Vercel API error (${method} ${path}): HTTP ${response.status}`);
|
|
1522
|
-
if (options.schema) return this.alepha.codec.validate(options.schema, json);
|
|
1523
|
-
return json;
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
|
-
//#endregion
|
|
1527
|
-
//#region ../../src/cli/platform/adapters/VercelAdapter.ts
|
|
1528
|
-
/**
|
|
1529
|
-
* Vercel platform adapter.
|
|
1530
|
-
*
|
|
1531
|
-
* Uses the Vercel CLI for login and deploy (--prebuilt),
|
|
1532
|
-
* and the Vercel REST API for project management, env vars, and inspection.
|
|
1533
|
-
*
|
|
1534
|
-
* v1 scope: deploy pipeline only. No DB/storage/KV provisioning.
|
|
1535
|
-
*/
|
|
1536
|
-
var VercelAdapter = class VercelAdapter extends PlatformAdapter {
|
|
1537
|
-
log = $logger();
|
|
1538
|
-
fs = $inject(FileSystemProvider);
|
|
1539
|
-
shell = $inject(ShellProvider);
|
|
1540
|
-
utils = $inject(AlephaCliUtils);
|
|
1541
|
-
cache = $inject(PlatformCacheProvider);
|
|
1542
|
-
alepha = $inject(Alepha);
|
|
1543
|
-
envUtils = $inject(EnvUtils);
|
|
1544
|
-
api = $inject(VercelApi);
|
|
1545
|
-
vercelCli = $inject(VercelCli);
|
|
1546
|
-
runner = $inject(Runner);
|
|
1547
|
-
/**
|
|
1548
|
-
* Vars that should not be pushed as env vars.
|
|
1549
|
-
* These are either handled by the build or are internal.
|
|
1550
|
-
*/
|
|
1551
|
-
static EXCLUDED_SECRET_KEYS = new Set(["NODE_ENV"]);
|
|
1552
|
-
async runShell(command, options = {}) {
|
|
1553
|
-
const capture = options.capture;
|
|
1554
|
-
const output = await this.shell.run(command, {
|
|
1555
|
-
...options,
|
|
1556
|
-
capture: capture ?? this.runner.useDynamicLogger
|
|
1557
|
-
});
|
|
1558
|
-
if (capture && !this.runner.useDynamicLogger) this.log.info(output);
|
|
1559
|
-
return output;
|
|
1560
|
-
}
|
|
1561
|
-
async authenticate(ctx, run) {
|
|
1562
|
-
await run({
|
|
1563
|
-
name: "authenticate",
|
|
1564
|
-
handler: async () => {
|
|
1565
|
-
await this.vercelCli.ensureInstalled(ctx.root, run);
|
|
1566
|
-
let needsLogin = false;
|
|
1567
|
-
try {
|
|
1568
|
-
await this.vercelCli.getAuthToken();
|
|
1569
|
-
await this.vercelCli.whoami();
|
|
1570
|
-
} catch {
|
|
1571
|
-
needsLogin = true;
|
|
1572
|
-
}
|
|
1573
|
-
if (needsLogin) {
|
|
1574
|
-
run.pause();
|
|
1575
|
-
await this.vercelCli.login();
|
|
1576
|
-
run.resume();
|
|
1577
|
-
}
|
|
1578
|
-
if (await this.cache.isLoginFresh(ctx.root, "vercel")) return;
|
|
1579
|
-
await this.cache.recordLogin(ctx.root, "vercel");
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
async build(ctx, run) {
|
|
1584
|
-
const appDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path) : ctx.root;
|
|
1585
|
-
await run({
|
|
1586
|
-
name: "alepha build -t vercel",
|
|
1587
|
-
handler: async () => {
|
|
1588
|
-
await this.runShell("alepha build -t vercel", { root: appDir });
|
|
1589
|
-
}
|
|
1590
|
-
});
|
|
1591
|
-
}
|
|
1592
|
-
async deploy(ctx, run) {
|
|
1593
|
-
const distDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path, "dist") : this.fs.join(ctx.root, "dist");
|
|
1594
|
-
const projectName = ctx.naming.worker();
|
|
1595
|
-
let url;
|
|
1596
|
-
await run({
|
|
1597
|
-
name: `deploy ${ctx.app.name}`,
|
|
1598
|
-
handler: async () => {
|
|
1599
|
-
let project = await this.api.getProject(projectName);
|
|
1600
|
-
if (!project) project = await this.api.createProject(projectName);
|
|
1601
|
-
await this.api.updateProject(projectName, { framework: null });
|
|
1602
|
-
const vercelDir = this.fs.join(distDir, ".vercel");
|
|
1603
|
-
await this.fs.mkdir(vercelDir);
|
|
1604
|
-
await this.fs.writeFile(this.fs.join(vercelDir, "project.json"), JSON.stringify({
|
|
1605
|
-
projectId: project.id,
|
|
1606
|
-
orgId: project.accountId
|
|
1607
|
-
}, null, 2));
|
|
1608
|
-
const token = process.env.VERCEL_TOKEN;
|
|
1609
|
-
await this.vercelCli.deploy(distDir, {
|
|
1610
|
-
prod: true,
|
|
1611
|
-
token
|
|
1612
|
-
});
|
|
1613
|
-
const latest = (await this.api.listDeployments(project.id, {
|
|
1614
|
-
limit: 1,
|
|
1615
|
-
target: "production"
|
|
1616
|
-
}))[0];
|
|
1617
|
-
url = latest?.alias?.[0] ? `https://${latest.alias[0]}` : `https://${projectName}.vercel.app`;
|
|
1618
|
-
}
|
|
1619
|
-
});
|
|
1620
|
-
return url;
|
|
1621
|
-
}
|
|
1622
|
-
async secrets(ctx, run) {
|
|
1623
|
-
const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
|
|
1624
|
-
const vars = [];
|
|
1625
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
1626
|
-
if (!value) continue;
|
|
1627
|
-
if (VercelAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
|
|
1628
|
-
if (key.startsWith("VITE_")) continue;
|
|
1629
|
-
vars.push({
|
|
1630
|
-
key,
|
|
1631
|
-
value,
|
|
1632
|
-
target: ["production", "preview"]
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
if (vars.length === 0) return;
|
|
1636
|
-
for (const app of ctx.apps) {
|
|
1637
|
-
const projectName = ctx.naming.worker();
|
|
1638
|
-
await run({
|
|
1639
|
-
name: `push env vars to ${projectName}`,
|
|
1640
|
-
handler: async () => {
|
|
1641
|
-
await this.api.upsertEnvVars(projectName, vars);
|
|
1642
|
-
}
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
async inspect(ctx, run) {
|
|
1647
|
-
const state = {
|
|
1648
|
-
workers: [],
|
|
1649
|
-
databases: [],
|
|
1650
|
-
buckets: [],
|
|
1651
|
-
kvNamespaces: [],
|
|
1652
|
-
queues: [],
|
|
1653
|
-
secrets: []
|
|
1654
|
-
};
|
|
1655
|
-
const tasks = [];
|
|
1656
|
-
for (const app of ctx.apps) {
|
|
1657
|
-
const projectName = ctx.naming.worker();
|
|
1658
|
-
tasks.push({
|
|
1659
|
-
name: `inspect project (${projectName})`,
|
|
1660
|
-
handler: async () => {
|
|
1661
|
-
const project = await this.api.getProject(projectName);
|
|
1662
|
-
if (!project) {
|
|
1663
|
-
state.workers.push({
|
|
1664
|
-
name: projectName,
|
|
1665
|
-
exists: false
|
|
1666
|
-
});
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
const latest = (await this.api.listDeployments(project.id, { limit: 1 }))[0];
|
|
1670
|
-
state.workers.push({
|
|
1671
|
-
name: projectName,
|
|
1672
|
-
exists: true,
|
|
1673
|
-
version: latest?.uid,
|
|
1674
|
-
createdAt: latest?.created ? new Date(latest.created).toISOString() : void 0
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
|
|
1680
|
-
const expectedVars = Object.keys(envVars).filter((key) => envVars[key] && !VercelAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
|
|
1681
|
-
if (expectedVars.length > 0) {
|
|
1682
|
-
const projectName = ctx.naming.worker();
|
|
1683
|
-
tasks.push({
|
|
1684
|
-
name: "inspect env vars",
|
|
1685
|
-
handler: async () => {
|
|
1686
|
-
try {
|
|
1687
|
-
const deployed = await this.api.listEnvVars(projectName);
|
|
1688
|
-
const deployedKeys = new Set(deployed.map((v) => v.key));
|
|
1689
|
-
for (const key of expectedVars) state.secrets.push({
|
|
1690
|
-
name: key,
|
|
1691
|
-
deployed: deployedKeys.has(key)
|
|
1692
|
-
});
|
|
1693
|
-
} catch {
|
|
1694
|
-
for (const key of expectedVars) state.secrets.push({
|
|
1695
|
-
name: key,
|
|
1696
|
-
deployed: false
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
await run(tasks);
|
|
1703
|
-
return state;
|
|
1704
|
-
}
|
|
1705
|
-
async teardown(ctx, run) {
|
|
1706
|
-
for (const app of ctx.apps) {
|
|
1707
|
-
const projectName = ctx.naming.worker();
|
|
1708
|
-
await run({
|
|
1709
|
-
name: `delete project ${projectName}`,
|
|
1710
|
-
handler: async () => {
|
|
1711
|
-
try {
|
|
1712
|
-
await this.api.deleteProject(projectName);
|
|
1713
|
-
} catch (error) {
|
|
1714
|
-
this.log.warn(`Failed to delete project ${projectName}: ${String(error.message || "")}`);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
};
|
|
1721
|
-
//#endregion
|
|
1722
|
-
//#region ../../src/cli/platform/atoms/platformOptions.ts
|
|
1723
|
-
/**
|
|
1724
|
-
* Platform deployment configuration atom.
|
|
1725
|
-
*
|
|
1726
|
-
* Filled from the `platform` section of `alepha.config.ts`.
|
|
1727
|
-
* Read by `PlatformCommand` to resolve environments and adapters.
|
|
1728
|
-
*/
|
|
1729
|
-
const platformOptions = $atom({
|
|
1730
|
-
name: "alepha.cli.platform.options",
|
|
1731
|
-
description: "Platform deployment configuration",
|
|
1732
|
-
schema: t.optional(t.object({
|
|
1733
|
-
/**
|
|
1734
|
-
* Project name override. Defaults to root package.json "name".
|
|
1735
|
-
*/
|
|
1736
|
-
name: t.optional(t.text()),
|
|
1737
|
-
/**
|
|
1738
|
-
* Default environment when --env is omitted.
|
|
1739
|
-
*
|
|
1740
|
-
* @default "production"
|
|
1741
|
-
*/
|
|
1742
|
-
default: t.optional(t.text()),
|
|
1743
|
-
/**
|
|
1744
|
-
* Secret store configuration for syncing .env secrets
|
|
1745
|
-
* to external providers (e.g. GitHub Actions environments).
|
|
1746
|
-
*/
|
|
1747
|
-
secrets: t.optional(t.object({
|
|
1748
|
-
/**
|
|
1749
|
-
* Secret store backend.
|
|
1750
|
-
*/
|
|
1751
|
-
store: t.enum(["github"]),
|
|
1752
|
-
/**
|
|
1753
|
-
* Pattern for resolving environment names in the store.
|
|
1754
|
-
* Placeholders: {project}, {env}.
|
|
1755
|
-
*
|
|
1756
|
-
* @default "{project}-{env}"
|
|
1757
|
-
*/
|
|
1758
|
-
environmentPattern: t.optional(t.text())
|
|
1759
|
-
})),
|
|
1760
|
-
/**
|
|
1761
|
-
* Named environments with their adapter and configuration.
|
|
1762
|
-
*/
|
|
1763
|
-
environments: t.record(t.text({ description: "Environment name (e.g. 'production', 'staging', 'preview'). Used in resource naming and selected via --env." }), t.object({
|
|
1764
|
-
adapter: t.enum(["cloudflare", "vercel"]),
|
|
1765
|
-
/**
|
|
1766
|
-
* Custom domain for the deployed worker (e.g. "api.example.com").
|
|
1767
|
-
*
|
|
1768
|
-
* On Cloudflare this is attached as a custom-domain route.
|
|
1769
|
-
* Omit to use the adapter's default `*.workers.dev` / preview URL.
|
|
1770
|
-
*
|
|
1771
|
-
* Wildcards are supported for multi-tenant SaaS apps:
|
|
1772
|
-
* `"*.club.alepha.dev"` routes every subdomain to the worker.
|
|
1773
|
-
* Wildcard patterns require `zone` to be set, and the wildcard DNS
|
|
1774
|
-
* record must already exist (proxied) in the Cloudflare zone.
|
|
1775
|
-
*/
|
|
1776
|
-
domain: t.optional(t.text()),
|
|
1777
|
-
/**
|
|
1778
|
-
* Cloudflare zone name (e.g. "alepha.dev") that owns `domain`.
|
|
1779
|
-
*
|
|
1780
|
-
* Required when `domain` contains a wildcard (`*`). Ignored for
|
|
1781
|
-
* plain custom domains, which Cloudflare resolves automatically.
|
|
1782
|
-
*/
|
|
1783
|
-
zone: t.optional(t.text()),
|
|
1784
|
-
/**
|
|
1785
|
-
* Cloudflare data jurisdiction for R2 buckets and D1 databases.
|
|
1786
|
-
* - "eu": data stays within the EU
|
|
1787
|
-
* - "fedramp": FedRAMP-authorized regions
|
|
1788
|
-
*
|
|
1789
|
-
* Omit for the default (global) jurisdiction.
|
|
1790
|
-
*/
|
|
1791
|
-
jurisdiction: t.optional(t.enum(["eu", "fedramp"])),
|
|
1792
|
-
/**
|
|
1793
|
-
* Cloudflare account ID to deploy into.
|
|
1794
|
-
*
|
|
1795
|
-
* Falls back to `CLOUDFLARE_ACCOUNT_ID` env var, then to the
|
|
1796
|
-
* token's account when the token is scoped to exactly one.
|
|
1797
|
-
* Required when the token has access to multiple accounts.
|
|
1798
|
-
*/
|
|
1799
|
-
accountId: t.optional(t.text())
|
|
1800
|
-
}))
|
|
1801
|
-
}))
|
|
1802
|
-
});
|
|
1803
|
-
//#endregion
|
|
1804
|
-
//#region ../../src/cli/platform/services/NamingService.ts
|
|
1805
|
-
/**
|
|
1806
|
-
* Generates deterministic resource names for cloud deployments.
|
|
1807
|
-
*
|
|
1808
|
-
* Pattern: `<project>-<env>`.
|
|
1809
|
-
*
|
|
1810
|
-
* All segments are slugified (lowercase, alphanumeric + dashes, max 63
|
|
1811
|
-
* chars). One app per workspace — see `alepha platform`.
|
|
1812
|
-
*/
|
|
1813
|
-
var NamingService = class {
|
|
1814
|
-
forContext(project, env) {
|
|
1815
|
-
return new NamingContext(`${this.slugify(project)}-${this.slugify(env)}`);
|
|
1816
|
-
}
|
|
1817
|
-
slugify(name) {
|
|
1818
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
1819
|
-
}
|
|
1820
|
-
};
|
|
1821
|
-
var NamingContext = class {
|
|
1822
|
-
prefix;
|
|
1823
|
-
constructor(prefix) {
|
|
1824
|
-
this.prefix = prefix;
|
|
1825
|
-
}
|
|
1826
|
-
worker() {
|
|
1827
|
-
return this.prefix;
|
|
1828
|
-
}
|
|
1829
|
-
d1() {
|
|
1830
|
-
return this.prefix;
|
|
1831
|
-
}
|
|
1832
|
-
hyperdrive() {
|
|
1833
|
-
return this.prefix;
|
|
1834
|
-
}
|
|
1835
|
-
r2() {
|
|
1836
|
-
return this.prefix;
|
|
1837
|
-
}
|
|
1838
|
-
kv() {
|
|
1839
|
-
return this.prefix;
|
|
1840
|
-
}
|
|
1841
|
-
queue() {
|
|
1842
|
-
return this.prefix;
|
|
1843
|
-
}
|
|
1844
|
-
};
|
|
1845
|
-
//#endregion
|
|
1846
|
-
//#region ../../src/cli/platform/services/PlatformInspector.ts
|
|
1847
|
-
/**
|
|
1848
|
-
* Reads platform config and resolves project topology.
|
|
1849
|
-
*
|
|
1850
|
-
* Validates project name and environment configuration. Does NOT
|
|
1851
|
-
* introspect app code for resources — that happens at deploy time via
|
|
1852
|
-
* ViteBuildProvider.
|
|
1853
|
-
*
|
|
1854
|
-
* Each app self-declares its platform topology via its own
|
|
1855
|
-
* `alepha.config.ts`. Run `alepha platform <op>` from the app's
|
|
1856
|
-
* directory; no monorepo-root orchestration here.
|
|
1857
|
-
*/
|
|
1858
|
-
var PlatformInspector = class {
|
|
1859
|
-
log = $logger();
|
|
1860
|
-
alepha = $inject(Alepha);
|
|
1861
|
-
fs = $inject(FileSystemProvider);
|
|
1862
|
-
asker = $inject(Asker);
|
|
1863
|
-
options = $state(platformOptions);
|
|
1864
|
-
naming = $inject(NamingService);
|
|
1865
|
-
/**
|
|
1866
|
-
* Resolve and validate the full platform configuration.
|
|
1867
|
-
*/
|
|
1868
|
-
async resolveConfig(root) {
|
|
1869
|
-
if (!this.options) {
|
|
1870
|
-
this.log.warn(` alepha.config.ts not found or missing platform config.
|
|
1871
|
-
|
|
1872
|
-
Please add a "platform" section to alepha.config.ts:
|
|
1873
|
-
|
|
1874
|
-
export default defineConfig({
|
|
1875
|
-
platform: {
|
|
1876
|
-
environments: {
|
|
1877
|
-
production: { adapter: "cloudflare" },
|
|
1878
|
-
},
|
|
1879
|
-
},
|
|
1880
|
-
});
|
|
1881
|
-
`);
|
|
1882
|
-
throw new AlephaError("Missing platform configuration.");
|
|
1883
|
-
}
|
|
1884
|
-
const opts = this.options;
|
|
1885
|
-
const project = await this.resolveProjectName(root, opts.name);
|
|
1886
|
-
return {
|
|
1887
|
-
project: this.naming.slugify(project),
|
|
1888
|
-
defaultEnv: opts.default ?? "production",
|
|
1889
|
-
environments: opts.environments
|
|
1890
|
-
};
|
|
1891
|
-
}
|
|
1892
|
-
/**
|
|
1893
|
-
* Resolve a specific environment, validating it exists.
|
|
1894
|
-
*/
|
|
1895
|
-
async resolveEnvironment(root, envName) {
|
|
1896
|
-
const config = await this.resolveConfig(root);
|
|
1897
|
-
const envConfig = config.environments[envName];
|
|
1898
|
-
if (!envConfig) throw new AlephaError(`Unknown environment "${envName}". Available: ${Object.keys(config.environments).join(", ")}`);
|
|
1899
|
-
return envConfig;
|
|
1900
|
-
}
|
|
1901
|
-
async resolveProjectName(root, configName) {
|
|
1902
|
-
if (configName) return configName;
|
|
1903
|
-
try {
|
|
1904
|
-
const pkgPath = this.fs.join(root, "package.json");
|
|
1905
|
-
const pkg = await this.fs.readJsonFile(pkgPath);
|
|
1906
|
-
if (pkg.name) return pkg.name;
|
|
1907
|
-
} catch {}
|
|
1908
|
-
throw new AlephaError("Missing project name. Set \"name\" in alepha.config.ts or add a \"name\" field to package.json.");
|
|
1909
|
-
}
|
|
1910
|
-
};
|
|
1911
|
-
//#endregion
|
|
1912
|
-
//#region ../../src/cli/platform/services/PlatformOrchestrator.ts
|
|
1913
|
-
/**
|
|
1914
|
-
* Orchestrates platform lifecycle operations.
|
|
1915
|
-
*
|
|
1916
|
-
* Coordinates adapter calls in the correct order for
|
|
1917
|
-
* up (build -> migrate -> deploy), down, plan, and status.
|
|
1918
|
-
*/
|
|
1919
|
-
var PlatformOrchestrator = class {
|
|
1920
|
-
log = $logger();
|
|
1921
|
-
color = $inject(ConsoleColorProvider);
|
|
1922
|
-
inspector = $inject(PlatformInspector);
|
|
1923
|
-
naming = $inject(NamingService);
|
|
1924
|
-
cloudflareAdapter = $inject(CloudflareAdapter);
|
|
1925
|
-
vercelAdapter = $inject(VercelAdapter);
|
|
1926
|
-
alepha = $inject(Alepha);
|
|
1927
|
-
resolveAdapter(adapterName) {
|
|
1928
|
-
switch (adapterName) {
|
|
1929
|
-
case "cloudflare": return this.cloudflareAdapter;
|
|
1930
|
-
case "vercel": return this.vercelAdapter;
|
|
1931
|
-
default: throw new AlephaError(`Unknown adapter: "${adapterName}"`);
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
async up(options) {
|
|
1935
|
-
const { root, env, apps, run, prebuilt } = options;
|
|
1936
|
-
const envConfig = await this.inspector.resolveEnvironment(root, env);
|
|
1937
|
-
const config = await this.inspector.resolveConfig(root);
|
|
1938
|
-
const adapter = this.resolveAdapter(envConfig.adapter);
|
|
1939
|
-
const namingCtx = this.naming.forContext(config.project, env);
|
|
1940
|
-
const ctx = {
|
|
1941
|
-
project: config.project,
|
|
1942
|
-
env,
|
|
1943
|
-
envConfig,
|
|
1944
|
-
apps,
|
|
1945
|
-
root,
|
|
1946
|
-
naming: namingCtx,
|
|
1947
|
-
prebuilt
|
|
1948
|
-
};
|
|
1949
|
-
await adapter.authenticate(ctx, run);
|
|
1950
|
-
await adapter.provision(ctx, run);
|
|
1951
|
-
for (const a of apps) await adapter.build({
|
|
1952
|
-
...ctx,
|
|
1953
|
-
app: a
|
|
1954
|
-
}, run);
|
|
1955
|
-
await adapter.migrate(ctx, run);
|
|
1956
|
-
const urls = [];
|
|
1957
|
-
for (const a of apps) {
|
|
1958
|
-
const url = await adapter.deploy({
|
|
1959
|
-
...ctx,
|
|
1960
|
-
app: a
|
|
1961
|
-
}, run);
|
|
1962
|
-
if (url) urls.push(url);
|
|
1963
|
-
}
|
|
1964
|
-
await adapter.secrets(ctx, run);
|
|
1965
|
-
run.end();
|
|
1966
|
-
return {
|
|
1967
|
-
urls,
|
|
1968
|
-
domain: envConfig.domain
|
|
1969
|
-
};
|
|
1970
|
-
}
|
|
1971
|
-
/**
|
|
1972
|
-
* Pretty-print the `up()` result to stdout. Matches the formatting the
|
|
1973
|
-
* orchestrator used to emit inline; split out so callers that want
|
|
1974
|
-
* JSON output can skip this branch.
|
|
1975
|
-
*/
|
|
1976
|
-
printUpSummary(result) {
|
|
1977
|
-
const c = this.color;
|
|
1978
|
-
if (result.domain) {
|
|
1979
|
-
this.log.info("");
|
|
1980
|
-
const display = result.domain.includes("*") ? `https://${result.domain} (wildcard route)` : `https://${result.domain}`;
|
|
1981
|
-
this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", display)}`);
|
|
1982
|
-
this.log.info("");
|
|
1983
|
-
} else for (const url of result.urls) {
|
|
1984
|
-
this.log.info("");
|
|
1985
|
-
this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", url)}`);
|
|
1986
|
-
this.log.info("");
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
async down(options) {
|
|
1990
|
-
const { root, env, apps, run, confirm } = options;
|
|
1991
|
-
const envConfig = await this.inspector.resolveEnvironment(root, env);
|
|
1992
|
-
const config = await this.inspector.resolveConfig(root);
|
|
1993
|
-
const adapter = this.resolveAdapter(envConfig.adapter);
|
|
1994
|
-
const namingCtx = this.naming.forContext(config.project, env);
|
|
1995
|
-
const ctx = {
|
|
1996
|
-
project: config.project,
|
|
1997
|
-
env,
|
|
1998
|
-
envConfig,
|
|
1999
|
-
apps,
|
|
2000
|
-
root,
|
|
2001
|
-
naming: namingCtx
|
|
2002
|
-
};
|
|
2003
|
-
if (!this.isTmpEnv(env)) {
|
|
2004
|
-
if (await confirm(`Type "${env}" to confirm teardown:`) !== env) {
|
|
2005
|
-
this.log.info("Aborted.");
|
|
2006
|
-
return false;
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
await adapter.authenticate(ctx, run);
|
|
2010
|
-
await adapter.teardown(ctx, run);
|
|
2011
|
-
run.end();
|
|
2012
|
-
return true;
|
|
2013
|
-
}
|
|
2014
|
-
async plan(options) {
|
|
2015
|
-
const { root, env, apps } = options;
|
|
2016
|
-
const config = await this.inspector.resolveConfig(root);
|
|
2017
|
-
return {
|
|
2018
|
-
config,
|
|
2019
|
-
naming: this.naming.forContext(config.project, env),
|
|
2020
|
-
apps
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
async status(options) {
|
|
2024
|
-
const { root, env, apps, run } = options;
|
|
2025
|
-
const envConfig = await this.inspector.resolveEnvironment(root, env);
|
|
2026
|
-
const config = await this.inspector.resolveConfig(root);
|
|
2027
|
-
const adapter = this.resolveAdapter(envConfig.adapter);
|
|
2028
|
-
const namingCtx = this.naming.forContext(config.project, env);
|
|
2029
|
-
const ctx = {
|
|
2030
|
-
project: config.project,
|
|
2031
|
-
env,
|
|
2032
|
-
envConfig,
|
|
2033
|
-
apps,
|
|
2034
|
-
root,
|
|
2035
|
-
naming: namingCtx
|
|
2036
|
-
};
|
|
2037
|
-
await adapter.authenticate(ctx, run);
|
|
2038
|
-
return {
|
|
2039
|
-
config,
|
|
2040
|
-
state: await adapter.inspect(ctx, run)
|
|
2041
|
-
};
|
|
2042
|
-
}
|
|
2043
|
-
isTmpEnv(env) {
|
|
2044
|
-
return env.startsWith("tmp");
|
|
2045
|
-
}
|
|
2046
|
-
};
|
|
2047
|
-
//#endregion
|
|
2048
|
-
//#region ../../src/cli/platform/providers/GitHubSecretStore.ts
|
|
2049
|
-
/**
|
|
2050
|
-
* GitHub Actions secret store backed by the `gh` CLI.
|
|
2051
|
-
*
|
|
2052
|
-
* Requires the GitHub CLI (`gh`) to be installed and authenticated.
|
|
2053
|
-
* Pushes secrets into GitHub Actions environments.
|
|
2054
|
-
*/
|
|
2055
|
-
var GitHubSecretStore = class {
|
|
2056
|
-
log = $logger();
|
|
2057
|
-
shell = $inject(ShellProvider);
|
|
2058
|
-
fs = $inject(FileSystemProvider);
|
|
2059
|
-
/**
|
|
2060
|
-
* Verify that `gh` is installed and authenticated.
|
|
2061
|
-
*/
|
|
2062
|
-
async ensureAvailable() {
|
|
2063
|
-
if (!await this.shell.isInstalled("gh")) throw new AlephaError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com");
|
|
2064
|
-
try {
|
|
2065
|
-
await this.shell.run("gh auth status", { capture: true });
|
|
2066
|
-
} catch {
|
|
2067
|
-
throw new AlephaError("GitHub CLI is not authenticated. Run `gh auth login` first.");
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
/**
|
|
2071
|
-
* Create the GitHub Actions environment if it doesn't exist.
|
|
2072
|
-
*/
|
|
2073
|
-
async ensureEnvironment(environment) {
|
|
2074
|
-
await this.shell.run(`gh api --method PUT /repos/{owner}/{repo}/environments/${environment} --silent`, { capture: true });
|
|
2075
|
-
this.log.debug(`Ensured environment "${environment}" exists`);
|
|
2076
|
-
}
|
|
2077
|
-
/**
|
|
2078
|
-
* List all secrets in a GitHub Actions environment.
|
|
2079
|
-
*/
|
|
2080
|
-
async list(environment) {
|
|
2081
|
-
try {
|
|
2082
|
-
const output = await this.shell.run(`gh secret list --env ${environment} --json name,updatedAt`, { capture: true });
|
|
2083
|
-
return JSON.parse(output || "[]").map((s) => ({
|
|
2084
|
-
name: s.name,
|
|
2085
|
-
updatedAt: s.updatedAt
|
|
2086
|
-
}));
|
|
2087
|
-
} catch (error) {
|
|
2088
|
-
this.log.debug("Failed to list secrets", {
|
|
2089
|
-
environment,
|
|
2090
|
-
error
|
|
2091
|
-
});
|
|
2092
|
-
return [];
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
/**
|
|
2096
|
-
* Set a secret in a GitHub Actions environment.
|
|
2097
|
-
*
|
|
2098
|
-
* Writes a dotenv-formatted file and uses `gh secret set --env-file` to
|
|
2099
|
-
* avoid shell pipe issues with NodeShellProvider escaping the `|` character.
|
|
2100
|
-
*/
|
|
2101
|
-
async set(environment, key, value) {
|
|
2102
|
-
const tmpFile = `/tmp/alepha-secret-${key}-${Date.now()}`;
|
|
2103
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
2104
|
-
await this.fs.writeFile(tmpFile, `${key}="${escaped}"\n`);
|
|
2105
|
-
try {
|
|
2106
|
-
const output = await this.shell.run(`gh secret set -f ${tmpFile} --env ${environment}`, { capture: true });
|
|
2107
|
-
this.log.debug(`Secret set: ${key}`, { output });
|
|
2108
|
-
} finally {
|
|
2109
|
-
await this.fs.rm(tmpFile);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
/**
|
|
2113
|
-
* Delete a secret from a GitHub Actions environment.
|
|
2114
|
-
*/
|
|
2115
|
-
async delete(environment, key) {
|
|
2116
|
-
await this.shell.run(`gh secret delete ${key} --env ${environment}`, { capture: true });
|
|
2117
|
-
}
|
|
2118
|
-
};
|
|
2119
|
-
//#endregion
|
|
2120
|
-
//#region ../../src/cli/platform/services/SecretFilterService.ts
|
|
2121
|
-
/**
|
|
2122
|
-
* Filters environment variables for secret store syncing.
|
|
2123
|
-
*
|
|
2124
|
-
* Excludes platform-managed vars (NODE_ENV), build-time vars (VITE_*),
|
|
2125
|
-
* and empty values. Keeps everything else — including DATABASE_URL
|
|
2126
|
-
* and POSTGRES_SCHEMA which GitHub Actions needs.
|
|
2127
|
-
*
|
|
2128
|
-
* Also handles renaming GITHUB_* keys since GitHub Actions rejects
|
|
2129
|
-
* secret names starting with GITHUB_.
|
|
2130
|
-
*/
|
|
2131
|
-
var SecretFilterService = class SecretFilterService {
|
|
2132
|
-
static EXCLUDED_KEYS = new Set(["NODE_ENV"]);
|
|
2133
|
-
static GITHUB_PREFIX = "GITHUB_";
|
|
2134
|
-
static REMOTE_PREFIX = "APP_GITHUB_";
|
|
2135
|
-
/**
|
|
2136
|
-
* Return only the entries that should be pushed to a secret store.
|
|
2137
|
-
*/
|
|
2138
|
-
filter(envVars) {
|
|
2139
|
-
const result = {};
|
|
2140
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
2141
|
-
if (!value) continue;
|
|
2142
|
-
if (SecretFilterService.EXCLUDED_KEYS.has(key)) continue;
|
|
2143
|
-
if (key.startsWith("VITE_")) continue;
|
|
2144
|
-
result[key] = value;
|
|
2145
|
-
}
|
|
2146
|
-
return result;
|
|
2147
|
-
}
|
|
2148
|
-
/**
|
|
2149
|
-
* Convert a local env key to a remote secret name.
|
|
2150
|
-
*
|
|
2151
|
-
* GITHUB_* keys are prefixed with APP_ since GitHub Actions rejects
|
|
2152
|
-
* secret names starting with GITHUB_.
|
|
2153
|
-
*/
|
|
2154
|
-
toRemoteName(key) {
|
|
2155
|
-
if (key.startsWith(SecretFilterService.GITHUB_PREFIX)) return `${SecretFilterService.REMOTE_PREFIX}${key.slice(SecretFilterService.GITHUB_PREFIX.length)}`;
|
|
2156
|
-
return key;
|
|
2157
|
-
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Convert a remote secret name back to the local env key.
|
|
2160
|
-
*/
|
|
2161
|
-
toLocalName(remoteName) {
|
|
2162
|
-
if (remoteName.startsWith(SecretFilterService.REMOTE_PREFIX)) return `${SecretFilterService.GITHUB_PREFIX}${remoteName.slice(SecretFilterService.REMOTE_PREFIX.length)}`;
|
|
2163
|
-
return remoteName;
|
|
2164
|
-
}
|
|
2165
|
-
};
|
|
2166
|
-
//#endregion
|
|
2167
6
|
//#region ../../src/cli/platform/commands/SecretsCommand.ts
|
|
2168
7
|
var SecretsCommand = class {
|
|
2169
8
|
log = $logger();
|
|
@@ -2352,6 +191,7 @@ var PlatformCommand = class {
|
|
|
2352
191
|
aliases: ["e"],
|
|
2353
192
|
description: "Target environment"
|
|
2354
193
|
})),
|
|
194
|
+
tenant: t.optional(t.text({ description: "Tenant slug (apps with tenancy: optional | required). Names resources <tenant>-<project>-<env> and serves <tenant>.<domain>." })),
|
|
2355
195
|
verbose: t.optional(t.boolean({
|
|
2356
196
|
aliases: ["v"],
|
|
2357
197
|
description: "Verbose output"
|
|
@@ -2367,7 +207,8 @@ var PlatformCommand = class {
|
|
|
2367
207
|
const env = flags.env ?? config.defaultEnv;
|
|
2368
208
|
const adapterName = config.environments[env]?.adapter ?? "cloudflare";
|
|
2369
209
|
const apps = await this.resolveApps(root, config, this.isServerless(adapterName));
|
|
2370
|
-
const
|
|
210
|
+
const tenant = resolveTenant(config.tenancy, flags.tenant);
|
|
211
|
+
const namingCtx = this.naming.forContext(config.project, env, tenant);
|
|
2371
212
|
const hasDB = apps.some((a) => a.resources.hasDatabase);
|
|
2372
213
|
const hasBucket = apps.some((a) => a.resources.hasBucket);
|
|
2373
214
|
const envVars = await this.envUtils.parseEnv(root, [`.env.${env}`]);
|
|
@@ -2462,13 +303,15 @@ var PlatformCommand = class {
|
|
|
2462
303
|
const config = await this.inspector.resolveConfig(root);
|
|
2463
304
|
const env = flags.env ?? config.defaultEnv;
|
|
2464
305
|
const adapter = config.environments[env]?.adapter ?? "cloudflare";
|
|
2465
|
-
const apps = await this.resolveApps(root, config, this.isServerless(adapter));
|
|
306
|
+
const apps = await this.resolveApps(root, config, this.isServerless(adapter), { prebuilt: flags.prebuilt });
|
|
2466
307
|
const result = await this.orchestrator.up({
|
|
2467
308
|
root,
|
|
2468
309
|
env,
|
|
2469
|
-
apps,
|
|
310
|
+
entry: apps[0].entry,
|
|
311
|
+
resources: apps[0].resources,
|
|
2470
312
|
run,
|
|
2471
|
-
prebuilt: flags.prebuilt
|
|
313
|
+
prebuilt: flags.prebuilt,
|
|
314
|
+
tenant: flags.tenant
|
|
2472
315
|
});
|
|
2473
316
|
if (flags.json) process.stdout.write(`${JSON.stringify({
|
|
2474
317
|
status: "succeeded",
|
|
@@ -2498,8 +341,10 @@ var PlatformCommand = class {
|
|
|
2498
341
|
const completed = await this.orchestrator.down({
|
|
2499
342
|
root,
|
|
2500
343
|
env: flags.env,
|
|
2501
|
-
apps,
|
|
344
|
+
entry: apps[0].entry,
|
|
345
|
+
resources: apps[0].resources,
|
|
2502
346
|
run,
|
|
347
|
+
tenant: flags.tenant,
|
|
2503
348
|
confirm: async (prompt) => {
|
|
2504
349
|
if (flags.yes) return flags.env;
|
|
2505
350
|
ask.intro("Confirm teardown");
|
|
@@ -2528,8 +373,10 @@ var PlatformCommand = class {
|
|
|
2528
373
|
const { state } = await this.orchestrator.status({
|
|
2529
374
|
root,
|
|
2530
375
|
env,
|
|
2531
|
-
apps,
|
|
2532
|
-
|
|
376
|
+
entry: apps[0].entry,
|
|
377
|
+
resources: apps[0].resources,
|
|
378
|
+
run,
|
|
379
|
+
tenant: flags.tenant
|
|
2533
380
|
});
|
|
2534
381
|
if (flags.json) {
|
|
2535
382
|
run.end();
|
|
@@ -2620,19 +467,19 @@ var PlatformCommand = class {
|
|
|
2620
467
|
const envConfig = config.environments[env];
|
|
2621
468
|
const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
|
|
2622
469
|
const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
|
|
2623
|
-
const
|
|
470
|
+
const tenant = resolveTenant(config.tenancy, flags.tenant);
|
|
471
|
+
const namingCtx = this.naming.forContext(config.project, env, tenant);
|
|
2624
472
|
const ctx = {
|
|
2625
473
|
project: config.project,
|
|
2626
474
|
env,
|
|
2627
475
|
envConfig,
|
|
2628
|
-
apps,
|
|
476
|
+
entry: apps[0].entry,
|
|
477
|
+
resources: apps[0].resources,
|
|
2629
478
|
root,
|
|
2630
|
-
naming: namingCtx
|
|
479
|
+
naming: namingCtx,
|
|
480
|
+
tenant
|
|
2631
481
|
};
|
|
2632
|
-
|
|
2633
|
-
...ctx,
|
|
2634
|
-
app
|
|
2635
|
-
}, run);
|
|
482
|
+
await adapter.build(ctx, run);
|
|
2636
483
|
}
|
|
2637
484
|
});
|
|
2638
485
|
deploy = $command({
|
|
@@ -2645,20 +492,20 @@ var PlatformCommand = class {
|
|
|
2645
492
|
const envConfig = config.environments[env];
|
|
2646
493
|
const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
|
|
2647
494
|
const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
|
|
2648
|
-
const
|
|
495
|
+
const tenant = resolveTenant(config.tenancy, flags.tenant);
|
|
496
|
+
const namingCtx = this.naming.forContext(config.project, env, tenant);
|
|
2649
497
|
const ctx = {
|
|
2650
498
|
project: config.project,
|
|
2651
499
|
env,
|
|
2652
500
|
envConfig,
|
|
2653
|
-
apps,
|
|
501
|
+
entry: apps[0].entry,
|
|
502
|
+
resources: apps[0].resources,
|
|
2654
503
|
root,
|
|
2655
|
-
naming: namingCtx
|
|
504
|
+
naming: namingCtx,
|
|
505
|
+
tenant
|
|
2656
506
|
};
|
|
2657
507
|
await adapter.authenticate(ctx, run);
|
|
2658
|
-
|
|
2659
|
-
...ctx,
|
|
2660
|
-
app
|
|
2661
|
-
}, run);
|
|
508
|
+
await adapter.deploy(ctx, run);
|
|
2662
509
|
}
|
|
2663
510
|
});
|
|
2664
511
|
migrate = $command({
|
|
@@ -2671,14 +518,17 @@ var PlatformCommand = class {
|
|
|
2671
518
|
const envConfig = config.environments[env];
|
|
2672
519
|
const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
|
|
2673
520
|
const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
|
|
2674
|
-
const
|
|
521
|
+
const tenant = resolveTenant(config.tenancy, flags.tenant);
|
|
522
|
+
const namingCtx = this.naming.forContext(config.project, env, tenant);
|
|
2675
523
|
const ctx = {
|
|
2676
524
|
project: config.project,
|
|
2677
525
|
env,
|
|
2678
526
|
envConfig,
|
|
2679
|
-
apps,
|
|
527
|
+
entry: apps[0].entry,
|
|
528
|
+
resources: apps[0].resources,
|
|
2680
529
|
root,
|
|
2681
|
-
naming: namingCtx
|
|
530
|
+
naming: namingCtx,
|
|
531
|
+
tenant
|
|
2682
532
|
};
|
|
2683
533
|
await adapter.authenticate(ctx, run);
|
|
2684
534
|
await adapter.migrate(ctx, run);
|
|
@@ -2689,6 +539,53 @@ var PlatformCommand = class {
|
|
|
2689
539
|
}, null, 2)}\n`);
|
|
2690
540
|
}
|
|
2691
541
|
});
|
|
542
|
+
dbExport = $command({
|
|
543
|
+
name: "export",
|
|
544
|
+
description: "Export the deployed database to a local snapshot (remote → local dev DB).",
|
|
545
|
+
flags: t.object({
|
|
546
|
+
...this.envFlags.properties,
|
|
547
|
+
output: t.optional(t.text({ description: "Destination SQLite file. Defaults to the dev DB path (node_modules/.alepha/sqlite.db)." })),
|
|
548
|
+
keepSql: t.optional(t.boolean({ description: "Keep the intermediate .sql dump file." }))
|
|
549
|
+
}),
|
|
550
|
+
handler: async ({ flags, root, run }) => {
|
|
551
|
+
const config = await this.inspector.resolveConfig(root);
|
|
552
|
+
const env = flags.env ?? config.defaultEnv;
|
|
553
|
+
const envConfig = config.environments[env];
|
|
554
|
+
const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
|
|
555
|
+
const app = await this.resolveApp(root, config, this.isServerless(envConfig.adapter));
|
|
556
|
+
const tenant = resolveTenant(config.tenancy, flags.tenant);
|
|
557
|
+
const namingCtx = this.naming.forContext(config.project, env, tenant);
|
|
558
|
+
const ctx = {
|
|
559
|
+
project: config.project,
|
|
560
|
+
env,
|
|
561
|
+
envConfig,
|
|
562
|
+
entry: app.entry,
|
|
563
|
+
resources: app.resources,
|
|
564
|
+
root,
|
|
565
|
+
naming: namingCtx,
|
|
566
|
+
tenant
|
|
567
|
+
};
|
|
568
|
+
await adapter.authenticate(ctx, run);
|
|
569
|
+
await adapter.exportDb(ctx, run, {
|
|
570
|
+
output: flags.output,
|
|
571
|
+
keepSql: flags.keepSql
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
/**
|
|
576
|
+
* `db` subgroup — operations against the *deployed* database (export,
|
|
577
|
+
* migrate). They live under `platform` (not core `alepha db`) because
|
|
578
|
+
* they need the env config, tenancy, adapter, and resource naming.
|
|
579
|
+
*/
|
|
580
|
+
db = $command({
|
|
581
|
+
name: "db",
|
|
582
|
+
description: "Deployed-database operations (export, migrate).",
|
|
583
|
+
children: [this.dbExport, this.migrate],
|
|
584
|
+
handler: async ({ help, root }) => {
|
|
585
|
+
await this.inspector.resolveConfig(root);
|
|
586
|
+
help();
|
|
587
|
+
}
|
|
588
|
+
});
|
|
2692
589
|
platform = $command({
|
|
2693
590
|
name: "platform",
|
|
2694
591
|
aliases: ["p"],
|
|
@@ -2700,7 +597,7 @@ var PlatformCommand = class {
|
|
|
2700
597
|
this.status,
|
|
2701
598
|
this.build,
|
|
2702
599
|
this.deploy,
|
|
2703
|
-
this.
|
|
600
|
+
this.db,
|
|
2704
601
|
this.secretsCommand.secrets
|
|
2705
602
|
],
|
|
2706
603
|
handler: async ({ help, root }) => {
|
|
@@ -2718,12 +615,32 @@ var PlatformCommand = class {
|
|
|
2718
615
|
* ViteBuildProvider.init() per app. This is expensive -- only done
|
|
2719
616
|
* for up/down/status, not for plan.
|
|
2720
617
|
*/
|
|
2721
|
-
async
|
|
618
|
+
async resolveApp(root, _config, isServerless, options = {}) {
|
|
619
|
+
if (options.prebuilt) {
|
|
620
|
+
const manifest = await this.readManifest(root);
|
|
621
|
+
if (manifest) return {
|
|
622
|
+
entry: {
|
|
623
|
+
root,
|
|
624
|
+
server: ""
|
|
625
|
+
},
|
|
626
|
+
resources: manifest.resources
|
|
627
|
+
};
|
|
628
|
+
}
|
|
2722
629
|
const entry = await this.boot.getAppEntry(root);
|
|
2723
630
|
if (isServerless) process.env.ALEPHA_SERVERLESS = "true";
|
|
2724
631
|
const appAlepha = await this.viteBuild.init({ entry });
|
|
2725
632
|
delete process.env.ALEPHA_SERVERLESS;
|
|
2726
|
-
|
|
633
|
+
return {
|
|
634
|
+
entry,
|
|
635
|
+
resources: this.detectResources(appAlepha)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* @deprecated single-app projects; use `resolveApp` directly.
|
|
640
|
+
* Kept temporarily so existing commands can be migrated one at a time.
|
|
641
|
+
*/
|
|
642
|
+
async resolveApps(root, config, isServerless, options = {}) {
|
|
643
|
+
const { entry, resources } = await this.resolveApp(root, config, isServerless, options);
|
|
2727
644
|
return [{
|
|
2728
645
|
name: config.project,
|
|
2729
646
|
path: "",
|
|
@@ -2734,6 +651,21 @@ var PlatformCommand = class {
|
|
|
2734
651
|
isServerless(adapter) {
|
|
2735
652
|
return adapter === "vercel" || adapter === "cloudflare";
|
|
2736
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Read `dist/manifest.json` if present. Returns `null` when the file
|
|
656
|
+
* doesn't exist or isn't parseable — caller falls back to the
|
|
657
|
+
* Vite-introspection path.
|
|
658
|
+
*/
|
|
659
|
+
async readManifest(root) {
|
|
660
|
+
try {
|
|
661
|
+
const fs = await import("node:fs/promises");
|
|
662
|
+
const path = await import("node:path");
|
|
663
|
+
const raw = await fs.readFile(path.join(root, "dist", "manifest.json"), "utf-8");
|
|
664
|
+
return JSON.parse(raw);
|
|
665
|
+
} catch {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
2737
669
|
detectResources(alepha) {
|
|
2738
670
|
let hasDatabase = false;
|
|
2739
671
|
let hasBucket = false;
|
|
@@ -2765,174 +697,15 @@ var PlatformCommand = class {
|
|
|
2765
697
|
}
|
|
2766
698
|
};
|
|
2767
699
|
//#endregion
|
|
2768
|
-
//#region ../../src/cli/platform/providers/MemorySecretStore.ts
|
|
2769
|
-
/**
|
|
2770
|
-
* In-memory implementation of SecretStoreProvider for testing.
|
|
2771
|
-
* Records all operations and stores secrets in a nested Map.
|
|
2772
|
-
*/
|
|
2773
|
-
var MemorySecretStore = class {
|
|
2774
|
-
/**
|
|
2775
|
-
* Secrets keyed by environment, then by key.
|
|
2776
|
-
*/
|
|
2777
|
-
secrets = /* @__PURE__ */ new Map();
|
|
2778
|
-
/**
|
|
2779
|
-
* All recorded operations.
|
|
2780
|
-
*/
|
|
2781
|
-
calls = [];
|
|
2782
|
-
/**
|
|
2783
|
-
* When set, ensureAvailable() will throw with this message.
|
|
2784
|
-
*/
|
|
2785
|
-
availableError = null;
|
|
2786
|
-
async ensureAvailable() {
|
|
2787
|
-
this.calls.push({ method: "ensureAvailable" });
|
|
2788
|
-
if (this.availableError) throw new AlephaError(this.availableError);
|
|
2789
|
-
}
|
|
2790
|
-
async ensureEnvironment(environment) {
|
|
2791
|
-
this.calls.push({
|
|
2792
|
-
method: "ensureEnvironment",
|
|
2793
|
-
environment
|
|
2794
|
-
});
|
|
2795
|
-
if (!this.secrets.has(environment)) this.secrets.set(environment, /* @__PURE__ */ new Map());
|
|
2796
|
-
}
|
|
2797
|
-
async list(environment) {
|
|
2798
|
-
this.calls.push({
|
|
2799
|
-
method: "list",
|
|
2800
|
-
environment
|
|
2801
|
-
});
|
|
2802
|
-
const envSecrets = this.secrets.get(environment);
|
|
2803
|
-
if (!envSecrets) return [];
|
|
2804
|
-
return Array.from(envSecrets.keys()).map((name) => ({ name }));
|
|
2805
|
-
}
|
|
2806
|
-
async set(environment, key, value) {
|
|
2807
|
-
this.calls.push({
|
|
2808
|
-
method: "set",
|
|
2809
|
-
environment,
|
|
2810
|
-
key,
|
|
2811
|
-
value
|
|
2812
|
-
});
|
|
2813
|
-
let envSecrets = this.secrets.get(environment);
|
|
2814
|
-
if (!envSecrets) {
|
|
2815
|
-
envSecrets = /* @__PURE__ */ new Map();
|
|
2816
|
-
this.secrets.set(environment, envSecrets);
|
|
2817
|
-
}
|
|
2818
|
-
envSecrets.set(key, value);
|
|
2819
|
-
}
|
|
2820
|
-
async delete(environment, key) {
|
|
2821
|
-
this.calls.push({
|
|
2822
|
-
method: "delete",
|
|
2823
|
-
environment,
|
|
2824
|
-
key
|
|
2825
|
-
});
|
|
2826
|
-
this.secrets.get(environment)?.delete(key);
|
|
2827
|
-
}
|
|
2828
|
-
/**
|
|
2829
|
-
* Check if set() was called for a given environment and key.
|
|
2830
|
-
*/
|
|
2831
|
-
wasSet(environment, key) {
|
|
2832
|
-
return this.calls.some((c) => c.method === "set" && c.environment === environment && c.key === key);
|
|
2833
|
-
}
|
|
2834
|
-
/**
|
|
2835
|
-
* Check if delete() was called for a given environment and key.
|
|
2836
|
-
*/
|
|
2837
|
-
wasDeleted(environment, key) {
|
|
2838
|
-
return this.calls.some((c) => c.method === "delete" && c.environment === environment && c.key === key);
|
|
2839
|
-
}
|
|
2840
|
-
/**
|
|
2841
|
-
* Get all set() calls for a given environment.
|
|
2842
|
-
*/
|
|
2843
|
-
getSetCalls(environment) {
|
|
2844
|
-
return this.calls.filter((c) => c.method === "set" && c.environment === environment).map((c) => ({
|
|
2845
|
-
key: c.key,
|
|
2846
|
-
value: c.value
|
|
2847
|
-
}));
|
|
2848
|
-
}
|
|
2849
|
-
/**
|
|
2850
|
-
* Reset all state.
|
|
2851
|
-
*/
|
|
2852
|
-
reset() {
|
|
2853
|
-
this.secrets.clear();
|
|
2854
|
-
this.calls = [];
|
|
2855
|
-
this.availableError = null;
|
|
2856
|
-
}
|
|
2857
|
-
};
|
|
2858
|
-
//#endregion
|
|
2859
|
-
//#region ../../src/cli/platform/providers/SecretStoreProvider.ts
|
|
2860
|
-
/**
|
|
2861
|
-
* Abstract provider for managing secrets in an external store.
|
|
2862
|
-
*
|
|
2863
|
-
* Implementations: GitHubSecretStore, MemorySecretStore
|
|
2864
|
-
*/
|
|
2865
|
-
var SecretStoreProvider = class {};
|
|
2866
|
-
//#endregion
|
|
2867
|
-
//#region ../../src/cli/platform/schemas/platform.ts
|
|
2868
|
-
const platformStatusWorkerSchema = t.object({
|
|
2869
|
-
name: t.string(),
|
|
2870
|
-
exists: t.boolean(),
|
|
2871
|
-
id: t.optional(t.string()),
|
|
2872
|
-
detail: t.optional(t.string()),
|
|
2873
|
-
version: t.optional(t.string()),
|
|
2874
|
-
tag: t.optional(t.string()),
|
|
2875
|
-
createdAt: t.optional(t.string())
|
|
2876
|
-
});
|
|
2877
|
-
const platformStatusResourceSchema = t.object({
|
|
2878
|
-
name: t.string(),
|
|
2879
|
-
exists: t.boolean(),
|
|
2880
|
-
id: t.optional(t.string()),
|
|
2881
|
-
detail: t.optional(t.string())
|
|
2882
|
-
});
|
|
2883
|
-
const platformStatusSecretSchema = t.object({
|
|
2884
|
-
name: t.string(),
|
|
2885
|
-
deployed: t.boolean()
|
|
2886
|
-
});
|
|
2887
|
-
const platformStatusSchema = t.object({
|
|
2888
|
-
project: t.string(),
|
|
2889
|
-
env: t.string(),
|
|
2890
|
-
adapter: t.string(),
|
|
2891
|
-
workers: t.array(platformStatusWorkerSchema),
|
|
2892
|
-
databases: t.array(platformStatusResourceSchema),
|
|
2893
|
-
buckets: t.array(platformStatusResourceSchema),
|
|
2894
|
-
kvNamespaces: t.array(platformStatusResourceSchema),
|
|
2895
|
-
queues: t.array(platformStatusResourceSchema),
|
|
2896
|
-
secrets: t.array(platformStatusSecretSchema)
|
|
2897
|
-
});
|
|
2898
|
-
const platformPlanAppResourcesSchema = t.object({
|
|
2899
|
-
hasDatabase: t.boolean(),
|
|
2900
|
-
hasBucket: t.boolean(),
|
|
2901
|
-
hasKV: t.boolean(),
|
|
2902
|
-
hasQueue: t.boolean(),
|
|
2903
|
-
hasCron: t.boolean()
|
|
2904
|
-
});
|
|
2905
|
-
const platformPlanAppSchema = t.object({
|
|
2906
|
-
name: t.string(),
|
|
2907
|
-
path: t.string(),
|
|
2908
|
-
resources: platformPlanAppResourcesSchema
|
|
2909
|
-
});
|
|
2910
|
-
const platformPlanEnvironmentSchema = t.object({
|
|
2911
|
-
adapter: t.string(),
|
|
2912
|
-
domain: t.optional(t.string()),
|
|
2913
|
-
zone: t.optional(t.string())
|
|
2914
|
-
});
|
|
2915
|
-
const platformPlanResourceSchema = t.object({
|
|
2916
|
-
label: t.string(),
|
|
2917
|
-
value: t.string()
|
|
2918
|
-
});
|
|
2919
|
-
const platformPlanSchema = t.object({
|
|
2920
|
-
project: t.string(),
|
|
2921
|
-
env: t.string(),
|
|
2922
|
-
mode: t.enum(["monorepo", "standalone"]),
|
|
2923
|
-
apps: t.array(platformPlanAppSchema),
|
|
2924
|
-
environments: t.record(t.string(), platformPlanEnvironmentSchema),
|
|
2925
|
-
resources: t.array(platformPlanResourceSchema),
|
|
2926
|
-
secretCount: t.number()
|
|
2927
|
-
});
|
|
2928
|
-
//#endregion
|
|
2929
700
|
//#region ../../src/cli/platform/index.ts
|
|
2930
701
|
/**
|
|
2931
702
|
* CLI plugin for multi-cloud deployment orchestration.
|
|
2932
703
|
*
|
|
2933
|
-
*
|
|
2934
|
-
*
|
|
2935
|
-
*
|
|
704
|
+
* Wraps `AlephaPlatformLibPlugin` (the framework-agnostic deploy
|
|
705
|
+
* services) with `$command` instances so the orchestration is
|
|
706
|
+
* reachable from `alepha platform …`. Non-CLI consumers (e.g. Alepha
|
|
707
|
+
* Rocket) should depend on `alepha/cli/platform-lib` directly instead
|
|
708
|
+
* of pulling in this command surface.
|
|
2936
709
|
*
|
|
2937
710
|
* Commands:
|
|
2938
711
|
* - `alepha platform plan` — show project topology and resource names
|
|
@@ -2940,9 +713,10 @@ const platformPlanSchema = t.object({
|
|
|
2940
713
|
* - `alepha platform down` — teardown an environment
|
|
2941
714
|
* - `alepha platform status` — inspect deployed resources
|
|
2942
715
|
* - `alepha platform build` — build apps locally
|
|
2943
|
-
* - `alepha platform deploy`
|
|
2944
|
-
* - `alepha platform migrate` — run database migrations
|
|
2945
|
-
* - `alepha platform
|
|
716
|
+
* - `alepha platform deploy` — deploy to cloud
|
|
717
|
+
* - `alepha platform db migrate` — run database migrations
|
|
718
|
+
* - `alepha platform db export` — pull the deployed DB into a local snapshot
|
|
719
|
+
* - `alepha platform secrets` — manage external secret stores
|
|
2946
720
|
*
|
|
2947
721
|
* Configuration in `alepha.config.ts`:
|
|
2948
722
|
*
|
|
@@ -2964,21 +738,9 @@ const AlephaCliPlatformPlugin = $module({
|
|
|
2964
738
|
name: "alepha.cli.plugins.platform",
|
|
2965
739
|
services: [
|
|
2966
740
|
AlephaCli,
|
|
741
|
+
AlephaPlatformLibPlugin,
|
|
2967
742
|
PlatformCommand,
|
|
2968
|
-
SecretsCommand
|
|
2969
|
-
CloudflareAdapter,
|
|
2970
|
-
CloudflareApi,
|
|
2971
|
-
VercelAdapter,
|
|
2972
|
-
VercelApi,
|
|
2973
|
-
VercelCli,
|
|
2974
|
-
WranglerApi,
|
|
2975
|
-
PlatformCacheProvider,
|
|
2976
|
-
GitHubSecretStore,
|
|
2977
|
-
MemorySecretStore,
|
|
2978
|
-
NamingService,
|
|
2979
|
-
SecretFilterService,
|
|
2980
|
-
PlatformInspector,
|
|
2981
|
-
PlatformOrchestrator
|
|
743
|
+
SecretsCommand
|
|
2982
744
|
]
|
|
2983
745
|
});
|
|
2984
746
|
const platform = (options) => {
|
|
@@ -2992,6 +754,6 @@ const platform = (options) => {
|
|
|
2992
754
|
};
|
|
2993
755
|
};
|
|
2994
756
|
//#endregion
|
|
2995
|
-
export { AlephaCliPlatformPlugin,
|
|
757
|
+
export { AlephaCliPlatformPlugin, PlatformCommand, SecretsCommand, platform };
|
|
2996
758
|
|
|
2997
759
|
//# sourceMappingURL=index.js.map
|