alchemy 0.47.0 → 0.48.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 (109) hide show
  1. package/bin/alchemy.mjs +63 -56
  2. package/lib/alchemy.d.ts +9 -3
  3. package/lib/alchemy.d.ts.map +1 -1
  4. package/lib/alchemy.js +19 -39
  5. package/lib/alchemy.js.map +1 -1
  6. package/lib/cloudflare/astro.d.ts.map +1 -1
  7. package/lib/cloudflare/astro.js +3 -0
  8. package/lib/cloudflare/astro.js.map +1 -1
  9. package/lib/cloudflare/compatibility-date.gen.d.ts +5 -0
  10. package/lib/cloudflare/compatibility-date.gen.d.ts.map +1 -0
  11. package/lib/cloudflare/compatibility-date.gen.js +7 -0
  12. package/lib/cloudflare/compatibility-date.gen.js.map +1 -0
  13. package/lib/cloudflare/container.js +1 -1
  14. package/lib/cloudflare/container.js.map +1 -1
  15. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  16. package/lib/cloudflare/d1-migrations.js +6 -11
  17. package/lib/cloudflare/d1-migrations.js.map +1 -1
  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/miniflare/miniflare-worker-options.d.ts +2 -2
  23. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
  24. package/lib/cloudflare/miniflare/miniflare-worker-options.js +11 -2
  25. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
  26. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +20 -0
  27. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -0
  28. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +80 -0
  29. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -0
  30. package/lib/cloudflare/miniflare/miniflare.d.ts +6 -6
  31. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
  32. package/lib/cloudflare/miniflare/miniflare.js +25 -16
  33. package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
  34. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +10 -10
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  36. package/lib/cloudflare/miniflare/remote-binding-proxy.js +34 -44
  37. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  38. package/lib/cloudflare/nuxt.d.ts.map +1 -1
  39. package/lib/cloudflare/nuxt.js +3 -0
  40. package/lib/cloudflare/nuxt.js.map +1 -1
  41. package/lib/cloudflare/oauth.js +3 -3
  42. package/lib/cloudflare/oauth.js.map +1 -1
  43. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  44. package/lib/cloudflare/pipeline.js +1 -3
  45. package/lib/cloudflare/pipeline.js.map +1 -1
  46. package/lib/cloudflare/react-router.d.ts.map +1 -1
  47. package/lib/cloudflare/react-router.js +3 -0
  48. package/lib/cloudflare/react-router.js.map +1 -1
  49. package/lib/cloudflare/vite.d.ts.map +1 -1
  50. package/lib/cloudflare/vite.js +0 -1
  51. package/lib/cloudflare/vite.js.map +1 -1
  52. package/lib/cloudflare/website.d.ts +0 -1
  53. package/lib/cloudflare/website.d.ts.map +1 -1
  54. package/lib/cloudflare/website.js +15 -8
  55. package/lib/cloudflare/website.js.map +1 -1
  56. package/lib/cloudflare/worker.d.ts +8 -4
  57. package/lib/cloudflare/worker.d.ts.map +1 -1
  58. package/lib/cloudflare/worker.js +82 -72
  59. package/lib/cloudflare/worker.js.map +1 -1
  60. package/lib/scope.d.ts +14 -2
  61. package/lib/scope.d.ts.map +1 -1
  62. package/lib/scope.js +8 -3
  63. package/lib/scope.js.map +1 -1
  64. package/lib/state/cloudflare-state-store.d.ts.map +1 -1
  65. package/lib/state/cloudflare-state-store.js +2 -2
  66. package/lib/state/cloudflare-state-store.js.map +1 -1
  67. package/lib/util/http.d.ts +13 -0
  68. package/lib/util/http.d.ts.map +1 -0
  69. package/lib/util/http.js +78 -0
  70. package/lib/util/http.js.map +1 -0
  71. package/lib/util/safe-fetch.d.ts.map +1 -1
  72. package/lib/util/safe-fetch.js +1 -0
  73. package/lib/util/safe-fetch.js.map +1 -1
  74. package/package.json +5 -4
  75. package/src/alchemy.ts +28 -49
  76. package/src/cloudflare/astro.ts +3 -0
  77. package/src/cloudflare/compatibility-date.gen.ts +7 -0
  78. package/src/cloudflare/container.ts +1 -1
  79. package/src/cloudflare/d1-migrations.ts +8 -21
  80. package/src/cloudflare/index.ts +1 -0
  81. package/src/cloudflare/miniflare/miniflare-worker-options.ts +13 -1
  82. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +97 -0
  83. package/src/cloudflare/miniflare/miniflare.ts +30 -21
  84. package/src/cloudflare/miniflare/remote-binding-proxy.ts +40 -52
  85. package/src/cloudflare/nuxt.ts +3 -0
  86. package/src/cloudflare/oauth.ts +3 -3
  87. package/src/cloudflare/pipeline.ts +1 -3
  88. package/src/cloudflare/react-router.ts +3 -0
  89. package/src/cloudflare/vite.ts +0 -1
  90. package/src/cloudflare/website.ts +16 -14
  91. package/src/cloudflare/worker.ts +104 -106
  92. package/src/scope.ts +21 -4
  93. package/src/state/cloudflare-state-store.ts +2 -2
  94. package/src/util/http.ts +94 -0
  95. package/src/util/safe-fetch.ts +1 -0
  96. package/templates/react-router/package.json +1 -1
  97. package/templates/react-router/tsconfig.cloudflare.json +5 -1
  98. package/templates/react-router/tsconfig.json +0 -3
  99. package/templates/react-router/tsconfig.node.json +0 -4
  100. package/lib/build-date.d.ts +0 -6
  101. package/lib/build-date.d.ts.map +0 -1
  102. package/lib/build-date.js +0 -8
  103. package/lib/build-date.js.map +0 -1
  104. package/lib/util/http-server.d.ts +0 -14
  105. package/lib/util/http-server.d.ts.map +0 -1
  106. package/lib/util/http-server.js +0 -71
  107. package/lib/util/http-server.js.map +0 -1
  108. package/src/build-date.ts +0 -8
  109. package/src/util/http-server.ts +0 -84
