@vibesdotdev/infra-cloudflare 0.0.1

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.
Files changed (128) hide show
  1. package/README.md +107 -0
  2. package/SPEC.md +166 -0
  3. package/dist/cloudflare.plugin.d.ts +73 -0
  4. package/dist/cloudflare.plugin.d.ts.map +1 -0
  5. package/dist/cloudflare.plugin.js +334 -0
  6. package/dist/cloudflare.plugin.js.map +1 -0
  7. package/dist/implementations/alerts.descriptor.d.ts +13 -0
  8. package/dist/implementations/alerts.descriptor.d.ts.map +1 -0
  9. package/dist/implementations/alerts.descriptor.js +30 -0
  10. package/dist/implementations/alerts.descriptor.js.map +1 -0
  11. package/dist/implementations/alerts.impl.d.ts +35 -0
  12. package/dist/implementations/alerts.impl.d.ts.map +1 -0
  13. package/dist/implementations/alerts.impl.js +283 -0
  14. package/dist/implementations/alerts.impl.js.map +1 -0
  15. package/dist/implementations/kv.impl.d.ts +29 -0
  16. package/dist/implementations/kv.impl.d.ts.map +1 -0
  17. package/dist/implementations/kv.impl.js +36 -0
  18. package/dist/implementations/kv.impl.js.map +1 -0
  19. package/dist/implementations/logs.descriptor.d.ts +15 -0
  20. package/dist/implementations/logs.descriptor.d.ts.map +1 -0
  21. package/dist/implementations/logs.descriptor.js +26 -0
  22. package/dist/implementations/logs.descriptor.js.map +1 -0
  23. package/dist/implementations/logs.impl.d.ts +108 -0
  24. package/dist/implementations/logs.impl.d.ts.map +1 -0
  25. package/dist/implementations/logs.impl.js +154 -0
  26. package/dist/implementations/logs.impl.js.map +1 -0
  27. package/dist/implementations/observability.descriptor.d.ts +9 -0
  28. package/dist/implementations/observability.descriptor.d.ts.map +1 -0
  29. package/dist/implementations/observability.descriptor.js +22 -0
  30. package/dist/implementations/observability.descriptor.js.map +1 -0
  31. package/dist/implementations/observability.impl.d.ts +35 -0
  32. package/dist/implementations/observability.impl.d.ts.map +1 -0
  33. package/dist/implementations/observability.impl.js +229 -0
  34. package/dist/implementations/observability.impl.js.map +1 -0
  35. package/dist/implementations/pages.impl.d.ts +98 -0
  36. package/dist/implementations/pages.impl.d.ts.map +1 -0
  37. package/dist/implementations/pages.impl.js +132 -0
  38. package/dist/implementations/pages.impl.js.map +1 -0
  39. package/dist/implementations/queues.impl.d.ts +29 -0
  40. package/dist/implementations/queues.impl.d.ts.map +1 -0
  41. package/dist/implementations/queues.impl.js +34 -0
  42. package/dist/implementations/queues.impl.js.map +1 -0
  43. package/dist/implementations/r2.impl.d.ts +31 -0
  44. package/dist/implementations/r2.impl.d.ts.map +1 -0
  45. package/dist/implementations/r2.impl.js +41 -0
  46. package/dist/implementations/r2.impl.js.map +1 -0
  47. package/dist/implementations/rum.descriptor.d.ts +13 -0
  48. package/dist/implementations/rum.descriptor.d.ts.map +1 -0
  49. package/dist/implementations/rum.descriptor.js +32 -0
  50. package/dist/implementations/rum.descriptor.js.map +1 -0
  51. package/dist/implementations/rum.impl.d.ts +34 -0
  52. package/dist/implementations/rum.impl.d.ts.map +1 -0
  53. package/dist/implementations/rum.impl.js +153 -0
  54. package/dist/implementations/rum.impl.js.map +1 -0
  55. package/dist/implementations/web-app.impl.d.ts +294 -0
  56. package/dist/implementations/web-app.impl.d.ts.map +1 -0
  57. package/dist/implementations/web-app.impl.js +208 -0
  58. package/dist/implementations/web-app.impl.js.map +1 -0
  59. package/dist/implementations/workers.impl.d.ts +157 -0
  60. package/dist/implementations/workers.impl.d.ts.map +1 -0
  61. package/dist/implementations/workers.impl.js +247 -0
  62. package/dist/implementations/workers.impl.js.map +1 -0
  63. package/dist/index.d.ts +17 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +12 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/pages.d.ts +9 -0
  68. package/dist/pages.d.ts.map +1 -0
  69. package/dist/pages.js +9 -0
  70. package/dist/pages.js.map +1 -0
  71. package/dist/regen.d.ts +58 -0
  72. package/dist/regen.d.ts.map +1 -0
  73. package/dist/regen.js +69 -0
  74. package/dist/regen.js.map +1 -0
  75. package/dist/secrets/cloudflare-api.descriptor.d.ts +18 -0
  76. package/dist/secrets/cloudflare-api.descriptor.d.ts.map +1 -0
  77. package/dist/secrets/cloudflare-api.descriptor.js +32 -0
  78. package/dist/secrets/cloudflare-api.descriptor.js.map +1 -0
  79. package/dist/secrets/cloudflare-api.impl.d.ts +30 -0
  80. package/dist/secrets/cloudflare-api.impl.d.ts.map +1 -0
  81. package/dist/secrets/cloudflare-api.impl.js +111 -0
  82. package/dist/secrets/cloudflare-api.impl.js.map +1 -0
  83. package/dist/secrets/cloudflare-secrets-store.descriptor.d.ts +10 -0
  84. package/dist/secrets/cloudflare-secrets-store.descriptor.d.ts.map +1 -0
  85. package/dist/secrets/cloudflare-secrets-store.descriptor.js +24 -0
  86. package/dist/secrets/cloudflare-secrets-store.descriptor.js.map +1 -0
  87. package/dist/secrets/cloudflare-secrets-store.impl.d.ts +27 -0
  88. package/dist/secrets/cloudflare-secrets-store.impl.d.ts.map +1 -0
  89. package/dist/secrets/cloudflare-secrets-store.impl.js +72 -0
  90. package/dist/secrets/cloudflare-secrets-store.impl.js.map +1 -0
  91. package/dist/secrets/index.d.ts +6 -0
  92. package/dist/secrets/index.d.ts.map +1 -0
  93. package/dist/secrets/index.js +6 -0
  94. package/dist/secrets/index.js.map +1 -0
  95. package/dist/secrets/resolve-cf-credentials.d.ts +18 -0
  96. package/dist/secrets/resolve-cf-credentials.d.ts.map +1 -0
  97. package/dist/secrets/resolve-cf-credentials.js +57 -0
  98. package/dist/secrets/resolve-cf-credentials.js.map +1 -0
  99. package/dist/web-app.d.ts +11 -0
  100. package/dist/web-app.d.ts.map +1 -0
  101. package/dist/web-app.js +11 -0
  102. package/dist/web-app.js.map +1 -0
  103. package/package.json +153 -0
  104. package/src/cloudflare.plugin.ts +477 -0
  105. package/src/implementations/alerts.descriptor.ts +33 -0
  106. package/src/implementations/alerts.impl.ts +332 -0
  107. package/src/implementations/kv.impl.ts +51 -0
  108. package/src/implementations/logs.descriptor.ts +29 -0
  109. package/src/implementations/logs.impl.ts +201 -0
  110. package/src/implementations/observability.descriptor.ts +25 -0
  111. package/src/implementations/observability.impl.ts +307 -0
  112. package/src/implementations/pages.impl.ts +189 -0
  113. package/src/implementations/queues.impl.ts +48 -0
  114. package/src/implementations/r2.impl.ts +58 -0
  115. package/src/implementations/rum.descriptor.ts +35 -0
  116. package/src/implementations/rum.impl.ts +192 -0
  117. package/src/implementations/web-app.impl.ts +494 -0
  118. package/src/implementations/workers.impl.ts +336 -0
  119. package/src/index.ts +60 -0
  120. package/src/pages.ts +18 -0
  121. package/src/regen.ts +87 -0
  122. package/src/secrets/cloudflare-api.descriptor.ts +35 -0
  123. package/src/secrets/cloudflare-api.impl.ts +131 -0
  124. package/src/secrets/cloudflare-secrets-store.descriptor.ts +27 -0
  125. package/src/secrets/cloudflare-secrets-store.impl.ts +87 -0
  126. package/src/secrets/index.ts +13 -0
  127. package/src/secrets/resolve-cf-credentials.ts +63 -0
  128. package/src/web-app.ts +32 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Cloudflare Workers Implementation
