alchemy 0.40.1 → 0.41.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.
Files changed (71) hide show
  1. package/lib/build-date.d.ts +1 -1
  2. package/lib/build-date.js +1 -1
  3. package/lib/cloudflare/bindings.d.ts +2 -1
  4. package/lib/cloudflare/bindings.d.ts.map +1 -1
  5. package/lib/cloudflare/bindings.js.map +1 -1
  6. package/lib/cloudflare/bound.d.ts +2 -1
  7. package/lib/cloudflare/bound.d.ts.map +1 -1
  8. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
  9. package/lib/cloudflare/bundle/bundle-worker-dev.js +2 -1
  10. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
  11. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  12. package/lib/cloudflare/bundle/bundle-worker.js +2 -1
  13. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  14. package/lib/cloudflare/container.d.ts +509 -0
  15. package/lib/cloudflare/container.d.ts.map +1 -0
  16. package/lib/cloudflare/container.js +274 -0
  17. package/lib/cloudflare/container.js.map +1 -0
  18. package/lib/cloudflare/index.d.ts +1 -0
  19. package/lib/cloudflare/index.d.ts.map +1 -1
  20. package/lib/cloudflare/index.js +1 -0
  21. package/lib/cloudflare/index.js.map +1 -1
  22. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +1 -1
  23. package/lib/cloudflare/worker/miniflare-worker-options.js +28 -0
  24. package/lib/cloudflare/worker/miniflare-worker-options.js.map +1 -1
  25. package/lib/cloudflare/worker-metadata.d.ts +3 -0
  26. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  27. package/lib/cloudflare/worker-metadata.js +22 -3
  28. package/lib/cloudflare/worker-metadata.js.map +1 -1
  29. package/lib/cloudflare/worker.d.ts +2 -1
  30. package/lib/cloudflare/worker.d.ts.map +1 -1
  31. package/lib/cloudflare/worker.js +39 -3
  32. package/lib/cloudflare/worker.js.map +1 -1
  33. package/lib/cloudflare/wrangler.json.js +11 -0
  34. package/lib/cloudflare/wrangler.json.js.map +1 -1
  35. package/lib/docker/api.d.ts +15 -0
  36. package/lib/docker/api.d.ts.map +1 -1
  37. package/lib/docker/api.js +59 -0
  38. package/lib/docker/api.js.map +1 -1
  39. package/lib/docker/image.d.ts +22 -4
  40. package/lib/docker/image.d.ts.map +1 -1
  41. package/lib/docker/image.js +68 -24
  42. package/lib/docker/image.js.map +1 -1
  43. package/lib/github/index.d.ts +1 -0
  44. package/lib/github/index.d.ts.map +1 -1
  45. package/lib/github/index.js +1 -0
  46. package/lib/github/index.js.map +1 -1
  47. package/lib/github/repository-webhook.d.ts +123 -0
  48. package/lib/github/repository-webhook.d.ts.map +1 -0
  49. package/lib/github/repository-webhook.js +146 -0
  50. package/lib/github/repository-webhook.js.map +1 -0
  51. package/lib/scope.d.ts +2 -0
  52. package/lib/scope.d.ts.map +1 -1
  53. package/lib/scope.js +4 -0
  54. package/lib/scope.js.map +1 -1
  55. package/package.json +3 -2
  56. package/src/build-date.ts +1 -1
  57. package/src/cloudflare/bindings.ts +2 -0
  58. package/src/cloudflare/bound.ts +9 -1
  59. package/src/cloudflare/bundle/bundle-worker-dev.ts +2 -1
  60. package/src/cloudflare/bundle/bundle-worker.ts +2 -1
  61. package/src/cloudflare/container.ts +843 -0
  62. package/src/cloudflare/index.ts +1 -0
  63. package/src/cloudflare/worker/miniflare-worker-options.ts +28 -0
  64. package/src/cloudflare/worker-metadata.ts +28 -6
  65. package/src/cloudflare/worker.ts +67 -16
  66. package/src/cloudflare/wrangler.json.ts +12 -0
  67. package/src/docker/api.ts +74 -0
  68. package/src/docker/image.ts +99 -29
  69. package/src/github/index.ts +1 -0
  70. package/src/github/repository-webhook.ts +269 -0
  71. package/src/scope.ts +5 -0
