alchemy 0.70.2 → 0.71.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 (88) hide show
  1. package/bin/alchemy.js +13 -15
  2. package/bin/commands/init.ts +0 -10
  3. package/lib/apply.d.ts.map +1 -1
  4. package/lib/apply.js +2 -0
  5. package/lib/apply.js.map +1 -1
  6. package/lib/cloudflare/bindings.d.ts +12 -2
  7. package/lib/cloudflare/bindings.d.ts.map +1 -1
  8. package/lib/cloudflare/bindings.js.map +1 -1
  9. package/lib/cloudflare/bound.d.ts +3 -2
  10. package/lib/cloudflare/bound.d.ts.map +1 -1
  11. package/lib/cloudflare/bucket.d.ts +6 -0
  12. package/lib/cloudflare/bucket.d.ts.map +1 -1
  13. package/lib/cloudflare/bucket.js +5 -3
  14. package/lib/cloudflare/bucket.js.map +1 -1
  15. package/lib/cloudflare/bun-spa/bun-spa.d.ts +6 -1
  16. package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -1
  17. package/lib/cloudflare/bun-spa/bun-spa.js +29 -9
  18. package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -1
  19. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  20. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  21. package/lib/cloudflare/index.d.ts +2 -0
  22. package/lib/cloudflare/index.d.ts.map +1 -1
  23. package/lib/cloudflare/index.js +2 -0
  24. package/lib/cloudflare/index.js.map +1 -1
  25. package/lib/cloudflare/logpush-job.d.ts +349 -0
  26. package/lib/cloudflare/logpush-job.d.ts.map +1 -0
  27. package/lib/cloudflare/logpush-job.js +277 -0
  28. package/lib/cloudflare/logpush-job.js.map +1 -0
  29. package/lib/cloudflare/miniflare/build-worker-options.d.ts +1 -0
  30. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  31. package/lib/cloudflare/miniflare/build-worker-options.js +16 -6
  32. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  33. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  34. package/lib/cloudflare/miniflare/remote-binding-proxy.js +2 -0
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  36. package/lib/cloudflare/queue-consumer.js +2 -2
  37. package/lib/cloudflare/queue-consumer.js.map +1 -1
  38. package/lib/cloudflare/worker-loader.d.ts +9 -0
  39. package/lib/cloudflare/worker-loader.d.ts.map +1 -0
  40. package/lib/cloudflare/worker-loader.js +6 -0
  41. package/lib/cloudflare/worker-loader.js.map +1 -0
  42. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  43. package/lib/cloudflare/worker-metadata.js +6 -0
  44. package/lib/cloudflare/worker-metadata.js.map +1 -1
  45. package/lib/cloudflare/worker-subdomain.js +1 -1
  46. package/lib/cloudflare/worker.d.ts.map +1 -1
  47. package/lib/cloudflare/worker.js +7 -0
  48. package/lib/cloudflare/worker.js.map +1 -1
  49. package/lib/cloudflare/wrangler.json.d.ts +3 -0
  50. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  51. package/lib/cloudflare/wrangler.json.js +9 -0
  52. package/lib/cloudflare/wrangler.json.js.map +1 -1
  53. package/lib/github/comment.d.ts.map +1 -1
  54. package/lib/github/comment.js +14 -7
  55. package/lib/github/comment.js.map +1 -1
  56. package/lib/scope.d.ts.map +1 -1
  57. package/lib/scope.js +6 -7
  58. package/lib/scope.js.map +1 -1
  59. package/lib/util/retry.d.ts +1 -1
  60. package/lib/util/retry.d.ts.map +1 -1
  61. package/lib/util/retry.js +2 -2
  62. package/lib/util/retry.js.map +1 -1
  63. package/lib/util/validate-resource-id.d.ts +2 -0
  64. package/lib/util/validate-resource-id.d.ts.map +1 -0
  65. package/lib/util/validate-resource-id.js +11 -0
  66. package/lib/util/validate-resource-id.js.map +1 -0
  67. package/package.json +2 -2
  68. package/src/apply.ts +2 -0
  69. package/src/cloudflare/bindings.ts +13 -0
  70. package/src/cloudflare/bound.ts +36 -32
  71. package/src/cloudflare/bucket.ts +13 -4
  72. package/src/cloudflare/bun-spa/bun-spa.ts +42 -10
  73. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  74. package/src/cloudflare/index.ts +2 -0
  75. package/src/cloudflare/logpush-job.ts +768 -0
  76. package/src/cloudflare/miniflare/build-worker-options.ts +18 -6
  77. package/src/cloudflare/miniflare/remote-binding-proxy.ts +2 -0
  78. package/src/cloudflare/queue-consumer.ts +2 -2
  79. package/src/cloudflare/worker-loader.ts +12 -0
  80. package/src/cloudflare/worker-metadata.ts +5 -0
  81. package/src/cloudflare/worker-subdomain.ts +1 -1
  82. package/src/cloudflare/worker.ts +8 -0
  83. package/src/cloudflare/wrangler.json.ts +15 -0
  84. package/src/github/comment.ts +17 -8
  85. package/src/scope.ts +7 -10
  86. package/src/util/retry.ts +2 -1
  87. package/src/util/validate-resource-id.ts +10 -0
  88. package/workers/tunnel-proxy.js +1 -1