@@ -2,14 +2,11 @@ import path from "node:path";
2
2
  import { alchemy } from "../alchemy.ts";
3
3
  import { Exec } from "../os/exec.ts";
4
4
  import { Scope } from "../scope.ts";
5
+ import { detectPackageManager } from "../util/detect-package-manager.ts";
5
6
  import { Assets } from "./assets.ts";
6
7
  import type { Bindings } from "./bindings.ts";
7
- import {
8
- DEFAULT_COMPATIBILITY_DATE,
9
- Worker,
10
- type AssetsConfig,
11
- type WorkerProps,
12
- } from "./worker.ts";
8
+ import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
9
+ import { Worker, type AssetsConfig, type WorkerProps } from "./worker.ts";
13
10
  import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
14
11
 
15
12
  export interface WebsiteProps<B extends Bindings>
@@ -100,7 +97,6 @@ export interface WebsiteProps<B extends Bindings>
100
97
  */
101
98
  dev?: {
102
99
  command: string;
103
- url: string;
104
100
  };
105
101
 
106
102
  /**
@@ -123,6 +119,15 @@ export interface WebsiteProps<B extends Bindings>
123
119
  };
124
120
  }
125
121
 
122
+ const packageManager = await detectPackageManager();
123
+ const devCommand = {
124
+ npm: "npx vite dev",
125
+ bun: "bun vite dev",
126
+ pnpm: "pnpm vite dev",
127
+ yarn: "yarn vite dev",
128
+ deno: "deno task dev",
129
+ }[packageManager];
130
+
126
131
  export type Website<B extends Bindings> = B extends { ASSETS: any }
127
132
  ? never
128
133
  : Worker<B & { ASSETS: Assets }>;
@@ -189,12 +194,9 @@ export default {
189
194
  };`,
190
195
  url: props.url ?? true,
191
196
  adopt: props.adopt ?? true,
192
- dev: props.dev
193
- ? {
194
- command: props.dev.command,
195
- url: props.dev.url,
196
- }
197
- : undefined,
197
+ dev: {
198
+ command: props.dev?.command ?? devCommand,
199
+ },
198
200
  } as WorkerProps<any> & { name: string };
199
201
 
200
202
  if (wrangler) {
@@ -216,7 +218,7 @@ export default {
216
218
  });
217
219
  }
218
220
 
219
- const isDev = scope.dev && !!props.dev;
221
+ const isDev = scope.local;
220
222
 
221
223
  if (props.command && !isDev) {
222
224
  await Exec("build", {
@@ -1,16 +1,9 @@
1
1
  import type esbuild from "esbuild";
2
2
  import kleur from "kleur";
3
3
  import { spawn } from "node:child_process";
4
- import {
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- unlinkSync,
9
- writeFileSync,
10
- } from "node:fs";
4
+ import fs from "node:fs/promises";
11
5
  import { isDeepStrictEqual } from "node:util";
12
6
  import path from "pathe";
13
- import { BUILD_DATE } from "../build-date.ts";
14
7
  import type { Context } from "../context.ts";
15
8
  import type { BundleProps } from "../esbuild/bundle.ts";
16
9
  import { InnerResourceScope, Resource, ResourceKind } from "../resource.ts";
@@ -18,7 +11,7 @@ import { getBindKey, tryGetBinding } from "../runtime/bind.ts";
18
11
  import { isRuntime } from "../runtime/global.ts";
19
12
  import { bootstrapPlugin } from "../runtime/plugin.ts";
20
13
  import { Scope } from "../scope.ts";
21
- import { Secret, secret } from "../secret.ts";
14
+ import { Secret, isSecret, secret } from "../secret.ts";
22
15
  import { serializeScope } from "../serde.ts";
23
16
  import type { type } from "../type.ts";
24
17
  import { DeferredPromise } from "../util/deferred-promise.ts";
@@ -49,6 +42,7 @@ import {
49
42
  normalizeWorkerBundle,
50
43
  } from "./bundle/index.ts";
51
44
  import { wrap } from "./bundle/normalize.ts";
45
+ import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
52
46
  import {
53
47
  type CompatibilityPreset,
54
48
  unionCompatibilityFlags,
@@ -92,6 +86,7 @@ import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.ts";
92
86
  // Previous versions of `Worker` used the `Bundle` resource.
93
87
  // This import is here to avoid errors when destroying the `Bundle` resource.
94
88
  import "../esbuild/bundle.ts";
89
+ import { exists } from "../util/exists.ts";
95
90
 
96
91
  /**
97
92
  * Configuration options for static assets
@@ -217,7 +212,7 @@ export interface BaseWorkerProps<
217
212
 
218
213
  /**
219
214
  * The compatibility date for the worker
220
- * @default BUILD_DATE - automatically pinned to the package build date
215
+ * @default DEFAULT_WORKER_COMPATIBILITY_DATE - automatically pinned to the latest Workers release
221
216
  */
