alchemy 0.68.0 → 0.69.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 (92) hide show
  1. package/bin/alchemy.js +19 -1
  2. package/lib/aws/control/properties.d.ts +64 -0
  3. package/lib/aws/control/properties.d.ts.map +1 -1
  4. package/lib/aws/control/properties.js +554 -39
  5. package/lib/aws/control/properties.js.map +1 -1
  6. package/lib/aws/control/resource.d.ts.map +1 -1
  7. package/lib/aws/control/resource.js +33 -0
  8. package/lib/aws/control/resource.js.map +1 -1
  9. package/lib/aws/control/types.d.ts +5451 -1587
  10. package/lib/aws/control/types.d.ts.map +1 -1
  11. package/lib/aws/control/types.js.map +1 -1
  12. package/lib/aws/control/update-types.d.ts +12913 -0
  13. package/lib/aws/control/update-types.d.ts.map +1 -0
  14. package/lib/aws/control/update-types.js +12916 -0
  15. package/lib/aws/control/update-types.js.map +1 -0
  16. package/lib/cloudflare/api.d.ts +1 -1
  17. package/lib/cloudflare/api.d.ts.map +1 -1
  18. package/lib/cloudflare/api.js +9 -10
  19. package/lib/cloudflare/api.js.map +1 -1
  20. package/lib/cloudflare/astro/astro.d.ts.map +1 -1
  21. package/lib/cloudflare/astro/astro.js +3 -3
  22. package/lib/cloudflare/astro/astro.js.map +1 -1
  23. package/lib/cloudflare/bucket-object.d.ts +81 -0
  24. package/lib/cloudflare/bucket-object.d.ts.map +1 -0
  25. package/lib/cloudflare/bucket-object.js +67 -0
  26. package/lib/cloudflare/bucket-object.js.map +1 -0
  27. package/lib/cloudflare/bucket.d.ts +30 -3
  28. package/lib/cloudflare/bucket.d.ts.map +1 -1
  29. package/lib/cloudflare/bucket.js +97 -13
  30. package/lib/cloudflare/bucket.js.map +1 -1
  31. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  32. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  33. package/lib/cloudflare/container.d.ts.map +1 -1
  34. package/lib/cloudflare/container.js +1 -1
  35. package/lib/cloudflare/container.js.map +1 -1
  36. package/lib/cloudflare/index.d.ts +1 -0
  37. package/lib/cloudflare/index.d.ts.map +1 -1
  38. package/lib/cloudflare/index.js +1 -0
  39. package/lib/cloudflare/index.js.map +1 -1
  40. package/lib/cloudflare/next.js.map +1 -1
  41. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -1
  42. package/lib/cloudflare/nuxt/nuxt.js +3 -3
  43. package/lib/cloudflare/nuxt/nuxt.js.map +1 -1
  44. package/lib/cloudflare/vite/vite.d.ts.map +1 -1
  45. package/lib/cloudflare/vite/vite.js +3 -3
  46. package/lib/cloudflare/vite/vite.js.map +1 -1
  47. package/lib/cloudflare/website.d.ts +7 -1
  48. package/lib/cloudflare/website.d.ts.map +1 -1
  49. package/lib/cloudflare/website.js +19 -1
  50. package/lib/cloudflare/website.js.map +1 -1
  51. package/lib/cloudflare/wrangler.json.d.ts +27 -8
  52. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  53. package/lib/cloudflare/wrangler.json.js +3 -1
  54. package/lib/cloudflare/wrangler.json.js.map +1 -1
  55. package/lib/llms.cloudflare.txt +2 -2
  56. package/lib/neon/branch.d.ts +19 -3
  57. package/lib/neon/branch.d.ts.map +1 -1
  58. package/lib/neon/branch.js +4 -4
  59. package/lib/neon/branch.js.map +1 -1
  60. package/lib/neon/project.d.ts +17 -1
  61. package/lib/neon/project.d.ts.map +1 -1
  62. package/lib/neon/project.js +4 -4
  63. package/lib/neon/project.js.map +1 -1
  64. package/lib/serde.d.ts +3 -0
  65. package/lib/serde.d.ts.map +1 -1
  66. package/lib/serde.js +40 -0
  67. package/lib/serde.js.map +1 -1
  68. package/lib/test/bun.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/aws/control/properties.ts +554 -39
  71. package/src/aws/control/resource.ts +58 -0
  72. package/src/aws/control/types.ts +85281 -84914
  73. package/src/aws/control/update-types.ts +12917 -0
  74. package/src/cloudflare/api.ts +9 -10
  75. package/src/cloudflare/astro/astro.ts +8 -3
  76. package/src/cloudflare/bucket-object.ts +117 -0
  77. package/src/cloudflare/bucket.ts +148 -14
  78. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  79. package/src/cloudflare/container.ts +2 -3
  80. package/src/cloudflare/index.ts +1 -0
  81. package/src/cloudflare/next.ts +2 -2
  82. package/src/cloudflare/nuxt/nuxt.ts +8 -3
  83. package/src/cloudflare/vite/vite.ts +8 -3
  84. package/src/cloudflare/website.ts +28 -2
  85. package/src/cloudflare/wrangler.json.ts +32 -17
  86. package/src/llms.cloudflare.txt +2 -2
  87. package/src/neon/branch.ts +6 -5
  88. package/src/neon/project.ts +4 -4
  89. package/src/serde.ts +50 -0
  90. package/src/test/bun.ts +1 -1
  91. package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +2 -2
  92. package/workers/tunnel-proxy.js +1 -1