@@ -9,5 +9,5 @@
9
9
  * @returns Result of the operation
10
10
  * @throws The last error encountered if all retries fail
11
11
  */
12
- export declare function withExponentialBackoff<T>(operation: () => Promise<T>, isRetryable: (error: any) => any, maxAttempts?: number, initialDelayMs?: number): Promise<T>;
12
+ export declare function withExponentialBackoff<T>(operation: () => Promise<T>, isRetryable: (error: any) => any, maxAttempts?: number, initialDelayMs?: number, maxDelayMs?: number): Promise<T>;
13
13
  //# sourceMappingURL=retry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/util/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,EAChC,WAAW,SAAI,EACf,cAAc,SAAM,GACnB,OAAO,CAAC,CAAC,CAAC,CAoBZ"}
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/util/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,EAChC,WAAW,SAAI,EACf,cAAc,SAAM,EACpB,UAAU,SAAQ,GACjB,OAAO,CAAC,CAAC,CAAC,CAoBZ"}
package/lib/util/retry.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * @returns Result of the operation
10
10
  * @throws The last error encountered if all retries fail
11
11
  */
12
- export async function withExponentialBackoff(operation, isRetryable, maxAttempts = 5, initialDelayMs = 100) {
12
+ export async function withExponentialBackoff(operation, isRetryable, maxAttempts = 5, initialDelayMs = 100, maxDelayMs = 10000) {
13
13
  let attempt = 0;
14
14
  let delay = initialDelayMs;
15
15
  while (true) {
@@ -25,7 +25,7 @@ export async function withExponentialBackoff(operation, isRetryable, maxAttempts
25
25
  const jitter = Math.random() * 0.1 * delay;
26
26
  await new Promise((resolve) => setTimeout(resolve, delay + jitter));
27
27
  delay *= 2; // Double the delay for next attempt
28
- delay = Math.min(delay, 10000); // Cap at 10 seconds
28
+ delay = Math.min(delay, maxDelayMs); // Cap at 10 seconds
29
29
  }
30
30
  }
31
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/util/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,SAA2B,EAC3B,WAAgC,EAChC,WAAW,GAAG,CAAC,EACf,cAAc,GAAG,GAAG;IAEpB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAK,GAAG,cAAc,CAAC;IAE3B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,IAAI,OAAO,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,KAAK,CAAC;YACd,CAAC;YAED,kCAAkC;YAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,KAAK,CAAC;YAC3C,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;YACpE,KAAK,IAAI,CAAC,CAAC,CAAC,oCAAoC;YAChD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,oBAAoB;QACtD,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/util/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,SAA2B,EAC3B,WAAgC,EAChC,WAAW,GAAG,CAAC,EACf,cAAc,GAAG,GAAG,EACpB,UAAU,GAAG,KAAK;IAElB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAK,GAAG,cAAc,CAAC;IAE3B,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;YACV,IAAI,OAAO,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,KAAK,CAAC;YACd,CAAC;YAED,kCAAkC;YAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,KAAK,CAAC;YAC3C,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;YACpE,KAAK,IAAI,CAAC,CAAC,CAAC,oCAAoC;YAChD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,oBAAoB;QAC3D,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function validateResourceID(id: string, qualifier?: string): void;
2
+ //# sourceMappingURL=validate-resource-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-resource-id.d.ts","sourceRoot":"","sources":["../../src/util/validate-resource-id.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,SAAa,GAAG,IAAI,CAS3E"}
@@ -0,0 +1,11 @@
1
+ export function validateResourceID(id, qualifier = "Resource") {
2
+ //todo(sam): during effect rewrite, this should be much more limiting
3
+ //todo: its hard to undo the flexibility we've offered to users now
4
+ if (!id) {
5
+ throw new Error(`${qualifier} ID cannot be an empty string: "${id}"`);
6
+ }
7
+ if (id.includes(":")) {
8
+ throw new Error(`${qualifier} ID cannot include colons: "${id}"`);
9
+ }
10
+ }
11
+ //# sourceMappingURL=validate-resource-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-resource-id.js","sourceRoot":"","sources":["../../src/util/validate-resource-id.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,EAAU,EAAE,SAAS,GAAG,UAAU;IACnE,qEAAqE;IACrE,wEAAwE;IACxE,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,mCAAmC,EAAE,GAAG,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.70.2",
3
+ "version": "0.71.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Sam Goodwin <sam@alchemy.run>",
6
6
  "homepage": "https://alchemy.run",
@@ -161,6 +161,7 @@
161
161
  "@aws-sdk/credential-providers": "^3.0.0",
162
162
  "@cloudflare/unenv-preset": "^2.6.3",
163
163
  "@iarna/toml": "^2.2.5",
164
+ "@octokit/rest": "^21.1.1",
164
165
  "@smithy/node-config-provider": "^4.0.0",
165
166
  "@smithy/types": "^4.6.0",
166
167
  "aws4fetch": "^1.0.20",
@@ -199,7 +200,6 @@
199
200
  "@cloudflare/vite-plugin": "^1.10.1",
200
201
  "@cloudflare/workers-types": "^4.20250805.0",
201
202
  "@libsql/client": "^0.15.12",
202
- "@octokit/rest": "^21.1.1",
203
203
  "@opennextjs/cloudflare": "^1.6.5",
204
204
  "astro": "^5.13.2",
205
205
  "drizzle-orm": "^0.44.2",
package/src/apply.ts CHANGED
@@ -20,6 +20,7 @@ import type { State } from "./state.ts";
20
20
  import { formatFQN } from "./util/cli.ts";
21
21
  import { logger } from "./util/logger.ts";
22
22
  import { createAndSendEvent } from "./util/telemetry.ts";
23
+ import { validateResourceID } from "./util/validate-resource-id.ts";
23
24
 
24
25
  export interface ApplyOptions {
25
26
  quiet?: boolean;
@@ -56,6 +57,7 @@ async function _apply<Out extends ResourceAttributes>(
56
57
  ): Promise<Awaited<Out> & Resource> {
57
58
  const scope = resource[ResourceScope];
58
59
  const start = performance.now();
60
+ validateResourceID(resource[ResourceID], "Resource");
59
61
  try {
60
62
  const quiet = props?.quiet ?? scope.quiet;
61
63
  await scope.init();
@@ -29,6 +29,7 @@ import type { VersionMetadata } from "./version-metadata.ts";
29
29
  import type { WorkerRef } from "./worker-ref.ts";
30
30
  import type { WorkerStub } from "./worker-stub.ts";
31
31
  import type { Worker } from "./worker.ts";
32
+ import type { WorkerLoader } from "./worker-loader.ts";
32
33
  import type { Workflow } from "./workflow.ts";
33
34
 
34
35
  export type Bindings = {
@@ -74,6 +75,7 @@ export type Binding =
74
75
  | WorkerStub
75
76
  | WorkerRef
76
77
  | WorkerEntrypoint
78
+ | WorkerLoader
77
79
  | Workflow
78
80
  | BrowserRendering
79
81
  | VersionMetadata
@@ -138,6 +140,7 @@ export type WorkerBindingSpec =
138
140
  | WorkerBindingVectorize
139
141
  | WorkerBindingVersionMetadata
140
142
  | WorkerBindingWasmModule
143
+ | WorkerBindingWorkerLoader
141
144
  | WorkerBindingWorkflow;
142
145
 
143
146
  /**
@@ -515,3 +518,13 @@ export interface WorkerBindingPipeline {
515
518
  /** Pipeline name */
516
519
  pipeline: string;
517
520
  }
521
+
522
+ /**
523
+ * Worker Loader binding type
524
+ */
525
+ export interface WorkerBindingWorkerLoader {
526
+ /** The name of the binding */
527
+ name: string;
528
+ /** Type identifier for Worker Loader binding */
529
+ type: "worker_loader";
530
+ }
@@ -4,6 +4,7 @@ import type {
4
4
  D1Database,
5
5
  DispatchNamespace,
6
6
  DurableObjectNamespace,
7
+ WorkerLoader,
7
8
  Fetcher,
8
9
  Hyperdrive,
9
10
  ImagesBinding,
@@ -43,6 +44,7 @@ import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts"
43
44
  import type { WorkerRef } from "./worker-ref.ts";
44
45
  import type { WorkerStub } from "./worker-stub.ts";
45
46
  import type { Worker as _Worker } from "./worker.ts";
47
+ import type { WorkerLoader as _WorkerLoader } from "./worker-loader.ts";
46
48
  import type { Workflow as _Workflow } from "./workflow.ts";
47
49
 
48
50
  type BoundWorker<
@@ -86,35 +88,37 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
86
88
  ? D1Database
87
89
  : T extends DispatchNamespace
88
90
  ? DispatchNamespace
89
- : T extends _VectorizeIndex
90
- ? VectorizeIndex
91
- : T extends _Queue<infer Body>
92
- ? Queue<Body>
93
- : T extends _AnalyticsEngineDataset
94
- ? AnalyticsEngineDataset
95
- : T extends _Pipeline<infer R>
96
- ? Pipeline<R>
97
- : T extends _RateLimit
98
- ? RateLimit
99
- : T extends string
100
- ? T
101
- : T extends BrowserRendering
102
- ? Fetcher
103
- : T extends _Ai<infer M>
104
- ? Ai<M>
105
- : T extends _Images
106
- ? ImagesBinding
107
- : T extends _VersionMetadata
108
- ? WorkerVersionMetadata
109
- : T extends Self
110
- ? Service
111
- : T extends Json<infer T>
112
- ? T
113
- : T extends _Container<
114
- infer Obj
115
- >
116
- ? DurableObjectNamespace<
117
- Obj &
118
- Rpc.DurableObjectBranded
119
- >
120
- : Service;
91
+ : T extends _WorkerLoader
92
+ ? WorkerLoader
93
+ : T extends _VectorizeIndex
94
+ ? VectorizeIndex
95
+ : T extends _Queue<infer Body>
96
+ ? Queue<Body>
97
+ : T extends _AnalyticsEngineDataset
98
+ ? AnalyticsEngineDataset
99
+ : T extends _Pipeline<infer R>
100
+ ? Pipeline<R>
101
+ : T extends _RateLimit
102
+ ? RateLimit
103
+ : T extends string
104
+ ? T
105
+ : T extends BrowserRendering
106
+ ? Fetcher
107
+ : T extends _Ai<infer M>
108
+ ? Ai<M>
109
+ : T extends _Images
110
+ ? ImagesBinding
111
+ : T extends _VersionMetadata
112
+ ? WorkerVersionMetadata
113
+ : T extends Self
114
+ ? Service
115
+ : T extends Json<infer T>
116
+ ? T
117
+ : T extends _Container<
118
+ infer Obj
119
+ >
120
+ ? DurableObjectNamespace<
121
+ Obj &
122
+ Rpc.DurableObjectBranded
123
+ >
124
+ : Service;
@@ -347,6 +347,13 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
347
347
  * Whether the bucket is running remotely
348
348
  */
349
349
  remote: boolean;
350
+
351
+ /**
352
+ * Whether the bucket has been deployed
353
+ *
354
+ * @internal
355
+ */
356
+ isDeployed?: boolean;
350
357
  };
351
358
 
352
359
  /**
@@ -587,15 +594,17 @@ const _R2Bucket = Resource(
587
594
  }
588
595
 
589
596
  const allowPublicAccess = props.allowPublicAccess === true;
597
+ const isLocal = this.scope.local && !props.dev?.remote;
590
598
  const dev = {
591
599
  id: this.output?.dev?.id ?? bucketName,
592
600
  remote: props.dev?.remote ?? false,
593
- };
601
+ isDeployed: this.output?.dev?.isDeployed || !isLocal,
602
+ } satisfies _R2Bucket["dev"];
594
603
  const adopt = props.adopt ?? this.scope.adopt;
595
604
 
596
- if (this.scope.local && !props.dev?.remote) {
605
+ if (isLocal) {
597
606
  return {
598
- name: this.output?.name ?? "",
607
+ name: bucketName,
599
608
  location: this.output?.location ?? "",
600
609
  creationDate: this.output?.creationDate ?? new Date(),
601
610
  jurisdiction: this.output?.jurisdiction ?? "default",
@@ -626,7 +635,7 @@ const _R2Bucket = Resource(
626
635
  return this.destroy();
627
636
  }
628
637
 
629
- if (this.phase === "create" || !this.output?.name) {
638
+ if (this.phase === "create" || !this.output?.dev?.isDeployed) {
630
639
  const bucket = await createBucket(api, bucketName, props).catch(
631
640
  async (err) => {
632
641
  if (
@@ -15,7 +15,12 @@ import {
15
15
  } from "../website.ts";
16
16
 
17
17
  export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
18
- frontend: string;
18
+ /**
19
+ * The path to the frontend entrypoints that bun should bundle for deployment & serve in dev mode.
20
+ * These are usually html files. Glob patterns are supported.
21
+ * Typically set to index.html
22
+ */
23
+ frontend: string | string[];
19
24
  outDir?: string;
20
25
  }
21
26
 
@@ -25,15 +30,39 @@ export async function BunSPA<B extends Bindings>(
25
30
  id: string,
26
31
  props: BunSPAProps<B>,
27
32
  ): Promise<BunSPA<B> & { apiUrl: string }> {
28
- const frontendPath = path.resolve(props.frontend);
29
- if (!(await exists(frontendPath))) {
30
- throw new Error(`Frontend path ${frontendPath} does not exist`);
31
- }
33
+ const frontendPaths = Array.isArray(props.frontend)
34
+ ? props.frontend.map((p) => path.resolve(p))
35
+ : [path.resolve(props.frontend)];
36
+
37
+ // Helper to check if a path contains glob patterns
38
+ const isGlobPattern = (p: string) =>
39
+ p.includes("*") || p.includes("?") || p.includes("[") || p.includes("]");
40
+
41
+ // Only validate non-glob paths
42
+ const nonGlobPaths = frontendPaths.filter((p) => !isGlobPattern(p));
32
43
 
33
- const stats = await fs.stat(frontendPath);
34
- if (!stats.isFile()) {
35
- throw new Error(`Frontend path ${frontendPath} is not a file`);
44
+ if (nonGlobPaths.length > 0) {
45
+ const existsPromises = nonGlobPaths.map((p) => exists(p));
46
+ const existsResults = await Promise.all(existsPromises);
47
+ const missingPaths = nonGlobPaths.filter((_p, i) => !existsResults[i]);
48
+ if (missingPaths.length > 0) {
49
+ if (missingPaths.length === 1) {
50
+ throw new Error(`Frontend path ${missingPaths[0]} does not exist`);
51
+ }
52
+ throw new Error(`Frontend paths ${missingPaths.join(", ")} do not exist`);
53
+ }
54
+
55
+ const statsPromises = nonGlobPaths.map((p) => fs.stat(p));
56
+ const statsResults = await Promise.all(statsPromises);
57
+ const notFiles = nonGlobPaths.filter((_, i) => !statsResults[i].isFile());
58
+ if (notFiles.length > 0) {
59
+ if (notFiles.length === 1) {
60
+ throw new Error(`Frontend path ${notFiles[0]} is not a file`);
61
+ }
62
+ throw new Error(`Frontend paths ${notFiles.join(", ")} are not files`);
63
+ }
36
64
  }
65
+
37
66
  const outDir = path.resolve(props.outDir ?? "dist/client");
38
67
 
39
68
  if (props.assets) {
@@ -58,7 +87,7 @@ export async function BunSPA<B extends Bindings>(
58
87
  },
59
88
  build: spreadBuildProps(
60
89
  props,
61
- `bun build '${frontendPath}' --outdir ${outDir}`,
90
+ `bun build '${frontendPaths.join("' '")}' --outdir ${outDir}`,
62
91
  ),
63
92
  });
64
93
 
@@ -67,9 +96,12 @@ export async function BunSPA<B extends Bindings>(
67
96
  if (scope.local) {
68
97
  const cwd = props.cwd ?? process.cwd();
69
98
  await validateBunfigToml(cwd);
99
+ const frontendPathsRelativeToCwd = frontendPaths.map((p) =>
100
+ path.relative(cwd, p),
101
+ );
70
102
  const dev = spreadDevProps(
71
103
  props,
72
- `bun '${path.relative(cwd, frontendPath)}'`,
104
+ `bun '${frontendPathsRelativeToCwd.join("' '")}'`,
73
105
  );
74
106
  const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
75
107
  const env = {
@@ -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-10-01";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-10-08";
@@ -36,6 +36,7 @@ export * from "./hyperdrive-ref.ts";
36
36
  export * from "./hyperdrive.ts";
37
37
  export * from "./images.ts";
38
38
  export * from "./kv-namespace.ts";
39
+ export * from "./logpush-job.ts";
39
40
  export * from "./next.ts";
40
41
  export * from "./nuxt/nuxt.ts";
41
42
  export * from "./orange.ts";
@@ -69,5 +70,6 @@ export * from "./worker-stub.ts";
69
70
  export * from "./worker-subdomain.ts";
70
71
  export * from "./worker.ts";
71
72
  export { Workflow } from "./workflow.ts";
73
+ export { WorkerLoader } from "./worker-loader.ts";
72
74
  export * from "./wrangler.json.ts";
73
75
  export * from "./zone.ts";