222
217
  compatibilityDate?: string;
223
218
 
@@ -353,7 +348,6 @@ export interface BaseWorkerProps<
353
348
  * the worker will be emulated locally and available at a randomly selected port.
354
349
  */
355
350
  dev?:
356
- | boolean
357
351
  | {
358
352
  /**
359
353
  * Port to use for local development
@@ -365,11 +359,16 @@ export interface BaseWorkerProps<
365
359
  * @default false
366
360
  */
367
361
  remote?: boolean;
362
+ /** @internal */
363
+ command?: undefined;
368
364
  }
369
365
  | {
370
366
  command: string;
371
- url: string;
372
367
  cwd?: string;
368
+ /** @internal */
369
+ port?: undefined;
370
+ /** @internal */
371
+ remote?: undefined;
373
372
  };
374
373
 
375
374
  /**
@@ -1014,8 +1013,6 @@ export function Worker<const B extends Bindings>(
1014
1013
  });
1015
1014
  }
1016
1015
 
1017
- export const DEFAULT_COMPATIBILITY_DATE = BUILD_DATE;
1018
-
1019
1016
  export const _Worker = Resource(
1020
1017
  "cloudflare::Worker",
1021
1018
  {
@@ -1047,8 +1044,6 @@ export const _Worker = Resource(
1047
1044
  ? props.namespace
1048
1045
  : props.namespace?.namespaceName;
1049
1046
 
1050
- const dev = normalizeDev(this, props.dev);
1051
-
1052
1047
  const [bundle, error] = wrap(() =>
1053
1048
  normalizeWorkerBundle({
1054
1049
  entrypoint: props.entrypoint,
@@ -1067,34 +1062,46 @@ export const _Worker = Resource(
1067
1062
  }),
1068
1063
  );
1069
1064
 
1070
- if (dev.local) {
1065
+ // run locally if
1066
+ const local = this.scope.local && !props.dev?.remote;
1067
+ const watch = this.scope.watch;
1068
+
1069
+ if (local) {
1071
1070
  let url: string | undefined;
1072
1071
  if (error) {
1073
1072
  throw error;
1074
1073
  }
1075
1074
 
1076
- switch (dev.type) {
1077
- case "command":
1078
- createDevCommand({
1079
- id,
1080
- command: dev.command,
1081
- cwd: dev.cwd ?? props.cwd ?? process.cwd(),
1082
- env: props.env ?? {},
1083
- });
1084
- url = dev.url;
1085
- break;
1086
- case "miniflare": {
1087
- url = await createMiniflare({
1088
- id,
1089
- workerName,
1090
- compatibilityDate,
1091
- compatibilityFlags,
1092
- bindings: props.bindings,
1093
- bundle,
1094
- port: dev.port,
1095
- });
1096
- break;
1097
- }
1075
+ if (props.dev?.command) {
1076
+ const { url: commandUrl } = await createDevCommand({
1077
+ id,
1078
+ command: props.dev.command,
1079
+ cwd: props.dev.cwd || props.cwd || process.cwd(),
1080
+ env: {
1081
+ ...props.env,
1082
+ ...Object.fromEntries(
1083
+ Object.entries(props.bindings ?? {}).flatMap(([key, value]) =>
1084
+ typeof value === "string"
1085
+ ? [[key, value]]
1086
+ : isSecret(value)
1087
+ ? [[key, value.unencrypted]]
1088
+ : [],
1089
+ ),
1090
+ ),
1091
+ },
1092
+ });
1093
+ url = commandUrl;
1094
+ } else {
1095
+ url = await createMiniflare({
1096
+ id,
1097
+ workerName,
1098
+ compatibilityDate,
1099
+ compatibilityFlags,
1100
+ bindings: props.bindings,
1101
+ bundle,
1102
+ port: props.dev?.port,
1103
+ assets: props.assets,
1104
+ });
1098
1105
  }
1099
1106
 
1100
1107
  return this({
@@ -1218,7 +1225,7 @@ export const _Worker = Resource(
1218
1225
  }
1219
1226
 
1220
1227
  let putWorkerResult: PutWorkerResult;
1221
- if (dev.type === "remote") {
1228
+ if (watch) {
1222
1229
  // todo(john): clean this up and add log tail
1223
1230
  const controller = new AbortController();
1224
1231
  cleanups.push(() => controller.abort());
@@ -1436,57 +1443,6 @@ process.on("SIGINT", async () => {
1436
1443
  process.exit(0);
1437
1444
  });
1438
1445
 
1439
- type Dev =
1440
- | {
1441
- type: "none";
1442
- local: false;
1443
- }
1444
- | {
1445
- type: "miniflare";
1446
- port?: number;
1447
- local: true;
1448
- }
1449
- | {
1450
- type: "command";
1451
- command: string;
1452
- url: string;
1453
- cwd?: string;
1454
- local: true;
1455
- }
1456
- | {
1457
- type: "remote";
1458
- local: false;
1459
- };
1460
-
1461
- const normalizeDev = (ctx: Context<any>, dev: WorkerProps["dev"]): Dev => {
1462
- if (!ctx.scope.dev || ctx.phase === "delete" || dev === false) {
1463
- return {
1464
- type: "none",
1465
- local: false,
1466
- };
1467
- }
1468
- const devObj = dev === true ? {} : (dev ?? {});
1469
- if ("command" in devObj) {
1470
- // Commands are always local
1471
- return {
1472
- type: "command",
1473
- ...devObj,
1474
- local: true,
1475
- };
1476
- }
1477
- if (devObj.remote === false || ctx.scope.dev === "prefer-local") {
1478
- return {
1479
- type: "miniflare",
1480
- port: devObj.port,
1481
- local: true,
1482
- };
1483
- }
1484
- return {
1485
- type: "remote",
1486
- local: false,
1487
- };
1488
- };
1489
-
1490
1446
  const assertUnique = <T, Key extends keyof T>(
1491
1447
  inputs: T[],
1492
1448
  key: Key,
@@ -1714,6 +1670,7 @@ async function createMiniflare(props: {
1714
1670
  bindings: Bindings | undefined;
1715
1671
  port: number | undefined;
1716
1672
  bundle: WorkerBundleProvider;
1673
+ assets: AssetsConfig | undefined;
1717
1674
  }) {
1718
1675
  const sharedOptions: Omit<MiniflareWorkerOptions, "bundle"> = {
1719
1676
  name: props.workerName,
@@ -1721,6 +1678,7 @@ async function createMiniflare(props: {
1721
1678
  compatibilityFlags: props.compatibilityFlags,
1722
1679
  bindings: props.bindings,
1723
1680
  port: props.port,
1681
+ assets: props.assets,
1724
1682
  };
1725
1683
 
1726
1684
  const startPromise = new DeferredPromise<string>();
@@ -1858,15 +1816,15 @@ async function createMiniflare(props: {
1858
1816
  return await startPromise.value;
1859
1817
  }
1860
1818
 
1861
- function createDevCommand(props: {
1819
+ async function createDevCommand(props: {
1862
1820
  id: string;
1863
1821
  command: string;
1864
1822
  cwd: string;
1865
1823
  env: Record<string, string>;
1866
- }) {
1824
+ }): Promise<{ url: string }> {
1867
1825
  const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
1868
- if (existsSync(persistFile)) {
1869
- const pid = Number.parseInt(readFileSync(persistFile, "utf8"));
1826
+ if (await exists(persistFile)) {
1827
+ const pid = Number.parseInt(await fs.readFile(persistFile, "utf8"));
1870
1828
  try {
1871
1829
  // Actually kill the process if it's alive
1872
1830
  process.kill(pid, "SIGTERM");
@@ -1874,7 +1832,7 @@ function createDevCommand(props: {
1874
1832
  // ignore
1875
1833
  }
1876
1834
  try {
1877
- unlinkSync(persistFile);
1835
+ await fs.unlink(persistFile);
1878
1836
  } catch {
1879
1837
  // ignore
1880
1838
  }
@@ -1890,21 +1848,61 @@ function createDevCommand(props: {
1890
1848
  ".alchemy",
1891
1849
  "miniflare",
1892
1850
  ),
1851
+ // Force colors in the child process since we're piping output
1852
+ // FORCE_COLOR: "1",
1893
1853
  },
1894
- stdio: ["inherit", "inherit", "inherit"],
1854
+ stdio: ["inherit", "pipe", "pipe"],
1895
1855
  });
1896
- cleanups.push(() => {
1897
- try {
1898
- unlinkSync(persistFile);
1899
- } catch {
1900
- // ignore
1901
- }
1902
- proc.kill();
1856
+ const { url } = await new Promise<{ url: string }>((resolve, reject) => {
1857
+ let urlFound = false;
1858
+ let stdout = "";
1859
+ let stderr = "";
1860
+ const urlRegex =
1861
+ /http:\/\/(?:(?:localhost|0\.0\.0\.0|127\.0\.0\.1)|(?:\d{1,3}\.){3}\d{1,3}):\d+(?:\/)?/;
1862
+
1863
+ const parseOutput = (data: string) => {
1864
+ if (!urlFound) {
1865
+ const match = data.match(urlRegex);
1866
+ if (match) {
1867
+ urlFound = true;
1868
+ resolve({ url: match[0] });
1869
+ }
1870
+ }
1871
+ };
1872
+
1873
+ // Handle stdout - parse for URL and write through with colors preserved
1874
+ proc.stdout?.on("data", (data) => {
1875
+ parseOutput(
1876
+ (stdout += data.toString().replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")),
1877
+ );
1878
+ process.stdout.write(data);
1879
+ });
1880
+
1881
+ proc.stderr?.on("data", (data) => {
1882
+ parseOutput(
1883
+ (stderr += data.toString().replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")),
1884
+ );
1885
+ process.stderr.write(data);
1886
+ });
1887
+
1888
+ proc.on("error", (error) => {
1889
+ reject(error);
1890
+ });
1891
+
1892
+ cleanups.push(async () => {
1893
+ try {
1894
+ await fs.unlink(persistFile);
1895
+ } catch {
1896
+ // ignore
1897
+ }
1898
+ proc.kill();
1899
+ });
1903
1900
  });
1904
1901
  if (proc.pid) {
1905
- mkdirSync(path.dirname(persistFile), { recursive: true });
1906
- writeFileSync(persistFile, proc.pid.toString());
1902
+ await fs.mkdir(path.dirname(persistFile), { recursive: true });
1903
+ await fs.writeFile(persistFile, proc.pid.toString());
1907
1904
  }
1905
+ return { url };
1908
1906
  }
1909
1907
 
1910
1908
  type PutWorkerOptions = Omit<WorkerProps, "entrypoint"> & {
package/src/scope.ts CHANGED
@@ -38,7 +38,18 @@ export interface ScopeOptions {
38
38
  stateStore?: StateStoreType;
39
39
  quiet?: boolean;
40
40
  phase?: Phase;
41
- dev?: "prefer-local" | "prefer-remote";
41
+ /**
42
+ * Determines if resources should be simulated locally (where possible)
43
+ *
44
+ * @default - `true` if ran with `alchemy dev` or `bun ./alchemy.run.ts --dev`
45
+ */
46
+ local?: boolean;
47
+ /**
48
+ * Determines if local changes to resources should be reactively pushed to the local or remote environment.
49
+ *
50
+ * @default - `true` if ran with `alchemy dev`, `alchemy watch`, `bun --watch ./alchemy.run.ts`
51
+ */
52
+ watch?: boolean;
42
53
  telemetryClient?: ITelemetryClient;