@@ -0,0 +1,843 @@
1
+ import type { Context } from "../context.ts";
2
+ import { Image, type ImageProps, type ImageRegistry } from "../docker/image.ts";
3
+ import { Resource } from "../resource.ts";
4
+ import { secret } from "../secret.ts";
5
+ import {
6
+ type CloudflareApi,
7
+ type CloudflareApiOptions,
8
+ createCloudflareApi,
9
+ } from "./api.ts";
10
+
11
+ export interface ContainerProps
12
+ extends Omit<ImageProps, "registry" | "skipPush">,
13
+ Partial<CloudflareApiOptions> {
14
+ className: string;
15
+ maxInstances?: number;
16
+ scriptName?: string;
17
+ instanceType?: InstanceType;
18
+ observability?: DeploymentObservability;
19
+ schedulingPolicy?: SchedulingPolicy;
20
+ }
21
+
22
+ /**
23
+ * @see https://developers.cloudflare.com/containers/pricing/
24
+ */
25
+ export type InstanceType = "dev" | "basic" | "standard" | (string & {});
26
+
27
+ export function isContainer(binding: any): binding is Container {
28
+ return binding.type === "container";
29
+ }
30
+
31
+ export type Container<T = any> = {
32
+ type: "container";
33
+ id: string;
34
+ name?: string;
35
+ className: string;
36
+ image: Image;
37
+ maxInstances?: number;
38
+ scriptName?: string;
39
+ sqlite?: true;
40
+ instanceType?: InstanceType;
41
+ observability?: DeploymentObservability;
42
+ schedulingPolicy?: SchedulingPolicy;
43
+ /**
44
+ * @internal
45
+ */
46
+ __phantom?: T;
47
+ };
48
+
49
+ export async function Container<T>(
50
+ id: string,
51
+ props: ContainerProps,
52
+ ): Promise<Container<T>> {
53
+ // Otherwise, obtain Cloudflare registry credentials automatically
54
+ const api = await createCloudflareApi(props);
55
+ const creds = await getContainerCredentials(api);
56
+
57
+ const registry: ImageRegistry = {
58
+ server: "registry.cloudflare.com",
59
+ username: creds.username || creds.user!,
60
+ password: secret(creds.password),
61
+ };
62
+
63
+ // Ensure repository name is namespaced with accountId
64
+ const repoBase = props.name ?? id;
65
+ const repoName = repoBase.includes("/")
66
+ ? repoBase
67
+ : `${api.accountId}/${repoBase}`;
68
+
69
+ // Replace disallowed "latest" tag with timestamp
70
+ const finalTag =
71
+ props.tag === undefined || props.tag === "latest"
72
+ ? `latest-${Date.now()}`
73
+ : props.tag;
74
+
75
+ const image = await Image(id, {
76
+ build: props.build,
77
+ name: repoName,
78
+ tag: finalTag,
79
+ skipPush: false,
80
+ registry,
81
+ });
82
+
83
+ return {
84
+ type: "container",
85
+ id,
86
+ name: props.name ?? id,
87
+ className: props.className,
88
+ image,
89
+ maxInstances: props.maxInstances,
90
+ scriptName: props.scriptName,
91
+ instanceType: props.instanceType,
92
+ observability: props.observability,
93
+ schedulingPolicy: props.schedulingPolicy,
94
+ sqlite: true,
95
+ };
96
+ }
97
+
98
+ export interface ContainerApplicationRollout {
99
+ strategy: "rolling";
100
+ kind?: "full_auto";
101
+ stepPercentage: number;
102
+ targetConfiguration: {
103
+ image: string;
104
+ instance_type?: InstanceType;
105
+ observability: {
106
+ logs: {
107
+ enabled: boolean;
108
+ };
109
+ };
110
+ };
111
+ }
112
+
113
+ export interface ContainerApplicationProps extends CloudflareApiOptions {
114
+ name: string;
115
+ schedulingPolicy?: SchedulingPolicy;
116
+ instances?: number;
117
+ /**
118
+ * The instance type to be used for the deployment.
119
+ *
120
+ * @default "dev"
121
+ */
122
+ instanceType?: InstanceType;
123
+ /**
124
+ * The observability configuration for the deployment.
125
+ */
126
+ observability?: DeploymentObservability;
127
+ /**
128
+ * The maximum number of instances to be used for the deployment.
129
+ */
130
+ maxInstances?: number;
131
+ image: Image;
132
+ registryId?: string;
133
+ durableObjects?: {
134
+ namespaceId: string;
135
+ };
136
+ rollout?: ContainerApplicationRollout;
137
+ }
138
+
139
+ export type SchedulingPolicy =
140
+ | "moon"
141
+ | "gpu"
142
+ | "regional"
143
+ | "fill_metals"
144
+ | "default";
145
+
146
+ export interface ContainerApplication
147
+ extends Resource<"cloudflare::ContainerApplication"> {
148
+ id: string;
149
+ name: string;
150
+ }
151
+
152
+ /**
153
+ * Deploy and manage container applications on Cloudflare's global network.
154
+ *
155
+ * ContainerApplication creates a managed container deployment that runs your Docker images
156
+ * with automatic scaling, scheduling, and integration with Cloudflare's services.
157
+ *
158
+ * @example
159
+ * // Deploy a simple web application container
160
+ * const webApp = await ContainerApplication("my-web-app", {
161
+ * name: "my-web-app",
162
+ * image: await Image("web-app", {
163
+ * name: "web-app",
164
+ * build: {
165
+ * context: "./docker/web-app"
166
+ * }
167
+ * }),
168
+ * instances: 1,
169
+ * maxInstances: 3
170
+ * });
171
+ *
172
+ * @example
173
+ * // Deploy a container with GPU support for AI workloads
174
+ * const aiApp = await ContainerApplication("ai-inference", {
175
+ * name: "ai-inference",
176
+ * image: await Image("ai-model", {
177
+ * name: "ai-model",
178
+ * build: {
179
+ * context: "./docker/ai"
180
+ * }
181
+ * }),
182
+ * schedulingPolicy: "gpu",
183
+ * instances: 2,
184
+ * maxInstances: 5
185
+ * });
186
+ *
187
+ * @example
188
+ * // Deploy a container integrated with Durable Objects
189
+ * const doApp = await ContainerApplication("stateful-app", {
190
+ * name: "stateful-app",
191
+ * image: await Image("do-app", {
192
+ * name: "do-app",
193
+ * build: {
194
+ * context: "./container"
195
+ * }
196
+ * }),
197
+ * durableObjects: {
198
+ * namespaceId: myDurableObjectNamespace.id
199
+ * },
200
+ * instances: 1,
201
+ * maxInstances: 10
202
+ * });
203
+ *
204
+ * @example
205
+ * // Create a Container binding for use in a Worker
206
+ * const worker = await Worker("my-worker", {
207
+ * name: "my-worker",
208
+ * entrypoint: "./src/worker.ts",
209
+ * bindings: {
210
+ * MY_CONTAINER: new Container("my-container", {
211
+ * className: "MyContainerClass",
212
+ * image: await Image("container-do", {
213
+ * name: "container-do",
214
+ * context: "./docker/durable-object"
215
+ * }),
216
+ * maxInstances: 100,
217
+ * name: "my-container-do"
218
+ * })
219
+ * }
220
+ * });
221
+ */
222
+ export const ContainerApplication = Resource(
223
+ "cloudflare::ContainerApplication",
224
+ async function (
225
+ this: Context<ContainerApplication, ContainerApplicationProps>,
226
+ _id: string,
227
+ props: ContainerApplicationProps,
228
+ ): Promise<ContainerApplication> {
229
+ const api = await createCloudflareApi(props);
230
+ if (this.phase === "delete") {
231
+ if (this.output?.id) {
232
+ // Delete the container application
233
+ await deleteContainerApplication(api, this.output.id);
234
+ }
235
+ return this.destroy();
236
+ }
237
+ // Prefer the immutable repo digest if present. Falls back to the tag reference.
238
+ const imageReference = props.image.repoDigest ?? props.image.imageRef;
239
+
240
+ const configuration = {
241
+ image: imageReference,
242
+ instance_type: props.instanceType ?? "dev",
243
+ observability: {
244
+ logs: {
245
+ enabled: true,
246
+ },
247
+ logging: {
248
+ enabled: true,
249
+ },
250
+ },
251
+ };
252
+ if (this.phase === "update") {
253
+ const application = await updateContainerApplication(
254
+ api,
255
+ this.output.id,
256
+ {
257
+ instances: props.instances ?? 1,
258
+ max_instances: props.maxInstances ?? 10,
259
+ scheduling_policy: props.schedulingPolicy ?? "default",
260
+ configuration,
261
+ },
262
+ );
263
+ // TODO(sam): should we wait for the rollout to complete?
264
+ await createContainerApplicationRollout(api, application.id, {
265
+ description: "Progressive update",
266
+ strategy: "rolling",
267
+ kind: props.rollout?.kind ?? "full_auto",
268
+ step_percentage: props.rollout?.stepPercentage ?? 25,
269
+ target_configuration: configuration,
270
+ });
271
+ return this({
272
+ id: application.id,
273
+ name: application.name,
274
+ });
275
+ } else {
276
+ const application = await createContainerApplication(api, {
277
+ name: props.name,
278
+ scheduling_policy: props.schedulingPolicy ?? "default",
279
+ instances: props.instances ?? 1,
280
+ max_instances: props.maxInstances ?? 1,
281
+ durable_objects: props.durableObjects
282
+ ? {
283
+ namespace_id: props.durableObjects.namespaceId,
284
+ }
285
+ : undefined,
286
+ constraints: {
287
+ tier: 1,
288
+ },
289
+ configuration: {
290
+ image: imageReference,
291
+ instance_type: props.instanceType ?? "dev",
292
+ observability: {
293
+ logs: {
294
+ enabled: true,
295
+ },
296
+ },
297
+ },
298
+ });
299
+
300
+ return this({
301
+ id: application.id,
302
+ name: application.name,
303
+ });
304
+ }
305
+ },
306
+ );
307
+
308
+ export interface ContainerApplicationData {
309
+ name: string;
310
+ scheduling_policy: string;
311
+ instances: number;
312
+ max_instances: number;
313
+ constraints: {
314
+ tier: number;
315
+ [key: string]: any;
316
+ };
317
+ configuration: {
318
+ image: string;
319
+ location: string;
320
+ vcpu: number;
321
+ memory_mib: number;
322
+ disk: any;
323
+ network: any;
324
+ command: string[];
325
+ entrypoint: string[];
326
+ runtime: string;
327
+ deployment_type: string;
328
+ observability: any;
329
+ memory: string;
330
+ [key: string]: any;
331
+ };
332
+ durable_objects: {
333
+ namespace_id: string;
334
+ [key: string]: any;
335
+ };
336
+ id: string;
337
+ account_id: string;
338
+ created_at: string;
339
+ version: number;
340
+ durable_object_namespace_id: string;
341
+ health: {
342
+ instances: any;
343
+ [key: string]: any;
344
+ };
345
+ [key: string]: any;
346
+ }
347
+
348
+ export async function listContainerApplications(
349
+ api: CloudflareApi,
350
+ ): Promise<ContainerApplicationData[]> {
351
+ const deployments = await api.get(
352
+ `/accounts/${api.accountId}/containers/applications`,
353
+ );
354
+ const response = (await deployments.json()) as any as {
355
+ result: ContainerApplicationData[];
356
+ errors: { message: string }[];
357
+ };
358
+ if (deployments.ok) {
359
+ return response.result;
360
+ }
361
+ throw Error(
362
+ `Failed to list container applications: ${response.errors.map((e) => e.message).join(", ")}`,
363
+ );
364
+ }
365
+
366
+ export interface CreateContainerApplicationBody {
367
+ name: string;
368
+ max_instances: number;
369
+ configuration: DeploymentConfiguration;
370
+ durable_objects?: {
371
+ namespace_id: string;
372
+ };
373
+ instances?: number;
374
+ scheduling_policy?: string;
375
+ constraints?: { tier: number };
376
+ [key: string]: any;
377
+ }
378
+
379
+ export async function createContainerApplication(
380
+ api: CloudflareApi,
381
+ body: CreateContainerApplicationBody,
382
+ ) {
383
+ const response = await api.post(
384
+ `/accounts/${api.accountId}/containers/applications`,
385
+ body,
386
+ );
387
+ const result = (await response.json()) as {
388
+ result: ContainerApplicationData;
389
+ errors: { message: string }[];
390
+ };
391
+ if (response.ok) {
392
+ return result.result;
393
+ }
394
+
395
+ throw Error(
396
+ `Failed to create container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
397
+ );
398
+ }
399
+
400
+ type Region =
401
+ | "AFR"
402
+ | "APAC"
403
+ | "EEUR"
404
+ | "ENAM"
405
+ | "WNAM"
406
+ | "ME"
407
+ | "OC"
408
+ | "SAM"
409
+ | "WEUR"
410
+ | (string & {});
411
+
412
+ type City =
413
+ | "AFR"
414
+ | "APAC"
415
+ | "EEUR"
416
+ | "ENAM"
417
+ | "WNAM"
418
+ | "ME"
419
+ | "OC"
420
+ | "SAM"
421
+ | "WEUR"
422
+ | (string & {});
423
+
424
+ export type UpdateApplicationRequestBody = {
425
+ /**
426
+ * Number of deployments to maintain within this applicaiton. This can be used to scale the appliation up/down.
427
+ */
428
+ instances?: number;
429
+ max_instances?: number;
430
+ affinities?: {
431
+ colocation?: "datacenter";
432
+ };
433
+ scheduling_policy?: SchedulingPolicy;
434
+ constraints?: {
435
+ region?: Region;
436
+ tier?: number;
437
+ regions?: Array<Region>;
438
+ cities?: Array<City>;
439
+ };
440
+ /**
441
+ * The deployment configuration of all deployments created by this application.
442
+ * Right now, if you modify the application configuration, only new deployments
443
+ * created will have the new configuration. You can delete old deployments to
444
+ * release new instances.
445
+ *
446
+ * TODO(sam): should this trigger a replacement?
447
+ */
448
+ configuration?: DeploymentConfiguration;
449
+ };
450
+
451
+ export async function updateContainerApplication(
452
+ api: CloudflareApi,
453
+ applicationId: string,
454
+ body: UpdateApplicationRequestBody,
455
+ ) {
456
+ const response = await api.patch(
457
+ `/accounts/${api.accountId}/containers/applications/${applicationId}`,
458
+ body,
459
+ );
460
+ const result = (await response.json()) as {
461
+ result: ContainerApplicationData;
462
+ errors: { message: string }[];
463
+ };
464
+ if (response.ok) {
465
+ return result.result;
466
+ }
467
+
468
+ throw Error(
469
+ `Failed to create container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
470
+ );
471
+ }
472
+
473
+ export async function deleteContainerApplication(
474
+ api: CloudflareApi,
475
+ applicationId: string,
476
+ ) {
477
+ const response = await api.delete(
478
+ `/accounts/${api.accountId}/containers/applications/${applicationId}`,
479
+ );
480
+ const result = (await response.json()) as any;
481
+ if (response.ok) {
482
+ return result.result;
483
+ }
484
+ throw Error(
485
+ `Failed to delete container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
486
+ );
487
+ }
488
+
489
+ interface CreateRolloutApplicationRequest {
490
+ description: string;
491
+ strategy: "rolling";
492
+ kind?: "full_auto";
493
+ step_percentage: number;
494
+ target_configuration: DeploymentConfiguration;
495
+ }
496
+
497
+ interface CreateRolloutApplicationResponse {
498
+ id: string;
499
+ created_at: string;
500
+ last_updated_at: string;
501
+ description: string;
502
+ status: "progressing" | "completed" | "failed" | (string & {});
503
+ health: {
504
+ instances: {
505
+ healthy: number;
506
+ failed: number;
507
+ starting: number;
508
+ scheduling: number;
509
+ };
510
+ };
511
+ kind: "full_auto" | (string & {});
512
+ strategy: "rolling" | (string & {});
513
+ current_configuration: {
514
+ image: string;
515
+ observability?: {
516
+ logs?: {
517
+ enabled: boolean;
518
+ };
519
+ logging?: {
520
+ enabled: boolean;
521
+ };
522
+ };
523
+ };
524
+ target_configuration: DeploymentConfiguration;
525
+ current_version: number;
526
+ target_version: number;
527
+ steps: Array<{
528
+ id: number;
529
+ status: "progressing" | "pending" | "completed" | "failed" | (string & {});
530
+ step_size: {
531
+ percentage: number;
532
+ };
533
+ description: string;
534
+ started_at?: string;
535
+ }>;
536
+ progress: {
537
+ total_steps: number;
538
+ current_step: number;
539
+ updated_instances: number;
540
+ total_instances: number;
541
+ };
542
+ }
543
+
544
+ export async function createContainerApplicationRollout(
545
+ api: CloudflareApi,
546
+ applicationId: string,
547
+ body: CreateRolloutApplicationRequest,
548
+ ) {
549
+ const response = await api.post(
550
+ `/accounts/${api.accountId}/containers/applications/${applicationId}/rollouts`,
551
+ body,
552
+ );
553
+ const result = (await response.json()) as {
554
+ result: CreateRolloutApplicationResponse;
555
+ errors: { message: string }[];
556
+ };
557
+ if (response.ok) {
558
+ return result.result;
559
+ }
560
+ throw Error(
561
+ `Failed to create container application rollout: ${result.errors.map((e: { message: string }) => e.message).join(", ")}`,
562
+ );
563
+ }
564
+
565
+ export type ImageRegistryCredentialsConfiguration = {
566
+ permissions: Array<"pull" | "push">;
567
+ expiration_minutes: number;
568
+ };
569
+
570
+ export async function getContainerCredentials(
571
+ api: CloudflareApi,
572
+ registryId = "registry.cloudflare.com",
573
+ ) {
574
+ const credentials = await api.post(
575
+ `/accounts/${api.accountId}/containers/registries/${registryId}/credentials`,
576
+ {
577
+ permissions: ["pull", "push"],
578
+ expiration_minutes: 60,
579
+ } satisfies ImageRegistryCredentialsConfiguration,
580
+ );
581
+ const result = (await credentials.json()) as {
582
+ result: {
583
+ user?: string;
584
+ username?: string;
585
+ password: string;
586
+ };
587
+ errors: { message: string }[];
588
+ };
589
+ if (credentials.ok) {
590
+ return result.result;
591
+ }
592
+ throw Error(
593
+ `Failed to get container credentials: ${result.errors.map((e: { message: string }) => e.message).join(", ")}`,
594
+ );
595
+ }
596
+
597
+ // The Cloudflare managed registry is special in that the namespaces for repos should always
598
+ // start with the Cloudflare Account tag
599
+ // This is a helper to generate the image tag with correct namespace attached to the Cloudflare Registry host
600
+ export const getCloudflareRegistryWithAccountNamespace = (
601
+ accountID: string,
602
+ tag: string,
603
+ ): string => {
604
+ return `${getCloudflareContainerRegistry()}/${accountID}/${tag}`;
605
+ };
606
+
607
+ // default cloudflare managed registry, can be overriden with the env var - CLOUDFLARE_CONTAINER_REGISTRY
608
+ export const getCloudflareContainerRegistry = () => {
609
+ // previously defaulted to registry.cloudchamber.cfdata.org
610
+ return process.env.CLOUDFLARE_CONTAINER_REGISTRY ?? "registry.cloudflare.com";
611
+ };
612
+
613
+ /**
614
+ * Given a container image that is a registry link, this function
615
+ * returns true if the link points the Cloudflare container registry
616
+ * (defined as per `getCloudflareContainerRegistry` above)
617
+ */
618
+ export function isCloudflareRegistryLink(image: string) {
619
+ const cfRegistry = getCloudflareContainerRegistry();
620
+ return image.includes(cfRegistry);
621
+ }
622
+
623
+ /** Prefixes with the cloudflare-dev namespace. The name should be the container's DO classname, and the tag a build uuid. */
624
+ export const getDevContainerImageName = (name: string, tag: string) => {
625
+ return `${MF_DEV_CONTAINER_PREFIX}/${name.toLowerCase()}:${tag}`;
626
+ };
627
+
628
+ export const MF_DEV_CONTAINER_PREFIX = "cloudflare-dev";
629
+
630
+ export interface ContainerIdentity {
631
+ account_id: string;
632
+ external_account_id: string;
633
+ legacy_identity: string;
634
+ capabilities: string[];
635
+ limits: {
636
+ account_id: string;
637
+ vcpu_per_deployment: number;
638
+ memory_mib_per_deployment: number;
639
+ memory_per_deployment: string;
640
+ disk_per_deployment: string;
641
+ disk_mb_per_deployment: number;
642
+ total_vcpu: number;
643
+ total_memory_mib: number;
644
+ node_group: string;
645
+ ipv4s: number;
646
+ network_modes: string[];
647
+ total_disk_mb: number;
648
+ total_memory: string;
649
+ };
650
+ locations: any[];
651
+ defaults: {
652
+ vcpus: number;
653
+ memory_mib: number;
654
+ memory: string;
655
+ disk_mb: number;
656
+ };
657
+ }
658
+
659
+ export async function getContainerIdentity(api: CloudflareApi) {
660
+ const metrics = await api.get(`/accounts/${api.accountId}/containers/me`);
661
+ const result = (await metrics.json()) as {
662
+ result: ContainerIdentity;
663
+ errors: { message: string }[];
664
+ };
665
+ if (metrics.ok) {
666
+ return result.result;
667
+ }
668
+ throw Error(
669
+ `Failed to get container me: ${result.errors.map((e: { message: string }) => e.message).join(", ")}`,
670
+ );
671
+ }
672
+
673
+ /**
674
+ * Duration string. From Go documentation:
675
+ * A string representing the duration in the form "3d1h3m". Leading zero units are omitted.
676
+ * As a special case, durations less than one second format use a smaller unit (milli-, micro-, or nanoseconds)
677
+ * to ensure that the leading digit is non-zero.
678
+ */
679
+ export type Duration = string;
680
+
681
+ interface DeploymentObservability {
682
+ logs?: {
683
+ enabled: boolean;
684
+ };
685
+ }
686
+
687
+ export type DeploymentConfiguration = {
688
+ /**
689
+ * The image to be used for the deployment.
690
+ */
691
+ image: string;
692
+ /**
693
+ * The instance type to be used for the deployment.
694
+ */
695
+ instance_type?: InstanceType;
696
+ /**
697
+ * The observability configuration for the deployment.
698
+ */
699
+ observability?: DeploymentObservability;
700
+ /**
701
+ * A list of SSH public key IDs from the account
702
+ */
703
+ ssh_public_key_ids?: Array<string>;
704
+ /**
705
+ * A list of objects with secret names and the their access types from the account
706
+ */
707
+ secrets?: Array<{
708
+ /**
709
+ * The name of the secret within the container
710
+ */
711
+ name: string;
712
+ type: "env";
713
+ /**
714
+ * Corresponding secret name from the account
715
+ */
716
+ secret: string;
717
+ }>;
718
+ /**
719
+ * Specify the vcpu to be used for the deployment. The default will be the one configured for the account.
720
+ */
721
+ vcpu?: number;
722
+ /**
723
+ * Specify the memory to be used for the deployment. The default will be the one configured for the account.
724
+ */
725
+ memory?: string;
726
+ /**
727
+ * The disk configuration for this deployment
728
+ */
729
+ disk?: {
730
+ size: string;
731
+ };
732
+ /**
733
+ * Container environment variables
734
+ */
735
+ environment_variables?: Array<{
736
+ name: string;
737
+ value: string;
738
+ }>;
739
+ /**
740
+ * Deployment labels
741
+ */
742
+ labels?: Array<{
743
+ name: string;
744
+ value: string;
745
+ }>;
746
+ network?: {
747
+ /**
748
+ * Assign an IPv4 address to the deployment. One of 'none' (default), 'predefined' (allocate one from a set of IPv4 addresses in the global pool), 'account' (allocate one from a set of IPv4 addresses preassigned in the account pool). Only applicable to "public" mode.
749
+ *
750
+ */
751
+ assign_ipv4?: "none" | "predefined" | "account";
752
+ /**
753
+ * Assign an IPv6 address to the deployment. One of 'predefined' (allocate one from a set of IPv6 addresses in the global pool), 'account' (allocate one from a set of IPv6 addresses preassigned in the account pool). The container will always be assigned to an IPv6 if the networking mode is "public".
754
+ *
755
+ */
756
+ assign_ipv6?: "none" | "predefined" | "account";
757
+ mode?: "public" | "private";
758
+ };
759
+ command?: string[];
760
+ entrypoint?: string[];
761
+ dns?: {
762
+ /**
763
+ * List of DNS servers that the deployment will use to resolve domain names. You can only specify a maximum of 3.
764
+ */
765
+ servers?: Array<string>;
766
+ /**
767
+ * The container resolver will append these domains to every resolve query. For example, if you have 'google.com',
768
+ * and your deployment queries 'web', it will append 'google.com' to 'web' in the search query before trying 'web'.
769
+ * Limited to 6 domains.
770
+ */
771
+ searches?: Array<string>;
772
+ };
773
+ ports?: Array<{
774
+ /**
775
+ * The name of the port. The port name should be unique for each deployment. Minimum length of 1 and maximum length of 15. No consecutive dashes. If the name is 'web-ui', the container will receive an environment variable as follows:
776
+ * - CLOUDFLARE_PORT_WEB_UI: Port inside the container
777
+ * - CLOUDFLARE_HOST_PORT_WEB_UI: Port outside the container
778
+ * - CLOUDFLARE_HOST_IP_WEB_UI: Address of the external network interface the port is allocated on
779
+ * - CLOUDFLARE_HOST_ADDR_WEB_UI: CLOUDFLARE_HOST_ADDR_WEB_UI ':' CLOUDFLARE_HOST_PORT_WEB_UI
780
+ *
781
+ */
782
+ name: string;
783
+ /**
784
+ * Optional port number, it's assigned only if the user specified it. If it's not specified, the datacenter scheduler will decide it.
785
+ */
786
+ port?: number;
787
+ }>;
788
+ /**
789
+ * Health and readiness checks for this deployment.
790
+ */
791
+ checks?: Array<{
792
+ /**
793
+ * Optional name for the check. If omitted, a name will be generated automatically.
794
+ */
795
+ name?: string;
796
+ /**
797
+ * The type of check to perform. A TCP check succeeds if it can connect to the provided port. An HTTP check succeeds if it receives a successful HTTP response (2XX)
798
+ */
799
+ type: "http" | "tcp";
800
+ /**
801
+ * Connect to the port using TLS
802
+ */
803
+ tls?: boolean;
804
+ /**
805
+ * The name of the port defined in the "ports" property of the deployment
806
+ */
807
+ port: string;
808
+ /**
809
+ * Configuration for HTTP checks. Only valid when "type" is "http"
810
+ */
811
+ http?: {
812
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
813
+ /**
814
+ * If the method is one of POST, PATCH or PUT, this is required. It's the body that will be passed to the HTTP healthcheck request.
815
+ */
816
+ body?: string;
817
+ /**
818
+ * Path that will be used to perform the healthcheck.
819
+ */
820
+ path?: string;
821
+ /**
822
+ * HTTP headers to include in the request.
823
+ */
824
+ headers?: Record<string, any>;
825
+ };
826
+ /**
827
+ * How often the check should be performed
828
+ */
829
+ interval: Duration;
830
+ /**
831
+ * The amount of time to wait for the check to complete before considering the check to have failed
832
+ */
833
+ timeout: Duration;
834
+ /**
835
+ * Number of times to attempt the check before considering it to have failed
836
+ */
837
+ retries?: number;
838
+ /**
839
+ * The kind of check. A failed "healthy" check affects a deployment's "healthy" status, while a failed "ready" check affects a deployment's "ready" status.
840
+ */
841
+ kind: "health" | "ready";
842
+ }>;
843
+ };