@@ -1,5 +1,6 @@
1
1
  import { Provider, type Credentials } from "../auth.ts";
2
2
  import type { Secret } from "../secret.ts";
3
+ import { isBinary } from "../serde.ts";
3
4
  import { memoize } from "../util/memoize.ts";
4
5
  import { withExponentialBackoff } from "../util/retry.ts";
5
6
  import { safeFetch } from "../util/safe-fetch.ts";
@@ -257,7 +258,7 @@ export class CloudflareApi {
257
258
  return this.fetch(path, {
258
259
  ...init,
259
260
  method: "POST",
260
- body: this.toBody(body),
261
+ body: await this.toBody(body),
261
262
  });
262
263
  }
263
264
 
@@ -272,18 +273,16 @@ export class CloudflareApi {
272
273
  return this.fetch(path, {
273
274
  ...init,
274
275
  method: "PUT",
275
- body: this.toBody(body),
276
+ body: await this.toBody(body),
276
277
  });
277
278
  }
278
279
 
279
- toBody(body: BodyInit): BodyInit {
280
- return body instanceof FormData
280
+ async toBody(body: BodyInit): Promise<BodyInit> {
281
+ return body instanceof FormData ||
282
+ typeof body === "string" ||
283
+ isBinary(body)
281
284
  ? body
282
- : typeof body === "string"
283
- ? body
284
- : body instanceof ReadableStream
285
- ? body
286
- : JSON.stringify(body);
285
+ : JSON.stringify(body);
287
286
  }
288
287
 
289
288
  /**
@@ -297,7 +296,7 @@ export class CloudflareApi {
297
296
  return this.fetch(path, {
298
297
  ...init,
299
298
  method: "PATCH",
300
- body: this.toBody(body),
299
+ body: await this.toBody(body),
301
300
  });
302
301
  }
303
302
 
@@ -3,7 +3,12 @@ import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
3
3
  import type { Assets } from "../assets.ts";
4
4
  import type { Bindings } from "../bindings.ts";
5
5
  import { withSkipPathValidation } from "../miniflare/paths.ts";
6
- import { Website, type WebsiteProps } from "../website.ts";
6
+ import {
7
+ spreadBuildProps,
8
+ spreadDevProps,
9
+ Website,
10
+ type WebsiteProps,
11
+ } from "../website.ts";
7
12
  import type { Worker } from "../worker.ts";
8
13
 
9
14
  /**
@@ -75,8 +80,8 @@ export async function Astro<B extends Bindings>(
75
80
  return await Website(id, {
76
81
  ...props,
77
82
  noBundle: props.noBundle ?? true,
78
- build: props.build ?? `${runner} astro build`,
79
- dev: props.dev ?? `${runner} astro dev`,
83
+ build: spreadBuildProps(props, `${runner} astro build`),
84
+ dev: spreadDevProps(props, `${runner} astro dev`),
80
85
  entrypoint:
81
86
  props.entrypoint ??
82
87
  (output === "server" ? "dist/_worker.js/index.js" : undefined),
@@ -0,0 +1,117 @@
1
+ import type { Context } from "../context.ts";
2
+ import { Resource } from "../resource.ts";
3
+ import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
4
+ import { deleteObject, type PutObjectObject, type R2Bucket } from "./bucket.ts";
5
+
6
+ /**
7
+ * Properties for creating or updating an R2 Object
8
+ */
9
+ export interface R2ObjectProps extends CloudflareApiOptions {
10
+ /**
11
+ * The R2 bucket where the object will be stored
12
+ */
13
+ bucket: R2Bucket;
14
+
15
+ /**
16
+ * The key (path/name) of the object within the bucket
17
+ * Must be a valid object key as defined by R2 storage requirements
18
+ */
19
+ key: string;
20
+
21
+ /**
22
+ * The content to store in the object
23
+ * Supports various data types including streams, buffers, strings, and blobs
24
+ */
25
+ content: PutObjectObject;
26
+ }
27
+
28
+ /**
29
+ * Output returned after R2 Object creation/update
30
+ */
31
+ export interface R2Object {
32
+ /**
33
+ * The resource identifier
34
+ */
35
+ id: string;
36
+
37
+ /**
38
+ * The key (path/name) of the object within the bucket
39
+ */
40
+ key: string;
41
+
42
+ /**
43
+ * The etag of the object
44
+ */
45
+ etag: string;
46
+ }
47
+
48
+ /**
49
+ * Creates and manages objects within Cloudflare R2 Buckets.
50
+ *
51
+ * R2 Objects represent individual files or data stored within an R2 bucket.
52
+ * This resource provides lifecycle management for object creation, updates, and deletion.
53
+ *
54
+ * @example
55
+ * // Create a simple text object in a bucket
56
+ * const textObject = await R2Object("readme", {
57
+ * bucket: myBucket,
58
+ * key: "docs/README.txt",
59
+ * content: "Welcome to our application!"
60
+ * });
61
+ *
62
+ * @example
63
+ * // Store JSON data as an object
64
+ * const configObject = await R2Object("app-config", {
65
+ * bucket: configBucket,
66
+ * key: "config/app.json",
67
+ * content: JSON.stringify({ version: "1.0.0", debug: false })
68
+ * });
69
+ *
70
+ * @example
71
+ * // Store binary data from a buffer
72
+ * const imageObject = await R2Object("user-avatar", {
73
+ * bucket: assetsBucket,
74
+ * key: "avatars/user-123.png",
75
+ * content: imageBuffer
76
+ * });
77
+ *
78
+ * @example
79
+ * // Store data from a readable stream
80
+ * const streamObject = await R2Object("upload", {
81
+ * bucket: uploadsBucket,
82
+ * key: "uploads/file.dat",
83
+ * content: readableStream
84
+ * });
85
+ *
86
+ * @see https://developers.cloudflare.com/r2/api/workers/workers-api-reference/
87
+ */
88
+ export const R2Object = Resource(
89
+ "cloudflare::R2Object",
90
+ async function (
91
+ this: Context<R2Object>,
92
+ id: string,
93
+ props: R2ObjectProps,
94
+ ): Promise<R2Object> {
95
+ if (this.phase === "delete") {
96
+ const api = await createCloudflareApi(props);
97
+ // Delete the object from the bucket
98
+ await deleteObject(api, {
99
+ bucketName: props.bucket.name,
100
+ key: props.key,
101
+ });
102
+ return this.destroy();
103
+ } else {
104
+ if (this.phase === "update" && this.output?.key !== props.key) {
105
+ this.replace();
106
+ }
107
+ // Create or update the object in the bucket
108
+ const response = await props.bucket.put(props.key, props.content);
109
+
110
+ return {
111
+ id,
112
+ key: props.key,
113
+ etag: response.etag,
114
+ };
115
+ }
116
+ },
117
+ );
@@ -1,7 +1,9 @@
1
+ import * as mf from "miniflare";
1
2
  import { isDeepStrictEqual } from "node:util";
2
3
  import type { Context } from "../context.ts";
3
4
  import { Resource, ResourceKind } from "../resource.ts";
4
5
  import { Scope } from "../scope.ts";
6
+ import { streamToBuffer } from "../serde.ts";
5
7
  import { isRetryableError } from "../state/r2-rest-state-store.ts";
6
8
  import { withExponentialBackoff } from "../util/retry.ts";
7
9
  import { CloudflareApiError, handleApiError } from "./api-error.ts";
@@ -15,6 +17,7 @@ import {
15
17
  type CloudflareApiOptions,
16
18
  } from "./api.ts";
17
19
  import { deleteMiniflareBinding } from "./miniflare/delete.ts";
20
+ import { getDefaultPersistPath } from "./miniflare/paths.ts";
18
21
 
19
22
  export type R2BucketJurisdiction = "default" | "eu" | "fedramp";
20
23
 
@@ -89,6 +92,11 @@ export interface BucketProps extends CloudflareApiOptions {
89
92
  */
90
93
  lock?: R2BucketLockRule[];
91
94
 
95
+ /**
96
+ * Enable data catalog for bucket
97
+ */
98
+ dataCatalog?: boolean;
99
+
92
100
  /**
93
101
  * Whether to emulate the bucket locally when Alchemy is running in watch mode.
94
102
  */
@@ -247,7 +255,7 @@ export type R2ObjectMetadata = {
247
255
  size: number;
248
256
  };
249
257
 
250
- export type R2Object = R2ObjectMetadata & {
258
+ export type R2ObjectContent = R2ObjectMetadata & {
251
259
  arrayBuffer(): Promise<ArrayBuffer>;
252
260
  bytes(): Promise<Uint8Array>;
253
261
  text(): Promise<string>;
@@ -278,7 +286,7 @@ export type R2Objects = {
278
286
 
279
287
  export type R2Bucket = _R2Bucket & {
280
288
  head(key: string): Promise<R2ObjectMetadata | null>;
281
- get(key: string): Promise<R2Object | null>;
289
+ get(key: string): Promise<R2ObjectContent | null>;
282
290
  put(
283
291
  key: string,
284
292
  value:
@@ -337,6 +345,24 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
337
345
  */
338
346
  remote: boolean;
339
347
  };
348
+
349
+ /**
350
+ * Data catalog for the bucket
351
+ */
352
+ catalog?: {
353
+ /**
354
+ * ID of the data catalog
355
+ */
356
+ id: string;
357
+ /**
358
+ * Name of the data catalog
359
+ */
360
+ name: string;
361
+ /**
362
+ * Host of the data catalog
363
+ */
364
+ host: string;
365
+ };
340
366
  };
341
367
 
342
368
  export function isBucket(resource: any): resource is R2Bucket {
@@ -391,6 +417,8 @@ export async function R2Bucket(
391
417
  id: string,
392
418
  props: BucketProps = {},
393
419
  ): Promise<R2Bucket> {
420
+ const scope = Scope.current;
421
+ const isLocal = scope.local && props.dev?.remote !== true;
394
422
  const api = await createCloudflareApi(props);
395
423
  const bucket = await _R2Bucket(id, {
396
424
  ...props,
@@ -399,14 +427,44 @@ export async function R2Bucket(
399
427
  force: Scope.current.local,
400
428
  },
401
429
  });
430
+
431
+ let _miniflare: mf.Miniflare | undefined;
432
+ const miniflare = () => {
433
+ if (_miniflare) {
434
+ return _miniflare;
435
+ }
436
+ _miniflare = new mf.Miniflare({
437
+ script: "",
438
+ modules: true,
439
+ defaultPersistRoot: getDefaultPersistPath(scope.rootDir),
440
+ r2Buckets: [bucket.dev.id],
441
+ log: process.env.DEBUG ? new mf.Log(mf.LogLevel.DEBUG) : undefined,
442
+ });
443
+ scope.onCleanup(async () => _miniflare?.dispose());
444
+ return _miniflare;
445
+ };
446
+ const localBucket = () => miniflare().getR2Bucket(bucket.dev.id);
447
+
402
448
  return {
403
449
  ...bucket,
404
- head: async (key: string) =>
405
- headObject(api, {
450
+ head: async (key: string) => {
451
+ if (isLocal) {
452
+ return (await localBucket()).head(key);
453
+ }
454
+ return headObject(api, {
406
455
  bucketName: bucket.name,
407
456
  key,
408
- }),
457
+ });
458
+ },
409
459
  get: async (key: string) => {
460
+ if (isLocal) {
461
+ const result = await (await localBucket()).get(key);
462
+ if (result) {
463
+ // cast because workers vs node built-ins
464
+ return result as unknown as R2ObjectContent;
465
+ }
466
+ return null;
467
+ }
410
468
  const response = await getObject(api, {
411
469
  bucketName: bucket.name,
412
470
  key,
@@ -419,15 +477,36 @@ export async function R2Bucket(
419
477
  throw await handleApiError(response, "get", "object", key);
420
478
  }
421
479
  },
422
- list: async (options?: R2ListOptions): Promise<R2Objects> =>
423
- listObjects(api, bucket.name, {
480
+ list: async (options?: R2ListOptions): Promise<R2Objects> => {
481
+ if (isLocal) {
482
+ return (await localBucket()).list(options);
483
+ }
484
+ return listObjects(api, bucket.name, {
424
485
  ...options,
425
486
  jurisdiction: bucket.jurisdiction,
426
- }),
487
+ });
488
+ },
427
489
  put: async (
428
490
  key: string,
429
491
  value: PutObjectObject,
430
492
  ): Promise<PutR2ObjectResponse> => {
493
+ if (isLocal) {
494
+ // @ts-expect-error - node built-ins vs cloudflare built-ins
495
+ return await (await localBucket()).put(
496
+ key,
497
+ typeof value === "string"
498
+ ? value
499
+ : Buffer.isBuffer(value) ||
500
+ value instanceof Uint8Array ||
501
+ value instanceof ArrayBuffer
502
+ ? new Uint8Array(value)
503
+ : value instanceof Blob
504
+ ? new Uint8Array(await value.arrayBuffer())
505
+ : value instanceof ReadableStream
506
+ ? new Uint8Array(await streamToBuffer(value))
507
+ : value,
508
+ );
509
+ }
431
510
  const response = await putObject(api, {
432
511
  bucketName: bucket.name,
433
512
  key: key,
@@ -450,15 +529,19 @@ export async function R2Bucket(
450
529
  size: Number(body.result.size),
451
530
  };
452
531
  },
453
- delete: async (key: string) =>
454
- deleteObject(api, {
532
+ delete: async (key: string) => {
533
+ if (isLocal) {
534
+ await (await localBucket()).delete(key);
535
+ }
536
+ return deleteObject(api, {
455
537
  bucketName: bucket.name,
456
538
  key: key,
457
- }),
539
+ });
540
+ },
458
541
  };
459
542
  }
460
543
 
461
- const parseR2Object = (key: string, response: Response): R2Object => ({
544
+ const parseR2Object = (key: string, response: Response): R2ObjectContent => ({
462
545
  etag: response.headers.get("ETag")!,
463
546
  uploaded: parseDate(response.headers),
464
547
  key,
@@ -481,7 +564,7 @@ const _R2Bucket = Resource(
481
564
  props: BucketProps = {},
482
565
  ): Promise<_R2Bucket> {
483
566
  const bucketName =
484
- props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
567
+ props.name ?? (this.output?.name || this.scope.createPhysicalName(id));
485
568
 
486
569
  if (this.phase === "update" && this.output?.name !== bucketName) {
487
570
  this.replace();
@@ -513,6 +596,9 @@ const _R2Bucket = Resource(
513
596
 
514
597
  if (this.phase === "delete") {
515
598
  if (props.delete !== false) {
599
+ if (this.output?.catalog) {
600
+ await disableDataCatalog(api, bucketName);
601
+ }
516
602
  if (this.output.dev?.id) {
517
603
  await deleteMiniflareBinding(this.scope, "r2", this.output.dev.id);
518
604
  }
@@ -552,6 +638,16 @@ const _R2Bucket = Resource(
552
638
  if (props.lock?.length) {
553
639
  await putBucketLockRules(api, bucketName, props);
554
640
  }
641
+ let dataCatalog:
642
+ | {
643
+ id: string;
644
+ name: string;
645
+ host: string;
646
+ }
647
+ | undefined;
648
+ if (props.dataCatalog) {
649
+ dataCatalog = await enableDataCatalog(api, bucketName);
650
+ }
555
651
  return {
556
652
  name: bucketName,
557
653
  location: bucket.location,
@@ -565,6 +661,7 @@ const _R2Bucket = Resource(
565
661
  lock: props.lock,
566
662
  cors: props.cors,
567
663
  dev,
664
+ catalog: dataCatalog,
568
665
  };
569
666
  } else {
570
667
  if (bucketName !== this.output.name) {
@@ -572,6 +669,13 @@ const _R2Bucket = Resource(
572
669
  `Cannot update R2Bucket name after creation. Bucket name is immutable. Before: ${this.output.name}, After: ${bucketName}`,
573
670
  );
574
671
  }
672
+ if (this.output?.catalog && !props.dataCatalog) {
673
+ await disableDataCatalog(api, bucketName);
674
+ this.output.catalog = undefined;
675
+ }
676
+ if (!this.output.catalog && props.dataCatalog) {
677
+ this.output.catalog = await enableDataCatalog(api, bucketName);
678
+ }
575
679
  let domain = this.output.domain;
576
680
  if (!!domain !== allowPublicAccess) {
577
681
  domain = await putManagedDomain(
@@ -1079,11 +1183,13 @@ export async function getObject(
1079
1183
  });
1080
1184
  }
1081
1185
 
1082
- type PutObjectObject =
1186
+ export type PutObjectObject =
1083
1187
  | ReadableStream
1084
1188
  | ArrayBuffer
1085
1189
  | ArrayBufferView
1190
+ | Uint8Array
1086
1191
  | string
1192
+ | Buffer
1087
1193
  | Blob;
1088
1194
 
1089
1195
  export async function putObject(
@@ -1132,3 +1238,31 @@ export async function deleteObject(
1132
1238
  return response;
1133
1239
  });
1134
1240
  }
1241
+
1242
+ export async function enableDataCatalog(
1243
+ api: CloudflareApi,
1244
+ bucketName: string,
1245
+ ) {
1246
+ const response = await extractCloudflareResult<{
1247
+ id: string;
1248
+ name: string;
1249
+ }>(
1250
+ `enable data catalog for bucket "${bucketName}"`,
1251
+ api.post(`/accounts/${api.accountId}/r2-catalog/${bucketName}/enable`, {}),
1252
+ );
1253
+ return {
1254
+ id: response.id,
1255
+ name: response.name,
1256
+ host: `https://catalog.cloudflarestorage.com/${response.name.replace("_", "/")}`,
1257
+ };
1258
+ }
1259
+
1260
+ export async function disableDataCatalog(
1261
+ api: CloudflareApi,
1262
+ bucketName: string,
1263
+ ) {
1264
+ await api.post(
1265
+ `/accounts/${api.accountId}/r2-catalog/${bucketName}/disable`,
1266
+ {},
1267
+ );
1268
+ }
@@ -4,4 +4,4 @@
4
4
  /**
5
5
  * The default Cloudflare Workers compatibility date, set based on the latest workerd release at the time of build.
6
6
  */
7
- export const DEFAULT_COMPATIBILITY_DATE = "2025-09-24";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-09-27";
@@ -950,14 +950,13 @@ export async function updateContainerApplication(
950
950
  );
951
951
  const result = (await response.json()) as {
952
952
  result: ContainerApplicationData;
953
- errors: { message: string }[];
953
+ errors: { message: string; code: number }[];
954
954
  };
955
955
  if (response.ok) {
956
956
  return result.result;
957
957
  }
958
-
959
958
  throw Error(
960
- `Failed to create container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
959
+ `Failed to create container application: ${result.errors?.map((e) => `[${e.code}] ${e.message}`).join(", ") ?? "Unknown error"}`,
961
960
  );
962
961
  }
963
962
 
@@ -13,6 +13,7 @@ export * from "./astro/astro.ts";
13
13
  export * from "./bindings.ts";
14
14
  export * from "./bound.ts";
15
15
  export * from "./browser-rendering.ts";
16
+ export * from "./bucket-object.ts";
16
17
  export * from "./bucket.ts";
17
18
  export * from "./certificate-pack.ts";
18
19
  export * from "./cloudflare-env-proxy.ts";
@@ -113,7 +113,7 @@ const normalizeCommand = (
113
113
  command: string;
114
114
  env: Record<string, string>;
115
115
  },
116
- ): WebsiteProps<any>["build"] => {
116
+ ) => {
117
117
  return {
118
118
  command:
119
119
  typeof input === "string" ? input : (input?.command ?? defaults.command),
@@ -122,5 +122,5 @@ const normalizeCommand = (
122
122
  ...(typeof input === "object" ? (input?.env ?? {}) : {}),
123
123
  },
124
124
  memoize: typeof input === "object" ? input?.memoize : undefined,
125
- };
125
+ } satisfies WebsiteProps<any>["build"];
126
126
  };
@@ -2,7 +2,12 @@ import path from "node:path";
2
2
  import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
3
3
  import type { Assets } from "../assets.ts";
4
4
  import type { Bindings } from "../bindings.ts";
5
- import { Website, type WebsiteProps } from "../website.ts";
5
+ import {
6
+ spreadBuildProps,
7
+ spreadDevProps,
8
+ Website,
9
+ type WebsiteProps,
10
+ } from "../website.ts";
6
11
  import type { Worker } from "../worker.ts";
7
12
 
8
13
  /**
@@ -55,8 +60,8 @@ export async function Nuxt<B extends Bindings>(
55
60
  return await Website(id, {
56
61
  ...props,
57
62
  noBundle: props?.noBundle ?? true,
58
- build: props?.build ?? `${runner} nuxt build`,
59
- dev: props?.dev ?? `${runner} nuxt dev`,
63
+ build: spreadBuildProps(props, `${runner} nuxt build`),
64
+ dev: spreadDevProps(props, `${runner} nuxt dev`),
60
65
  compatibility: "node",
61
66
  // Default entry point for cloudflare-module preset
62
67
  entrypoint: props?.entrypoint ?? "./.output/server/index.mjs",
@@ -1,7 +1,12 @@
1
1
  import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
2
2
  import type { Assets } from "../assets.ts";
3
3
  import type { Bindings } from "../bindings.ts";
4
- import { Website, type WebsiteProps } from "../website.ts";
4
+ import {
5
+ spreadBuildProps,
6
+ spreadDevProps,
7
+ Website,
8
+ type WebsiteProps,
9
+ } from "../website.ts";
5
10
  import type { Worker } from "../worker.ts";
6
11
 
7
12
  export interface ViteProps<B extends Bindings> extends WebsiteProps<B> {}
@@ -27,7 +32,7 @@ export async function Vite<B extends Bindings>(
27
32
  props.assets?.directory ??
28
33
  (props.entrypoint || props.script ? "dist/client" : "dist"),
29
34
  },
30
- build: props.build ?? `${runner} vite build`,
31
- dev: props.dev ?? `${runner} vite dev`,
35
+ build: spreadBuildProps(props, `${runner} vite build`),
36
+ dev: spreadDevProps(props, `${runner} vite dev`),
32
37
  });
33
38
  }
@@ -27,7 +27,7 @@ export interface WebsiteProps<B extends Bindings>
27
27
  /**
28
28
  * The command to run to build the site
29
29
  */
30
- command: string;
30
+ command?: string;
31
31
  /**
32
32
  * Additional environment variables to set when running the build command
33
33
  */
@@ -287,7 +287,7 @@ export async function Website<B extends Bindings>(
287
287
 
288
288
  const scope = Scope.current;
289
289
 
290
- if (build && !scope.local) {
290
+ if (build?.command && !scope.local) {
291
291
  await Exec(`${id}-build`, {
292
292
  cwd: path.relative(process.cwd(), paths.cwd),
293
293
  command: build.command,
@@ -353,3 +353,29 @@ export async function Website<B extends Bindings>(
353
353
  dev: url ? { url } : undefined,
354
354
  })) as Website<B>;
355
355
  }
356
+
357
+ export const spreadBuildProps = (
358
+ props: { build?: WebsiteProps<Bindings>["build"] } | undefined,
359
+ defaultCommand: string,
360
+ ): WebsiteProps<Bindings>["build"] => {
361
+ if (typeof props?.build === "object") {
362
+ return {
363
+ ...props.build,
364
+ command: props.build.command ?? defaultCommand,
365
+ };
366
+ }
367
+ return props?.build ?? defaultCommand;
368
+ };
369
+
370
+ export const spreadDevProps = (
371
+ props: { dev?: WebsiteProps<Bindings>["dev"] } | undefined,
372
+ defaultCommand: string,
373
+ ): WebsiteProps<Bindings>["dev"] => {
374
+ if (typeof props?.dev === "object") {
375
+ return {
376
+ ...props.dev,
377
+ command: props.dev.command ?? defaultCommand,
378
+ };
379
+ }
380
+ return props?.dev ?? defaultCommand;
381
+ };