43
54
  logger?: LoggerApi;
44
55
  }
@@ -107,7 +118,8 @@ export class Scope {
107
118
  public readonly stateStore: StateStoreType;
108
119
  public readonly quiet: boolean;
109
120
  public readonly phase: Phase;
110
- public readonly dev?: "prefer-local" | "prefer-remote";
121
+ public readonly local: boolean;
122
+ public readonly watch: boolean;
111
123
  public readonly logger: LoggerApi;
112
124
  public readonly telemetryClient: ITelemetryClient;
113
125
  public readonly dataMutex: AsyncMutex;
@@ -162,9 +174,10 @@ export class Scope {
162
174
  options.logger,
163
175
  );
164
176
 
165
- this.dev = options.dev ?? this.parent?.dev;
177
+ this.local = options.local ?? this.parent?.local ?? false;
178
+ this.watch = options.watch ?? this.parent?.watch ?? false;
166
179
 
167
- if (this.dev) {
180
+ if (this.local) {
168
181
  this.logger.warnOnce(
169
182
  "Development mode is in beta. Please report any issues to https://github.com/sam-goodwin/alchemy/issues.",
170
183
  );
@@ -415,6 +428,10 @@ export class Scope {
415
428
  await this.rootTelemetryClient?.finalize()?.catch((error) => {
416
429
  this.logger.warn("Telemetry finalization failed:", error);
417
430
  });
431
+
432
+ if (!this.parent && process.env.ALCHEMY_TEST_KILL_ON_FINALIZE) {
433
+ process.exit(0);
434
+ }
418
435
  }
419
436
 
420
437
  public async destroyPendingDeletions() {
@@ -1,8 +1,8 @@
1
1
  import { alchemy } from "../alchemy.ts";
2
- import { BUILD_DATE } from "../build-date.ts";
3
2
  import type { CloudflareApiOptions } from "../cloudflare/api.ts";
4
3
  import { createCloudflareApi } from "../cloudflare/api.ts";
5
4
  import { getInternalWorkerBundle } from "../cloudflare/bundle/internal-worker-bundle.ts";
5
+ import { DEFAULT_COMPATIBILITY_DATE } from "../cloudflare/compatibility-date.gen.ts";
6
6
  import { DurableObjectNamespace } from "../cloudflare/durable-object-namespace.ts";
7
7
  import { getWorkerSettings } from "../cloudflare/worker-metadata.ts";
8
8
  import {
@@ -109,7 +109,7 @@ const provision = memoize(async (options: CloudflareStateStoreOptions) => {
109
109
  );
110
110
  await putWorker(api, {
111
111
  workerName: scriptName,
112
- compatibilityDate: BUILD_DATE,
112
+ compatibilityDate: DEFAULT_COMPATIBILITY_DATE,
113
113
  format: "esm",
114
114
  scriptBundle: {
115
115
  entrypoint: bundle.file.name,
@@ -0,0 +1,94 @@
1
+ import http from "node:http";
2
+ import type { AddressInfo } from "node:net";
3
+ import { Readable } from "node:stream";
4
+
5
+ export class HTTPServer {
6
+ httpServer: http.Server;
7
+
8
+ constructor(options: {
9
+ fetch: (request: Request) => Promise<Response>;
10
+ }) {
11
+ this.httpServer = http.createServer();
12
+ this.httpServer.on("request", async (req, res) => {
13
+ const response = await options.fetch(toWebRequest(req));
14
+ await writeNodeResponse(res, response);
15
+ });
16
+ }
17
+
18
+ listen(port?: number) {
19
+ return new Promise<this>((resolve, reject) => {
20
+ this.httpServer.on("listening", () => {
21
+ resolve(this);
22
+ });
23
+ this.httpServer.on("error", (error) => {
24
+ reject(error);
25
+ });
26
+ this.httpServer.listen(port);
27
+ });
28
+ }
29
+
30
+ get url() {
31
+ const address = this.httpServer.address() as AddressInfo | null;
32
+ if (!address) {
33
+ throw new Error("Server is not listening");
34
+ }
35
+ const hostname = address.address === "::" ? "localhost" : address.address;
36
+ return `http://${hostname}:${address.port}`;
37
+ }
38
+
39
+ close() {
40
+ return new Promise((resolve, reject) => {
41
+ this.httpServer.close((err) => {
42
+ if (err) {
43
+ reject(err);
44
+ } else {
45
+ resolve(undefined);
46
+ }
47
+ });
48
+ });
49
+ }
50
+ }
51
+
52
+ export function toWebRequest(
53
+ req: http.IncomingMessage,
54
+ host?: string,
55
+ ): Request {
56
+ const method = req.method ?? "GET";
57
+ const url = new URL(req.url ?? "/", `http://${host ?? req.headers.host}`);
58
+ const body =
59
+ ["GET", "HEAD", "OPTIONS"].includes(method) || !req.readable
60
+ ? undefined
61
+ : Readable.toWeb(req);
62
+ return new Request(url.toString(), {
63
+ method,
64
+ headers: req.headers as Record<string, string>,
65
+ body: body as unknown as BodyInit,
66
+ // @ts-expect-error - caused by @cloudflare/workers-types
67
+ duplex: body ? "half" : undefined,
68
+ redirect: "manual",
69
+ });
70
+ }
71
+
72
+ export async function writeNodeResponse(
73
+ res: http.ServerResponse,
74
+ response: Response,
75
+ ) {
76
+ res.statusCode = response.status;
77
+ response.headers.forEach((value, key) => {
78
+ res.setHeader(key, value);
79
+ });
80
+ if (response.body) {
81
+ await response.body.pipeTo(
82
+ new WritableStream({
83
+ write(chunk) {
84
+ res.write(chunk);
85
+ },
86
+ close() {
87
+ res.end();
88
+ },
89
+ }),
90
+ );
91
+ } else {
92
+ res.end();
93
+ }
94
+ }
@@ -20,6 +20,7 @@ export async function safeFetch(
20
20
  err?.code === "UND_ERR_SOCKET" ||
21
21
  err?.code === "ECONNRESET" ||
22
22
  err?.code === "UND_ERR_CONNECT_TIMEOUT" ||
23
+ err?.code === "EPIPE" ||
23
24
  err?.name === "FetchError";
24
25
 
25
26
  if (!shouldRetry || attempt === retries) {
@@ -3,7 +3,7 @@
3
3
  "name": "@alchemy.run/react-router-template",
4
4
  "private": true,
5
5
  "scripts": {
6
- "build": "vite build",
6
+ "build": "react-router build",
7
7
  "deploy": "alchemy deploy",
8
8
  "destroy": "alchemy destroy",
9
9
  "dev": "alchemy dev",
@@ -11,7 +11,11 @@
11
11
  "composite": true,
12
12
  "strict": true,
13
13
  "lib": ["DOM", "DOM.Iterable", "ES2022"],
14
- "types": ["vite/client"],
14
+ "types": [
15
+ "vite/client",
16
+ "@cloudflare/workers-types",
17
+ "./types/env.d.ts"
18
+ ],
15
19
  "target": "ES2022",
16
20
  "module": "ES2022",
17
21
  "moduleResolution": "bundler",
@@ -10,8 +10,5 @@
10
10
  "skipLibCheck": true,
11
11
  "strict": true,
12
12
  "noEmit": true,
13
- "types": [
14
- "./worker-configuration.d.ts"
15
- ]
16
13
  }
17
14
  }
@@ -8,10 +8,6 @@
8
8
  "compilerOptions": {
9
9
  "composite": true,
10
10
  "strict": true,
11
- "types": [
12
- "@cloudflare/workers-types",
13
- "./types/env.d.ts"
14
- ],
15
11
  "lib": ["ES2022"],
16
12
  "target": "ES2022",
17
13
  "module": "ES2022",
@@ -1,6 +0,0 @@
1
- /**
2
- * The build date used as the default worker compatibility date.
3
- * This is set to the date when the package was built.
4
- */
5
- export declare const BUILD_DATE = "2025-07-16";
6
- //# sourceMappingURL=build-date.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-date.d.ts","sourceRoot":"","sources":["../src/build-date.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,UAAU,eAAe,CAAC"}
package/lib/build-date.js DELETED
@@ -1,8 +0,0 @@
1
- // This file is auto-generated during build
2
- // Do not edit manually
3
- /**
4
- * The build date used as the default worker compatibility date.
5
- * This is set to the date when the package was built.
6
- */
7
- export const BUILD_DATE = "2025-07-16";
8
- //# sourceMappingURL=build-date.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-date.js","sourceRoot":"","sources":["../src/build-date.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,uBAAuB;AAEvB;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC"}
@@ -1,14 +0,0 @@
1
- import http from "node:http";
2
- export declare class HTTPServer {
3
- server: http.Server;
4
- ready: Promise<void>;
5
- constructor(options: {
6
- port?: number;
7
- fetch: (request: Request) => Promise<Response>;
8
- });
9
- get port(): number;
10
- get hostname(): string;
11
- get url(): string;
12
- stop(): Promise<unknown>;
13
- }
14
- //# sourceMappingURL=http-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/util/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAK7B,qBAAa,UAAU;IACrB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAET,OAAO,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;KAChD;IAWD,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAMX;IAED,IAAI,GAAG,WAEN;IAED,IAAI;CAWL"}