3
+ *
4
+ * Config generator for `infra/worker` kind when adapter is `cloudflare-workers`.
5
+ * Takes an infra descriptor and outputs a wrangler config for a CF Worker
6
+ * that consumes from CF Queues.
7
+ */
8
+
9
+ import * as z from 'zod/v4';
10
+
11
+ const QueueConsumerSchema = z.object({
12
+ queue: z.string().min(1),
13
+ maxBatchSize: z.number().int().positive().default(10),
14
+ maxRetries: z.number().int().min(0).default(3),
15
+ deadLetterQueue: z.string().optional()
16
+ });
17
+
18
+ export const CloudflareWorkerDescriptorSchema = z.object({
19
+ kind: z.literal('infra/worker'),
20
+ id: z.string().min(1),
21
+ name: z.string().min(1),
22
+ adapter: z.literal('cloudflare-workers'),
23
+ /** Wrangler worker name (e.g. "vibes-billing-worker") */
24
+ workerName: z.string().min(1),
25
+ /** Entry point for the worker */
26
+ main: z.string().min(1).default('src/worker.ts'),
27
+ compatibilityDate: z
28
+ .string()
29
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
30
+ .optional(),
31
+ compatibilityFlags: z.array(z.string()).default(['nodejs_compat']),
32
+ /** Environment variables */
33
+ vars: z.record(z.string(), z.string()).default({}),
34
+ /** Queue consumers this worker processes */
35
+ consumers: z.array(QueueConsumerSchema).default([]),
36
+ /** KV namespace bindings */
37
+ kvNamespaces: z
38
+ .array(
39
+ z.object({
40
+ binding: z.string().min(1),
41
+ id: z.string().min(1)
42
+ })
43
+ )
44
+ .default([]),
45
+ /** R2 bucket bindings */
46
+ r2Buckets: z
47
+ .array(
48
+ z.object({
49
+ binding: z.string().min(1),
50
+ bucketName: z.string().min(1)
51
+ })
52
+ )
53
+ .default([]),
54
+ /** Queue producer bindings (worker can also produce to queues) */
55
+ queueProducers: z
56
+ .array(
57
+ z.object({
58
+ queue: z.string().min(1),
59
+ binding: z.string().min(1)
60
+ })
61
+ )
62
+ .default([]),
63
+ /** Durable Object class bindings */
64
+ durableObjects: z
65
+ .array(
66
+ z.object({
67
+ binding: z.string().regex(/^[_A-Z0-9]+$/),
68
+ className: z.string().min(1),
69
+ /** Cross-script DO bindings; omit when the DO class is in this same worker */
70
+ scriptName: z.string().min(1).optional()
71
+ })
72
+ )
73
+ .default([]),
74
+ /**
75
+ * Durable Object migrations. Each entry corresponds to one
76
+ * `[[migrations]]` block in wrangler.toml. The first deploy of any new
77
+ * DO class needs `newClasses: ['ClassName']` under a fresh tag; later
78
+ * renames/deletes append further migration entries.
79
+ */
80
+ migrations: z
81
+ .array(
82
+ z.object({
83
+ tag: z.string().min(1),
84
+ newClasses: z.array(z.string().min(1)).optional(),
85
+ renamedClasses: z
86
+ .array(z.object({ from: z.string().min(1), to: z.string().min(1) }))
87
+ .optional(),
88
+ deletedClasses: z.array(z.string().min(1)).optional(),
89
+ newSqliteClasses: z.array(z.string().min(1)).optional()
90
+ })
91
+ )
92
+ .default([]),
93
+ /**
94
+ * Service bindings — call other Workers by name from this Worker via
95
+ * `env.<binding>.fetch(...)`.
96
+ */
97
+ services: z
98
+ .array(
99
+ z.object({
100
+ binding: z.string().regex(/^[_A-Z0-9]+$/),
101
+ service: z.string().min(1),
102
+ environment: z.string().min(1).optional()
103
+ })
104
+ )
105
+ .default([]),
106
+ /**
107
+ * D1 database bindings — `env.<binding>` exposes a D1 client.
108
+ */
109
+ d1Databases: z
110
+ .array(
111
+ z.object({
112
+ binding: z.string().regex(/^[_A-Z0-9]+$/),
113
+ databaseName: z.string().min(1),
114
+ databaseId: z.string().min(1)
115
+ })
116
+ )
117
+ .default([]),
118
+ /** Enable smart placement */
119
+ smartPlacement: z.boolean().default(false)
120
+ });
121
+
122
+ export type CloudflareWorkerDescriptorInput = z.input<typeof CloudflareWorkerDescriptorSchema>;
123
+ export type CloudflareWorkerDescriptor = z.infer<typeof CloudflareWorkerDescriptorSchema>;
124
+
125
+ export interface WranglerWorkerConfig {
126
+ $schema?: string;
127
+ name: string;
128
+ main: string;
129
+ compatibility_date: string;
130
+ compatibility_flags: string[];
131
+ vars?: Record<string, string>;
132
+ placement?: { mode: string };
133
+ queues?: {
134
+ consumers?: Array<{
135
+ queue: string;
136
+ max_batch_size: number;
137
+ max_retries: number;
138
+ dead_letter_queue?: string;
139
+ }>;
140
+ producers?: Array<{ queue: string; binding: string }>;
141
+ };
142
+ kv_namespaces?: Array<{ binding: string; id: string }>;
143
+ r2_buckets?: Array<{ binding: string; bucket_name: string }>;
144
+ durable_objects?: {
145
+ bindings: Array<{ name: string; class_name: string; script_name?: string }>;
146
+ };
147
+ migrations?: Array<{
148
+ tag: string;
149
+ new_classes?: string[];
150
+ renamed_classes?: Array<{ from: string; to: string }>;
151
+ deleted_classes?: string[];
152
+ new_sqlite_classes?: string[];
153
+ }>;
154
+ services?: Array<{ binding: string; service: string; environment?: string }>;
155
+ d1_databases?: Array<{ binding: string; database_name: string; database_id: string }>;
156
+ }
157
+
158
+ /**
159
+ * Generate a wrangler config from a Workers descriptor.
160
+ */
161
+ export function generateCloudflareWorkerConfig(
162
+ descriptor: CloudflareWorkerDescriptor
163
+ ): WranglerWorkerConfig {
164
+ const parsed = CloudflareWorkerDescriptorSchema.parse(descriptor);
165
+ const compatibilityDate = parsed.compatibilityDate ?? new Date().toISOString().slice(0, 10);
166
+
167
+ const config: WranglerWorkerConfig = {
168
+ $schema: 'node_modules/wrangler/config-schema.json',
169
+ name: parsed.workerName,
170
+ main: parsed.main,
171
+ compatibility_date: compatibilityDate,
172
+ compatibility_flags: parsed.compatibilityFlags
173
+ };
174
+
175
+ if (Object.keys(parsed.vars).length > 0) {
176
+ config.vars = parsed.vars;
177
+ }
178
+
179
+ if (parsed.smartPlacement) {
180
+ config.placement = { mode: 'smart' };
181
+ }
182
+
183
+ const queues: WranglerWorkerConfig['queues'] = {};
184
+
185
+ if (parsed.consumers.length > 0) {
186
+ queues.consumers = parsed.consumers.map((c) => {
187
+ const consumer: {
188
+ queue: string;
189
+ max_batch_size: number;
190
+ max_retries: number;
191
+ dead_letter_queue?: string;
192
+ } = {
193
+ queue: c.queue,
194
+ max_batch_size: c.maxBatchSize,
195
+ max_retries: c.maxRetries
196
+ };
197
+ if (c.deadLetterQueue) {
198
+ consumer.dead_letter_queue = c.deadLetterQueue;
199
+ }
200
+ return consumer;
201
+ });
202
+ }
203
+
204
+ if (parsed.queueProducers.length > 0) {
205
+ queues.producers = parsed.queueProducers.map((p) => ({
206
+ queue: p.queue,
207
+ binding: p.binding
208
+ }));
209
+ }
210
+
211
+ if (queues.consumers || queues.producers) {
212
+ config.queues = queues;
213
+ }
214
+
215
+ if (parsed.kvNamespaces.length > 0) {
216
+ config.kv_namespaces = parsed.kvNamespaces.map((ns) => ({
217
+ binding: ns.binding,
218
+ id: ns.id
219
+ }));
220
+ }
221
+
222
+ if (parsed.r2Buckets.length > 0) {
223
+ config.r2_buckets = parsed.r2Buckets.map((bucket) => ({
224
+ binding: bucket.binding,
225
+ bucket_name: bucket.bucketName
226
+ }));
227
+ }
228
+
229
+ if (parsed.durableObjects.length > 0) {
230
+ config.durable_objects = {
231
+ bindings: parsed.durableObjects.map((binding) => {
232
+ const out: { name: string; class_name: string; script_name?: string } = {
233
+ name: binding.binding,
234
+ class_name: binding.className
235
+ };
236
+ if (binding.scriptName) out.script_name = binding.scriptName;
237
+ return out;
238
+ })
239
+ };
240
+ }
241
+
242
+ if (parsed.migrations.length > 0) {
243
+ config.migrations = parsed.migrations.map((migration) => {
244
+ const out: {
245
+ tag: string;
246
+ new_classes?: string[];
247
+ renamed_classes?: Array<{ from: string; to: string }>;
248
+ deleted_classes?: string[];
249
+ new_sqlite_classes?: string[];
250
+ } = { tag: migration.tag };
251
+ if (migration.newClasses?.length) out.new_classes = migration.newClasses;
252
+ if (migration.renamedClasses?.length) out.renamed_classes = migration.renamedClasses;
253
+ if (migration.deletedClasses?.length) out.deleted_classes = migration.deletedClasses;
254
+ if (migration.newSqliteClasses?.length) out.new_sqlite_classes = migration.newSqliteClasses;
255
+ return out;
256
+ });
257
+ }
258
+
259
+ if (parsed.services.length > 0) {
260
+ config.services = parsed.services.map((service) => {
261
+ const out: { binding: string; service: string; environment?: string } = {
262
+ binding: service.binding,
263
+ service: service.service
264
+ };
265
+ if (service.environment) out.environment = service.environment;
266
+ return out;
267
+ });
268
+ }
269
+
270
+ if (parsed.d1Databases.length > 0) {
271
+ config.d1_databases = parsed.d1Databases.map((db) => ({
272
+ binding: db.binding,
273
+ database_name: db.databaseName,
274
+ database_id: db.databaseId
275
+ }));
276
+ }
277
+
278
+ return config;
279
+ }
280
+
281
+ /**
282
+ * Convention defaults for cloud Flow job consumers.
283
+ *
284
+ * The `workers` runtime cloud impl rendezvous protocol expects a Durable
285
+ * Object class named `JobResultStore` exported from the consumer worker
286
+ * and exposed under the binding `JOB_RESULT_STORE`. Apps shouldn't
287
+ * re-derive this every time — call `generateJobConsumerWorkerConfig`
288
+ * and the convention is filled in.
289
+ */
290
+ export const JOB_RESULT_STORE_BINDING = 'JOB_RESULT_STORE';
291
+ export const JOB_RESULT_STORE_CLASS = 'JobResultStore';
292
+ export const JOB_RESULT_STORE_INITIAL_MIGRATION_TAG = 'v1-job-result-store';
293
+
294
+ export type JobConsumerWorkerInput = Omit<
295
+ CloudflareWorkerDescriptorInput,
296
+ 'durableObjects' | 'migrations'
297
+ > & {
298
+ /** Extra DO bindings beyond the JOB_RESULT_STORE convention. */
299
+ extraDurableObjects?: NonNullable<CloudflareWorkerDescriptorInput['durableObjects']>;
300
+ /** Extra migrations appended after the JobResultStore registration. */
301
+ extraMigrations?: NonNullable<CloudflareWorkerDescriptorInput['migrations']>;
302
+ };
303
+
304
+ /** Re-exported convention bindings for use in tests + descriptor declarations. */
305
+
306
+ /**
307
+ * Generate a wrangler config for a worker that consumes cloud Flow jobs.
308
+ *
309
+ * Injects the `JOB_RESULT_STORE` → `JobResultStore` DO binding and the
310
+ * initial migration that registers `JobResultStore` as a SQLite-backed
311
+ * Durable Object (the modern default — required for new DO classes on
312
+ * accounts created after 2024-04-01). Callers may add extra DO bindings
313
+ * and migrations via `extraDurableObjects` / `extraMigrations`.
314
+ */
315
+ export function generateJobConsumerWorkerConfig(
316
+ input: JobConsumerWorkerInput
317
+ ): WranglerWorkerConfig {
318
+ const { extraDurableObjects = [], extraMigrations = [], ...rest } = input;
319
+ return generateCloudflareWorkerConfig({
320
+ ...rest,
321
+ durableObjects: [
322
+ {
323
+ binding: JOB_RESULT_STORE_BINDING,
324
+ className: JOB_RESULT_STORE_CLASS
325
+ },
326
+ ...extraDurableObjects
327
+ ],
328
+ migrations: [
329
+ {
330
+ tag: JOB_RESULT_STORE_INITIAL_MIGRATION_TAG,
331
+ newSqliteClasses: [JOB_RESULT_STORE_CLASS]
332
+ },
333
+ ...extraMigrations
334
+ ]
335
+ } as CloudflareWorkerDescriptor);
336
+ }
package/src/index.ts ADDED
@@ -0,0 +1,60 @@
1
+ export {
2
+ CloudflareWorkerDescriptorSchema,
3
+ generateCloudflareWorkerConfig,
4
+ JOB_RESULT_STORE_BINDING,
5
+ JOB_RESULT_STORE_CLASS,
6
+ JOB_RESULT_STORE_INITIAL_MIGRATION_TAG,
7
+ generateJobConsumerWorkerConfig
8
+ } from './implementations/workers.impl';
9
+ export type {
10
+ CloudflareWorkerDescriptorInput,
11
+ CloudflareWorkerDescriptor,
12
+ WranglerWorkerConfig,
13
+ JobConsumerWorkerInput
14
+ } from './implementations/workers.impl';
15
+ export {
16
+ CloudflareLogsDescriptorSchema,
17
+ createCloudflareLogsImplementation,
18
+ type CloudflareLogsDescriptorInput,
19
+ type CloudflareLogsDescriptor,
20
+ type LogsImplementation
21
+ } from './implementations/logs.impl';
22
+ export {
23
+ CloudflareQueueDescriptorSchema,
24
+ generateCloudflareQueueConfig
25
+ } from './implementations/queues.impl';
26
+ export type { CloudflareQueueDescriptor, WranglerQueueProducerConfig } from './implementations/queues.impl';
27
+ export {
28
+ CloudflareR2DescriptorSchema,
29
+ generateCloudflareR2Config
30
+ } from './implementations/r2.impl';
31
+ export type { CloudflareR2Descriptor, WranglerR2Config } from './implementations/r2.impl';
32
+ export {
33
+ CloudflareKVDescriptorSchema,
34
+ generateCloudflareKVConfig
35
+ } from './implementations/kv.impl';
36
+ export type { CloudflareKVDescriptor, WranglerKVConfig } from './implementations/kv.impl';
37
+ export {
38
+ CloudflarePagesDescriptorSchema,
39
+ createCloudflarePagesDeployment,
40
+ renderWranglerJson,
41
+ generateCloudflarePagesDescriptor
42
+ } from './implementations/pages.impl';
43
+ export type {
44
+ CloudflarePagesDescriptor,
45
+ CloudflarePagesDescriptorInput,
46
+ CloudflarePagesDeployment,
47
+ WranglerSecretsStoreBinding,
48
+ WranglerPagesConfig
49
+ } from './implementations/pages.impl';
50
+ export {
51
+ cloudflareApiDescriptor,
52
+ createCloudflareApiStore
53
+ } from './secrets/index.ts';
54
+ export { cloudflareApiDescriptor as cloudflareApiDescriptorDefault } from './secrets/index.ts';
55
+ export {
56
+ cloudflareSecretsStoreDescriptor,
57
+ createCloudflareSecretsStore
58
+ } from './secrets/index.ts';
59
+ export { cloudflareSecretsStoreDescriptor as cloudflareSecretsStoreDescriptorDefault } from './secrets/index.ts';
60
+ export { default as cloudflarePlugin } from './cloudflare.plugin';
package/src/pages.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Cloudflare Pages Deployment Module
3
+ *
4
+ * Public entrypoint for Cloudflare Pages deployment helpers.
5
+ * Re-exports the Pages implementation for creating wrangler configs
6
+ * and rendering deployment artifacts.
7
+ */
8
+
9
+ export {
10
+ createCloudflarePagesDeployment,
11
+ renderWranglerJson,
12
+ generateCloudflarePagesDescriptor,
13
+ CloudflarePagesDescriptorSchema,
14
+ type CloudflarePagesDeployment,
15
+ type CloudflarePagesDescriptor,
16
+ type CloudflarePagesDescriptorInput,
17
+ type WranglerPagesConfig
18
+ } from './implementations/pages.impl';
package/src/regen.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Cloudflare wrangler.jsonc regeneration.
3
+ *
4
+ * Pure render layer for the deploy-pipeline regen check. Given a
5
+ * `CloudflareWebAppDeployment` (the default export of a managed
6
+ * `apps/<name>/deployment.config.ts`), produces the canonical
7
+ * wrangler.jsonc string.
8
+ *
9
+ * The render is intentionally separate from filesystem I/O — the
10
+ * `@vibesdotdev/infra-deploy` orchestrator owns reading the on-disk
11
+ * wrangler.jsonc, diffing, and writing. This module's only job is
12
+ * to turn typed config → canonical JSONC string.
13
+ *
14
+ * Subprocess use: this module is dynamically imported from inside
15
+ * the `infra-deploy/config-loader` subprocess so the rendered string
16
+ * can be returned to the orchestrator without requiring infra-deploy
17
+ * to take a static dep on infra-cloudflare.
18
+ */
19
+
20
+ import type { CloudflareWebAppDeployment } from './implementations/web-app.impl';
21
+ import { renderWranglerJson } from './implementations/pages.impl';
22
+
23
+ /**
24
+ * Canonical header for Workers + Static Assets wrangler.jsonc.
25
+ * Includes the "(Workers + assets shape, per deployment.config.ts)"
26
+ * qualifier to distinguish it from Pages output.
27
+ */
28
+ export const WEB_APP_HEADER_LINES = [
29
+ 'Generated by @vibesdotdev/infra-cloudflare (Workers + assets shape, per deployment.config.ts)',
30
+ 'https://developers.cloudflare.com/workers/wrangler/configuration/'
31
+ ];
32
+
33
+ export interface WranglerRenderResult {
34
+ /** True iff the deployment opted in via `managed: true`. */
35
+ managed: boolean;
36
+ /**
37
+ * Canonical wrangler.jsonc string. Null when the source isn't a
38
+ * managed Cloudflare web-app deployment (regen skipped).
39
+ */
40
+ rendered: string | null;
41
+ /** Workers script name, for logging. */
42
+ workerName: string | null;
43
+ }
44
+
45
+ /**
46
+ * Structural type guard for the wrapper. Avoids importing classes —
47
+ * any object with `generateWranglerConfig`, `workerName`, and `managed`
48
+ * is treated as a `CloudflareWebAppDeployment`. The cost of a false
49
+ * positive is a render attempt that throws, which the caller catches.
50
+ */
51
+ export function isCloudflareWebAppDeployment(x: unknown): x is CloudflareWebAppDeployment {
52
+ if (!x || typeof x !== 'object') return false;
53
+ const obj = x as Record<string, unknown>;
54
+ return (
55
+ typeof obj.generateWranglerConfig === 'function' &&
56
+ typeof obj.workerName === 'string' &&
57
+ typeof obj.managed === 'boolean'
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Render the canonical wrangler.jsonc string for a managed web-app
63
+ * deployment. Always uses `WEB_APP_HEADER_LINES`; rendered output is
64
+ * tab-indented JSON with a trailing newline.
65
+ */
66
+ export function renderManagedWranglerJsonc(webAppDeployment: CloudflareWebAppDeployment): string {
67
+ const config = webAppDeployment.generateWranglerConfig() as unknown as Record<string, unknown>;
68
+ return renderWranglerJson(config, { headerLines: WEB_APP_HEADER_LINES });
69
+ }
70
+
71
+ /**
72
+ * Inspect the default export of a `deployment.config.ts` module and
73
+ * — when it's a managed Cloudflare web-app — return the rendered
74
+ * wrangler.jsonc string. Designed to be called from a subprocess
75
+ * loader; never throws on a non-managed export, just returns
76
+ * `{ managed: false, rendered: null, workerName: null }`.
77
+ */
78
+ export function renderWranglerJsoncFromDefaultExport(raw: unknown): WranglerRenderResult {
79
+ if (!isCloudflareWebAppDeployment(raw)) {
80
+ return { managed: false, rendered: null, workerName: null };
81
+ }
82
+ return {
83
+ managed: raw.managed,
84
+ rendered: raw.managed ? renderManagedWranglerJsonc(raw) : null,
85
+ workerName: raw.workerName
86
+ };
87
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Cloudflare API Backend Descriptor
3
+ *
4
+ * Pushes secrets to Cloudflare Pages/Workers via the REST API.
5
+ * Write-only: CF does not expose secret values after setting.
6
+ */
7
+
8
+ import type { SecretsStoreDescriptor } from '@vibesdotdev/secrets/kinds/store.schema';
9
+
10
+ /**
11
+ * Cloudflare API secrets backend descriptor.
12
+ *
13
+ * @remarks
14
+ * Project names can be provided via descriptor.config.cfProjectNames.
15
+ * If not provided, the implementation will attempt to derive them from
16
+ * runtime-resolved infra/web-app descriptors (requires runtime context).
17
+ */
18
+ const descriptor: SecretsStoreDescriptor = {
19
+ id: 'cloudflare-api',
20
+ kind: 'secrets/store',
21
+ name: 'Cloudflare API',
22
+ description: 'Push secrets to Cloudflare Pages and Workers via REST API',
23
+ tags: ['remote', 'cloudflare'],
24
+ enabled: true,
25
+ backend: 'cloudflare-api',
26
+ tiers: ['staging', 'production'],
27
+ priority: 20,
28
+ config: {
29
+ apiTokenEnvVar: 'CLOUDFLARE_API_TOKEN',
30
+ accountIdEnvVar: 'CLOUDFLARE_ACCOUNT_ID'
31
+ // cfProjectNames: optional string[] - omitted to allow runtime resolution
32
+ }
33
+ };
34
+
35
+ export default descriptor;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Cloudflare API Backend Implementation
3
+ *
4
+ * Manages secrets across CF Pages projects and Workers scripts.
5
+ * Maps infra/web-app descriptors to CF project names for push operations.
6
+ *
7
+ * Note: CF secrets are write-only. get() and getAll() return empty results.
8
+ */
9
+
10
+ import { createRuntimeImplementation } from '@vibesdotdev/runtime/factory/implementation';
11
+ import type { SecretsStoreDescriptor } from '@vibesdotdev/secrets/kinds/store.schema';
12
+ import type { SecretsStoreImplementation, SecretEntry } from '@vibesdotdev/secrets/kinds/store.interface';
13
+ import { CloudflareSecretsManagerConnector } from '@vibesdotdev/connector-cloudflare';
14
+ import descriptor from './cloudflare-api.descriptor';
15
+ import { resolveCfCredentialKeys } from './resolve-cf-credentials';
16
+
17
+ /** Function type for resolving CF project names from descriptors */
18
+ type CFProjectNamesResolver = () => string[];
19
+
20
+ class CloudflareApiStore implements SecretsStoreImplementation {
21
+ readonly id = 'cloudflare-api';
22
+ readonly descriptor: SecretsStoreDescriptor;
23
+ private readonly cfProjectNamesResolver?: CFProjectNamesResolver;
24
+ private connector: CloudflareSecretsManagerConnector | null = null;
25
+
26
+ constructor(desc: SecretsStoreDescriptor, projectNamesResolver?: CFProjectNamesResolver) {
27
+ this.descriptor = desc;
28
+ this.cfProjectNamesResolver = projectNamesResolver;
29
+ }
30
+
31
+ private async getConnector(): Promise<CloudflareSecretsManagerConnector> {
32
+ if (this.connector) return this.connector;
33
+
34
+ const tokenVar = this.descriptor.config?.apiTokenEnvVar ?? 'CLOUDFLARE_API_TOKEN';
35
+ const accountVar = this.descriptor.config?.accountIdEnvVar ?? 'CLOUDFLARE_ACCOUNT_ID';
36
+
37
+ const envReader = await resolveCfCredentialKeys([tokenVar, accountVar]);
38
+
39
+ this.connector = await CloudflareSecretsManagerConnector.create({
40
+ accountId: { source: 'env', key: accountVar },
41
+ apiToken: { source: 'env', key: tokenVar }
42
+ }, {
43
+ environment: 'production',
44
+ envReader
45
+ });
46
+
47
+ return this.connector;
48
+ }
49
+
50
+ async list(_environment: string): Promise<SecretEntry[]> {
51
+ const connector = await this.getConnector();
52
+ const entries: SecretEntry[] = [];
53
+
54
+ try {
55
+ const projectNames = this.getCFProjectNames();
56
+ for (const projectName of projectNames) {
57
+ try {
58
+ const names = await connector.list(projectName);
59
+ for (const key of names) {
60
+ entries.push({ key, hasValue: true, source: this.id });
61
+ }
62
+ } catch {
63
+ // skip unreachable projects
64
+ }
65
+ }
66
+ } catch (e) {
67
+ console.warn(`[cloudflare-api] Failed to list secrets: ${e}`);
68
+ }
69
+
70
+ return entries;
71
+ }
72
+
73
+ async get(_environment: string, _key: string): Promise<string | undefined> {
74
+ return undefined; // CF does not return secret values
75
+ }
76
+
77
+ async set(environment: string, key: string, value: string): Promise<void> {
78
+ await this.setAll(environment, { [key]: value });
79
+ }
80
+
81
+ async unset(_environment: string, _key: string): Promise<void> {
82
+ console.warn('[cloudflare-api] CF does not support deleting individual secrets. Re-push all secrets to update.');
83
+ }
84
+
85
+ async getAll(_environment: string): Promise<Record<string, string>> {
86
+ return {}; // CF does not return secret values
87
+ }
88
+
89
+ async setAll(_environment: string, secrets: Record<string, string>): Promise<void> {
90
+ const connector = await this.getConnector();
91
+ const projectNames = this.getCFProjectNames();
92
+
93
+ for (const projectName of projectNames) {
94
+ try {
95
+ await connector.set(secrets, projectName);
96
+ } catch (e) {
97
+ console.warn(`[cloudflare-api] Failed to push to ${projectName}: ${e}`);
98
+ }
99
+ }
100
+ }
101
+
102
+ private getCFProjectNames(): string[] {
103
+ // First priority: explicit project names from descriptor config
104
+ const explicitNames = this.descriptor.config?.cfProjectNames as string[] | undefined;
105
+ if (explicitNames && explicitNames.length > 0) {
106
+ return explicitNames;
107
+ }
108
+
109
+ // Second priority: runtime-resolved project names via injected resolver
110
+ if (this.cfProjectNamesResolver) {
111
+ try {
112
+ return this.cfProjectNamesResolver();
113
+ } catch {
114
+ // Resolver failed, fall through to empty
115
+ }
116
+ }
117
+
118
+ // No project names available
119
+ return [];
120
+ }
121
+ }
122
+
123
+ export function createCloudflareApiStore(projectNamesResolver?: CFProjectNamesResolver) {
124
+ const store = new CloudflareApiStore(descriptor, projectNamesResolver);
125
+ return createRuntimeImplementation({
126
+ id: 'cloudflare-api',
127
+ kind: 'secrets/store',
128
+ priority: 20,
129
+ implementation: store
130
+ });
131
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Cloudflare Secrets Store Backend Descriptor
3
+ *
4
+ * Account-level secrets, bound to Workers via wrangler `secrets_store_secrets`.
5
+ * Distinct from `cloudflare-api`, which writes per-Worker / per-Pages secrets.
6
+ */
7
+
8
+ import type { SecretsStoreDescriptor } from '@vibesdotdev/secrets/kinds/store.schema';
9
+
10
+ const descriptor: SecretsStoreDescriptor = {
11
+ id: 'cloudflare-secrets-store',
12
+ kind: 'secrets/store',
13
+ name: 'Cloudflare Secrets Store',
14
+ description: 'Account-level Cloudflare Secrets Store; bound to Workers via secrets_store_secrets',
15
+ tags: ['remote', 'cloudflare', 'shared'],
16
+ enabled: true,
17
+ backend: 'cloudflare-secrets-store',
18
+ tiers: ['staging', 'production'],
19
+ priority: 30,
20
+ config: {
21
+ apiTokenEnvVar: 'CLOUDFLARE_API_TOKEN',
22
+ accountIdEnvVar: 'CLOUDFLARE_ACCOUNT_ID',
23
+ storeIdEnvVar: 'CLOUDFLARE_SECRETS_STORE_ID'
24
+ }
25
+ };
26
+
27
+ export default descriptor;