alchemy 0.56.0 → 0.57.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 (247) hide show
  1. package/bin/alchemy.js +14201 -14162
  2. package/bin/commands/create.ts +7 -7
  3. package/bin/commands/deploy.ts +2 -2
  4. package/bin/commands/destroy.ts +2 -2
  5. package/bin/commands/dev.ts +2 -2
  6. package/bin/commands/init.ts +6 -6
  7. package/bin/commands/login.ts +3 -3
  8. package/bin/commands/run.ts +2 -2
  9. package/bin/services/execute-alchemy.ts +3 -3
  10. package/bin/trpc.ts +49 -0
  11. package/lib/apply.d.ts.map +1 -1
  12. package/lib/apply.js +3 -4
  13. package/lib/apply.js.map +1 -1
  14. package/lib/cloudflare/{astro.d.ts → astro/astro.d.ts} +6 -5
  15. package/lib/cloudflare/astro/astro.d.ts.map +1 -0
  16. package/lib/cloudflare/astro/astro.js +91 -0
  17. package/lib/cloudflare/astro/astro.js.map +1 -0
  18. package/lib/cloudflare/astro/plugin.d.ts +4 -0
  19. package/lib/cloudflare/astro/plugin.d.ts.map +1 -0
  20. package/lib/cloudflare/astro/plugin.js +10 -0
  21. package/lib/cloudflare/astro/plugin.js.map +1 -0
  22. package/lib/cloudflare/cloudflare-env-proxy.d.ts +4 -0
  23. package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -0
  24. package/lib/cloudflare/cloudflare-env-proxy.js +34 -0
  25. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -0
  26. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  27. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  28. package/lib/cloudflare/d1-local-migrations.js +1 -1
  29. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  30. package/lib/cloudflare/index.d.ts +8 -8
  31. package/lib/cloudflare/index.d.ts.map +1 -1
  32. package/lib/cloudflare/index.js +8 -8
  33. package/lib/cloudflare/index.js.map +1 -1
  34. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  35. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -1
  36. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  37. package/lib/cloudflare/miniflare/paths.d.ts +5 -0
  38. package/lib/cloudflare/miniflare/paths.d.ts.map +1 -0
  39. package/lib/cloudflare/miniflare/paths.js +35 -0
  40. package/lib/cloudflare/miniflare/paths.js.map +1 -0
  41. package/lib/cloudflare/{nuxt.d.ts → nuxt/nuxt.d.ts} +4 -4
  42. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -0
  43. package/lib/cloudflare/{nuxt.js → nuxt/nuxt.js} +8 -13
  44. package/lib/cloudflare/nuxt/nuxt.js.map +1 -0
  45. package/lib/cloudflare/nuxt/plugin.d.ts +4 -0
  46. package/lib/cloudflare/nuxt/plugin.d.ts.map +1 -0
  47. package/lib/cloudflare/nuxt/plugin.js +14 -0
  48. package/lib/cloudflare/nuxt/plugin.js.map +1 -0
  49. package/lib/cloudflare/oauth.js +2 -2
  50. package/lib/cloudflare/oauth.js.map +1 -1
  51. package/lib/cloudflare/orange.d.ts +3 -3
  52. package/lib/cloudflare/orange.d.ts.map +1 -1
  53. package/lib/cloudflare/orange.js +11 -21
  54. package/lib/cloudflare/orange.js.map +1 -1
  55. package/lib/cloudflare/react-router/plugin.d.ts +4 -0
  56. package/lib/cloudflare/react-router/plugin.d.ts.map +1 -0
  57. package/lib/cloudflare/react-router/plugin.js +11 -0
  58. package/lib/cloudflare/react-router/plugin.js.map +1 -0
  59. package/lib/cloudflare/react-router/react-router.d.ts +17 -0
  60. package/lib/cloudflare/react-router/react-router.d.ts.map +1 -0
  61. package/lib/cloudflare/react-router/react-router.js +21 -0
  62. package/lib/cloudflare/react-router/react-router.js.map +1 -0
  63. package/lib/cloudflare/redwood/plugin.d.ts +4 -0
  64. package/lib/cloudflare/redwood/plugin.d.ts.map +1 -0
  65. package/lib/cloudflare/redwood/plugin.js +19 -0
  66. package/lib/cloudflare/redwood/plugin.js.map +1 -0
  67. package/lib/cloudflare/{redwood.d.ts → redwood/redwood.d.ts} +5 -5
  68. package/lib/cloudflare/redwood/redwood.d.ts.map +1 -0
  69. package/lib/cloudflare/{redwood.js → redwood/redwood.js} +12 -13
  70. package/lib/cloudflare/redwood/redwood.js.map +1 -0
  71. package/lib/cloudflare/sveltekit/plugin.d.ts +4 -0
  72. package/lib/cloudflare/sveltekit/plugin.d.ts.map +1 -0
  73. package/lib/cloudflare/sveltekit/plugin.js +11 -0
  74. package/lib/cloudflare/sveltekit/plugin.js.map +1 -0
  75. package/lib/cloudflare/{sveltekit.d.ts → sveltekit/sveltekit.d.ts} +5 -5
  76. package/lib/cloudflare/sveltekit/sveltekit.d.ts.map +1 -0
  77. package/lib/cloudflare/{sveltekit.js → sveltekit/sveltekit.js} +4 -4
  78. package/lib/cloudflare/sveltekit/sveltekit.js.map +1 -0
  79. package/lib/cloudflare/{local-dev-cloudflare-shim.d.ts → tanstack-start/plugin.d.ts} +3 -2
  80. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -0
  81. package/lib/cloudflare/{local-dev-cloudflare-shim.js → tanstack-start/plugin.js} +6 -9
  82. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -0
  83. package/lib/cloudflare/{tanstack-start.d.ts → tanstack-start/tanstack-start.d.ts} +5 -5
  84. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -0
  85. package/lib/cloudflare/tanstack-start/tanstack-start.js +11 -0
  86. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -0
  87. package/lib/cloudflare/vite/plugin.d.ts +4 -0
  88. package/lib/cloudflare/vite/plugin.d.ts.map +1 -0
  89. package/lib/cloudflare/vite/plugin.js +23 -0
  90. package/lib/cloudflare/vite/plugin.js.map +1 -0
  91. package/lib/cloudflare/{vite.d.ts → vite/vite.d.ts} +4 -4
  92. package/lib/cloudflare/vite/vite.d.ts.map +1 -0
  93. package/lib/cloudflare/vite/vite.js +19 -0
  94. package/lib/cloudflare/vite/vite.js.map +1 -0
  95. package/lib/cloudflare/website.d.ts +92 -70
  96. package/lib/cloudflare/website.d.ts.map +1 -1
  97. package/lib/cloudflare/website.js +225 -94
  98. package/lib/cloudflare/website.js.map +1 -1
  99. package/lib/cloudflare/worker-bundle.d.ts +1 -1
  100. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  101. package/lib/cloudflare/worker-bundle.js +23 -13
  102. package/lib/cloudflare/worker-bundle.js.map +1 -1
  103. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  104. package/lib/cloudflare/worker-stub.js +3 -6
  105. package/lib/cloudflare/worker-stub.js.map +1 -1
  106. package/lib/cloudflare/worker.d.ts +2 -7
  107. package/lib/cloudflare/worker.d.ts.map +1 -1
  108. package/lib/cloudflare/worker.js +5 -124
  109. package/lib/cloudflare/worker.js.map +1 -1
  110. package/lib/cloudflare/wrangler.json.d.ts +6 -0
  111. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  112. package/lib/cloudflare/wrangler.json.js +23 -6
  113. package/lib/cloudflare/wrangler.json.js.map +1 -1
  114. package/lib/env.d.ts +2 -2
  115. package/lib/env.d.ts.map +1 -1
  116. package/lib/env.js +5 -6
  117. package/lib/env.js.map +1 -1
  118. package/lib/random/index.d.ts +2 -0
  119. package/lib/random/index.d.ts.map +1 -0
  120. package/lib/random/index.js +2 -0
  121. package/lib/random/index.js.map +1 -0
  122. package/lib/random/random-string.d.ts +87 -0
  123. package/lib/random/random-string.d.ts.map +1 -0
  124. package/lib/random/random-string.js +72 -0
  125. package/lib/random/random-string.js.map +1 -0
  126. package/lib/runtime/bind.d.ts +1 -1
  127. package/lib/runtime/bind.d.ts.map +1 -1
  128. package/lib/runtime/bind.js.map +1 -1
  129. package/lib/scope.d.ts +0 -1
  130. package/lib/scope.d.ts.map +1 -1
  131. package/lib/scope.js +1 -2
  132. package/lib/scope.js.map +1 -1
  133. package/lib/secret.d.ts +2 -2
  134. package/lib/secret.d.ts.map +1 -1
  135. package/lib/secret.js +2 -2
  136. package/lib/secret.js.map +1 -1
  137. package/lib/state/cloudflare-state-store.js +1 -1
  138. package/lib/state/cloudflare-state-store.js.map +1 -1
  139. package/lib/state/dofs-state-store/index.js +1 -1
  140. package/lib/state/dofs-state-store/index.js.map +1 -1
  141. package/lib/util/detect-package-manager.d.ts +1 -0
  142. package/lib/util/detect-package-manager.d.ts.map +1 -1
  143. package/lib/util/detect-package-manager.js +10 -0
  144. package/lib/util/detect-package-manager.js.map +1 -1
  145. package/lib/util/telemetry/client.d.ts +1 -1
  146. package/lib/util/telemetry/client.d.ts.map +1 -1
  147. package/lib/util/telemetry/client.js +22 -1
  148. package/lib/util/telemetry/client.js.map +1 -1
  149. package/lib/util/telemetry/constants.d.ts.map +1 -1
  150. package/lib/util/telemetry/constants.js +2 -3
  151. package/lib/util/telemetry/constants.js.map +1 -1
  152. package/lib/util/telemetry/context.d.ts +1 -1
  153. package/lib/util/telemetry/context.d.ts.map +1 -1
  154. package/lib/util/telemetry/types.d.ts +7 -3
  155. package/lib/util/telemetry/types.d.ts.map +1 -1
  156. package/lib/vercel/api.d.ts.map +1 -1
  157. package/lib/vercel/api.js +1 -1
  158. package/lib/vercel/api.js.map +1 -1
  159. package/package.json +41 -3
  160. package/src/apply.ts +3 -7
  161. package/src/cloudflare/{astro.ts → astro/astro.ts} +46 -29
  162. package/src/cloudflare/astro/plugin.ts +11 -0
  163. package/src/cloudflare/cloudflare-env-proxy.ts +50 -0
  164. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  165. package/src/cloudflare/d1-local-migrations.ts +1 -1
  166. package/src/cloudflare/index.ts +8 -8
  167. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -1
  168. package/src/cloudflare/miniflare/paths.ts +52 -0
  169. package/src/cloudflare/{nuxt.ts → nuxt/nuxt.ts} +11 -16
  170. package/src/cloudflare/nuxt/plugin.ts +18 -0
  171. package/src/cloudflare/oauth.ts +2 -2
  172. package/src/cloudflare/orange.ts +13 -23
  173. package/src/cloudflare/react-router/plugin.ts +13 -0
  174. package/src/cloudflare/react-router/react-router.ts +41 -0
  175. package/src/cloudflare/redwood/plugin.ts +20 -0
  176. package/src/cloudflare/{redwood.ts → redwood/redwood.ts} +16 -19
  177. package/src/cloudflare/sveltekit/plugin.ts +12 -0
  178. package/src/cloudflare/{sveltekit.ts → sveltekit/sveltekit.ts} +9 -8
  179. package/src/cloudflare/{local-dev-cloudflare-shim.ts → tanstack-start/plugin.ts} +8 -9
  180. package/src/cloudflare/tanstack-start/tanstack-start.ts +24 -0
  181. package/src/cloudflare/vite/plugin.ts +30 -0
  182. package/src/cloudflare/vite/vite.ts +34 -0
  183. package/src/cloudflare/website.ts +379 -204
  184. package/src/cloudflare/worker-bundle.ts +27 -13
  185. package/src/cloudflare/worker-stub.ts +11 -14
  186. package/src/cloudflare/worker.ts +8 -155
  187. package/src/cloudflare/wrangler.json.ts +32 -4
  188. package/src/env.ts +8 -9
  189. package/src/random/index.ts +1 -0
  190. package/src/random/random-string.ts +126 -0
  191. package/src/runtime/bind.ts +2 -2
  192. package/src/scope.ts +1 -3
  193. package/src/secret.ts +4 -8
  194. package/src/state/cloudflare-state-store.ts +2 -2
  195. package/src/state/dofs-state-store/index.ts +1 -1
  196. package/src/util/detect-package-manager.ts +11 -0
  197. package/src/util/telemetry/client.ts +25 -2
  198. package/src/util/telemetry/constants.ts +2 -4
  199. package/src/util/telemetry/context.ts +1 -1
  200. package/src/util/telemetry/types.ts +8 -2
  201. package/src/vercel/api.ts +2 -4
  202. package/templates/astro/alchemy.run.ts +1 -3
  203. package/templates/astro/astro.config.mjs +2 -2
  204. package/templates/nuxt/alchemy.run.ts +1 -3
  205. package/templates/nuxt/nuxt.config.ts +4 -5
  206. package/templates/nuxt/package.json +0 -1
  207. package/templates/react-router/alchemy.run.ts +1 -4
  208. package/templates/react-router/vite.config.ts +2 -2
  209. package/templates/rwsdk/alchemy.run.ts +0 -1
  210. package/templates/rwsdk/package.json +0 -2
  211. package/templates/rwsdk/vite.config.mts +2 -2
  212. package/templates/sveltekit/alchemy.run.ts +1 -3
  213. package/templates/sveltekit/svelte.config.js +2 -2
  214. package/templates/tanstack-start/vite.config.ts +2 -2
  215. package/templates/vite/alchemy.run.ts +1 -1
  216. package/templates/vite/vite.config.ts +4 -5
  217. package/workers/cloudflare-state-store.js +12 -14
  218. package/lib/cloudflare/astro.d.ts.map +0 -1
  219. package/lib/cloudflare/astro.js +0 -69
  220. package/lib/cloudflare/astro.js.map +0 -1
  221. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +0 -1
  222. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +0 -1
  223. package/lib/cloudflare/nuxt.d.ts.map +0 -1
  224. package/lib/cloudflare/nuxt.js.map +0 -1
  225. package/lib/cloudflare/react-router.d.ts +0 -17
  226. package/lib/cloudflare/react-router.d.ts.map +0 -1
  227. package/lib/cloudflare/react-router.js +0 -35
  228. package/lib/cloudflare/react-router.js.map +0 -1
  229. package/lib/cloudflare/redwood.d.ts.map +0 -1
  230. package/lib/cloudflare/redwood.js.map +0 -1
  231. package/lib/cloudflare/sveltekit.d.ts.map +0 -1
  232. package/lib/cloudflare/sveltekit.js.map +0 -1
  233. package/lib/cloudflare/tanstack-start.d.ts.map +0 -1
  234. package/lib/cloudflare/tanstack-start.js +0 -21
  235. package/lib/cloudflare/tanstack-start.js.map +0 -1
  236. package/lib/cloudflare/vite.d.ts.map +0 -1
  237. package/lib/cloudflare/vite.js +0 -20
  238. package/lib/cloudflare/vite.js.map +0 -1
  239. package/src/cloudflare/react-router.ts +0 -56
  240. package/src/cloudflare/tanstack-start.ts +0 -35
  241. package/src/cloudflare/vite.ts +0 -35
  242. package/templates/react-router/wrangler.jsonc +0 -6
  243. package/templates/rwsdk/wrangler.jsonc +0 -31
  244. package/templates/sveltekit/wrangler.jsonc +0 -7
  245. package/templates/tanstack-start/wrangler.jsonc +0 -7
  246. package/templates/typescript/index.ts +0 -1
  247. package/templates/vite/wrangler.jsonc +0 -6
@@ -74,7 +74,7 @@ export function normalizeWorkerBundle(props: {
74
74
  nodeCompat,
75
75
  cwd: props.cwd,
76
76
  outdir: props.outdir,
77
- sourcemap: props.sourceMap !== false ? "linked" : undefined,
77
+ sourcemap: props.sourceMap !== false ? true : undefined,
78
78
  ...props.bundle,
79
79
  }),
80
80
  );
@@ -128,7 +128,8 @@ export namespace WorkerBundle {
128
128
  (await fs.readFile(path.join(bundle.root!, module.path)));
129
129
  form.append(
130
130
  module.path,
131
- new Blob([content], {
131
+ // TODO(sam): tsc -b geting a weird error about SharedArrayBuffer
132
+ new Blob([content as any as BlobPart], {
132
133
  type: types[module.type],
133
134
  }),
134
135
  module.path,
@@ -266,7 +267,7 @@ export namespace WorkerBundleSource {
266
267
  const wasmPlugin = createWasmPlugin();
267
268
  const options = this.buildOptions([wasmPlugin.plugin]);
268
269
  const result = await esbuild.build(options);
269
- const { entrypoint, root, modules } = this.resolveBuildOutput(
270
+ const { entrypoint, root, modules } = await this.formatBuildOutput(
270
271
  result.metafile,
271
272
  );
272
273
  return {
@@ -300,7 +301,7 @@ export namespace WorkerBundleSource {
300
301
 
301
302
  for await (const result of hotReload.iterator) {
302
303
  count++;
303
- const { entrypoint, root, modules } = this.resolveBuildOutput(
304
+ const { entrypoint, root, modules } = await this.formatBuildOutput(
304
305
  result.metafile!,
305
306
  );
306
307
  yield {
@@ -330,20 +331,27 @@ export namespace WorkerBundleSource {
330
331
  return {
331
332
  entryPoints: [entrypoint],
332
333
  absWorkingDir: cwd,
334
+ target: "es2022",
335
+ loader: {
336
+ ".js": "jsx",
337
+ ".mjs": "jsx",
338
+ ".cjs": "jsx",
339
+ },
333
340
  format,
334
- target: "esnext",
341
+ sourceRoot: this.props.outdir,
342
+ jsxFactory: "React.createElement",
343
+ jsxFragment: "React.Fragment",
344
+ keepNames: true,
335
345
  ...props,
336
346
  metafile: true,
337
347
  write: true,
338
348
  bundle: true,
339
- conditions: props.conditions ?? ["workerd", "worker", "browser"],
340
- mainFields: props.mainFields,
341
- loader: {
342
- ".sql": "text",
343
- ".json": "json",
344
- ".wasm": "binary",
345
- ...props.loader,
349
+ define: {
350
+ "navigator.userAgent": '"Cloudflare-Workers"',
351
+ "process.env.NODE_ENV": '"undefined"',
352
+ ...props.define,
346
353
  },
354
+ conditions: props.conditions ?? ["workerd", "worker", "browser"],
347
355
  plugins: [
348
356
  esbuildPluginAlias(props.alias ?? {}, this.props.cwd),
349
357
  nodeCompat === "v2"
@@ -359,8 +367,14 @@ export namespace WorkerBundleSource {
359
367
  } satisfies esbuild.BuildOptions;
360
368
  }
361
369
 
362
- private resolveBuildOutput(metafile: esbuild.Metafile): WorkerBundle {
370
+ private async formatBuildOutput(
371
+ metafile: esbuild.Metafile,
372
+ ): Promise<WorkerBundle> {
363
373
  const outdir = path.resolve(this.props.cwd, this.props.outdir);
374
+ await fs.writeFile(
375
+ path.join(outdir, "metafile.json"),
376
+ JSON.stringify(metafile, null, 2),
377
+ );
364
378
  const paths: string[] = [];
365
379
  let entrypoint: string | undefined;
366
380
  for (const [key, value] of Object.entries(metafile.outputs)) {
@@ -2,6 +2,7 @@ import type { Context } from "../context.ts";
2
2
  import { Resource, ResourceKind } from "../resource.ts";
3
3
  import type { type } from "../type.ts";
4
4
  import { handleApiError } from "./api-error.ts";
5
+ import { extractCloudflareResult } from "./api-response.ts";
5
6
  import {
6
7
  createCloudflareApi,
7
8
  type CloudflareApi,
@@ -185,20 +186,16 @@ async function createEmptyWorker(
185
186
  );
186
187
 
187
188
  // Upload worker script
188
- const uploadResponse = await api.put(
189
- `/accounts/${api.accountId}/workers/scripts/${workerName}`,
190
- formData,
191
- {
192
- headers: {
193
- "Content-Type": "multipart/form-data",
189
+ await extractCloudflareResult(
190
+ `create empty worker "${workerName}"`,
191
+ api.put(
192
+ `/accounts/${api.accountId}/workers/scripts/${workerName}`,
193
+ formData,
194
+ {
195
+ headers: {
196
+ "Content-Type": "multipart/form-data",
197
+ },
194
198
  },
195
- },
199
+ ),
196
200
  );
197
-
198
- // Check if the upload was successful
199
- if (!uploadResponse.ok) {
200
- throw new Error(
201
- `Failed to create empty worker: ${uploadResponse.status} ${uploadResponse.statusText}`,
202
- );
203
- }
204
201
  }
@@ -1,14 +1,10 @@
1
- import { spawn } from "node:child_process";
2
- import fs from "node:fs/promises";
3
1
  import { isDeepStrictEqual } from "node:util";
4
2
  import path from "pathe";
5
3
  import type { Context } from "../context.ts";
6
4
  import type { BundleProps } from "../esbuild/bundle.ts";
7
5
  import { Resource, ResourceKind } from "../resource.ts";
8
- import { isSecret } from "../secret.ts";
9
6
  import type { type } from "../type.ts";
10
7
  import { DeferredPromise } from "../util/deferred-promise.ts";
11
- import { exists } from "../util/exists.ts";
12
8
  import { logger } from "../util/logger.ts";
13
9
  import { withExponentialBackoff } from "../util/retry.ts";
14
10
  import { CloudflareApiError, handleApiError } from "./api-error.ts";
@@ -331,15 +327,10 @@ export interface BaseWorkerProps<
331
327
  * @default false
332
328
  */
333
329
  remote?: boolean;
334
- /** @internal */
335
- command?: undefined;
330
+ url?: undefined;
336
331
  }
337
332
  | {
338
- command: string;
339
- cwd?: string;
340
- /** @internal */
341
- port?: undefined;
342
- /** @internal */
333
+ url: string;
343
334
  remote?: undefined;
344
335
  };
345
336
 
@@ -740,8 +731,7 @@ const _Worker = Resource(
740
731
  compatibilityDate,
741
732
  compatibilityFlags,
742
733
  outdir:
743
- props.bundle?.outdir ??
744
- path.join(cwd, ".alchemy", ...this.scope.chain, id),
734
+ props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", workerName),
745
735
  sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
746
736
  });
747
737
 
@@ -755,30 +745,11 @@ const _Worker = Resource(
755
745
  throw bundleSourceResult.error;
756
746
  }
757
747
 
758
- if (props.dev?.command) {
759
- const result = await createDevCommand({
760
- id,
761
- command: props.dev.command,
762
- cwd: props.dev.cwd || props.cwd || process.cwd(),
763
- env: {
764
- ...(process.env ?? {}),
765
- ...props.env,
766
- ...Object.fromEntries(
767
- Object.entries(props.bindings ?? {}).flatMap(([key, value]) =>
768
- typeof value === "string"
769
- ? [[key, value]]
770
- : isSecret(value)
771
- ? [[key, value.unencrypted]]
772
- : [],
773
- ),
774
- ),
775
- },
776
- });
777
- url = result.url;
778
- this.onCleanup(() => result.cleanup());
748
+ if (props.dev && "url" in props.dev) {
749
+ url = props.dev.url;
779
750
  } else {
780
751
  const { MiniflareController } = await import(
781
- "./miniflare/miniflare-controller.ts"
752
+ "./miniflare/miniflare-controller.js"
782
753
  );
783
754
  const controller = MiniflareController.singleton;
784
755
  url = await controller.add({
@@ -790,7 +761,7 @@ const _Worker = Resource(
790
761
  eventSources: props.eventSources,
791
762
  assets: props.assets,
792
763
  bundle: bundleSourceResult.value,
793
- port: props.dev?.port,
764
+ port: props.dev?.port ?? undefined,
794
765
  });
795
766
  this.onCleanup(() => controller.dispose());
796
767
  }
@@ -849,7 +820,7 @@ const _Worker = Resource(
849
820
  const oldName = this.output.name ?? this.output.id;
850
821
  const newName = workerName;
851
822
 
852
- if (oldName !== newName) {
823
+ if (oldName && oldName !== newName) {
853
824
  if (dispatchNamespace) {
854
825
  this.replace(true);
855
826
  } else {
@@ -1319,124 +1290,6 @@ async function provisionSubdomain(
1319
1290
  }
1320
1291
  }
1321
1292
 
1322
- async function createDevCommand(props: {
1323
- id: string;
1324
- command: string;
1325
- cwd: string;
1326
- env: Record<string, string | undefined>;
1327
- }) {
1328
- const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
1329
- if (await exists(persistFile)) {
1330
- const pid = Number.parseInt(await fs.readFile(persistFile, "utf8"));
1331
- try {
1332
- // Actually kill the process if it's alive
1333
- process.kill(pid, "SIGTERM");
1334
- } catch {
1335
- // ignore
1336
- }
1337
- try {
1338
- await fs.unlink(persistFile);
1339
- } catch {
1340
- // ignore
1341
- }
1342
- }
1343
- const command = props.command.split(" ");
1344
- const [cmd, ...args] = command;
1345
-
1346
- const promise = new DeferredPromise<string>();
1347
- const childProcess = spawn(cmd, args, {
1348
- cwd: props.cwd,
1349
- shell: true,
1350
- env: {
1351
- ...process.env,
1352
- ...props.env,
1353
- ALCHEMY_CLOUDFLARE_PERSIST_PATH: path.join(
1354
- process.cwd(),
1355
- ".alchemy",
1356
- "miniflare",
1357
- ),
1358
- // Force colors in the child process since we're piping output
1359
- // FORCE_COLOR: "1",
1360
- },
1361
- stdio: ["inherit", "pipe", "pipe"],
1362
- });
1363
-
1364
- // Clean up the pid file when the process exits
1365
- childProcess.once("exit", async () => {
1366
- try {
1367
- await fs.unlink(persistFile);
1368
- } catch {
1369
- // ignore
1370
- }
1371
- });
1372
-
1373
- let urlFound = false;
1374
- let stdout = "";
1375
- let stderr = "";
1376
- const urlRegex =
1377
- /http:\/\/(?:(?:localhost|0\.0\.0\.0|127\.0\.0\.1)|(?:\d{1,3}\.){3}\d{1,3}):\d+(?:\/)?/;
1378
-
1379
- const parseOutput = (data: string) => {
1380
- if (!urlFound) {
1381
- const match = data.match(urlRegex);
1382
- if (match) {
1383
- urlFound = true;
1384
- promise.resolve(match[0]);
1385
- }
1386
- }
1387
- };
1388
-
1389
- // Handle stdout - parse for URL and write through with colors preserved
1390
- childProcess.stdout?.on("data", (data) => {
1391
- parseOutput(
1392
- (stdout += data.toString().replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")),
1393
- );
1394
- process.stdout.write(data);
1395
- });
1396
-
1397
- childProcess.stderr?.on("data", (data) => {
1398
- parseOutput(
1399
- (stderr += data.toString().replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")),
1400
- );
1401
- process.stderr.write(data);
1402
- });
1403
-
1404
- childProcess.on("error", (error) => {
1405
- promise.reject(error);
1406
- });
1407
- if (childProcess.pid) {
1408
- await fs.mkdir(path.dirname(persistFile), { recursive: true });
1409
- await fs.writeFile(persistFile, childProcess.pid.toString());
1410
- }
1411
- return {
1412
- url: await promise.value,
1413
- cleanup: async () => {
1414
- try {
1415
- await fs.unlink(persistFile);
1416
- } catch {
1417
- // ignore
1418
- }
1419
- if (!childProcess.killed) {
1420
- childProcess.kill("SIGTERM");
1421
- // Give it time to exit gracefully
1422
- await new Promise<void>((resolve) => {
1423
- const timeout = setTimeout(() => {
1424
- if (!childProcess.killed) {
1425
- childProcess.kill("SIGKILL");
1426
- }
1427
- resolve();
1428
- }, 5000);
1429
-
1430
- childProcess.once("exit", () => {
1431
- clearTimeout(timeout);
1432
- resolve();
1433
- });
1434
- });
1435
- }
1436
- },
1437
- };
1438
- }
1439
-
1440
1293
  type PutWorkerOptions = Omit<WorkerProps, "entrypoint"> & {
1441
1294
  dispatchNamespace?: string;
1442
1295
  migrationTag?: string;
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import type { Context } from "../context.ts";
4
4
  import { formatJson } from "../fs/static-json-file.ts";
5
5
  import { Resource } from "../resource.ts";
6
+ import { isSecret } from "../secret.ts";
6
7
  import { assertNever } from "../util/assert-never.ts";
7
8
  import {
8
9
  Self,
@@ -56,6 +57,13 @@ export interface WranglerJsonProps {
56
57
  directory: string;
57
58
  };
58
59
 
60
+ /**
61
+ * Whether to include secrets in the wrangler.json file
62
+ *
63
+ * @default true
64
+ */
65
+ secrets?: boolean;
66
+
59
67
  /**
60
68
  * Transform hooks to modify generated configuration files
61
69
  */
@@ -169,6 +177,7 @@ export const WranglerJson = Resource(
169
177
  worker.eventSources,
170
178
  worker.name,
171
179
  cwd,
180
+ props.secrets ?? false,
172
181
  );
173
182
  }
174
183
 
@@ -191,7 +200,24 @@ export const WranglerJson = Resource(
191
200
  : spec;
192
201
 
193
202
  await fs.mkdir(dirname, { recursive: true });
194
- await fs.writeFile(filePath, await formatJson(finalSpec));
203
+ if (props.secrets) {
204
+ // If secrets are enabled, decrypt them in the wrangler.json file,
205
+ // but do not modify `finalSpec` so that way secrets aren't written to state unencrypted.
206
+ const withSecretsUnwrapped = {
207
+ ...finalSpec,
208
+ vars: {
209
+ ...finalSpec.vars,
210
+ ...Object.fromEntries(
211
+ Object.entries(finalSpec.vars ?? {}).map(([key, value]) =>
212
+ isSecret(value) ? [key, value.unencrypted] : [key, value],
213
+ ),
214
+ ),
215
+ },
216
+ };
217
+ await fs.writeFile(filePath, await formatJson(withSecretsUnwrapped));
218
+ } else {
219
+ await fs.writeFile(filePath, await formatJson(finalSpec));
220
+ }
195
221
 
196
222
  // Return the resource
197
223
  return this({
@@ -473,6 +499,7 @@ function processBindings(
473
499
  eventSources: EventSource[] | undefined,
474
500
  workerName: string,
475
501
  workerCwd: string,
502
+ writeSecrets: boolean,
476
503
  ): void {
477
504
  // Arrays to collect different binding types
478
505
  const kvNamespaces: {
@@ -578,10 +605,11 @@ function processBindings(
578
605
  }
579
606
  if (typeof binding === "string") {
580
607
  // Plain text binding - add to vars
581
- if (!spec.vars) {
582
- spec.vars = {};
583
- }
608
+ spec.vars ??= {};
584
609
  spec.vars[bindingName] = binding;
610
+ } else if (writeSecrets && isSecret(binding)) {
611
+ spec.vars ??= {};
612
+ spec.vars[bindingName] = binding as any;
585
613
  } else if (binding === Self) {
586
614
  // Self(service) binding
587
615
  services.push({
package/src/env.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export interface Env {
2
- [key: string]: Promise<string>;
3
- <T = string>(name: string, value?: T | undefined, error?: string): Promise<T>;
2
+ [key: string]: string;
3
+ <T = string>(name: string, value?: T | undefined, error?: string): T;
4
4
  }
5
5
 
6
6
  export const env = new Proxy(_env, {
@@ -8,22 +8,21 @@ export const env = new Proxy(_env, {
8
8
  apply: (_, __, args: [string, any?, string?]) => _env(...args),
9
9
  }) as Env;
10
10
 
11
- async function _env<T = string>(
11
+ function _env<T = string>(
12
12
  name: string,
13
13
  value?: T | undefined,
14
14
  error?: string,
15
- ): Promise<T> {
15
+ ): T {
16
16
  if (value !== undefined) {
17
17
  return value;
18
18
  }
19
- const env = await resolveEnv();
20
- if (name in env) {
21
- return env[name] as T;
19
+ if (name in environment) {
20
+ return environment[name] as T;
22
21
  }
23
22
  throw new Error(error ?? `Environment variable ${name} is not set`);
24
23
  }
25
24
 
26
- async function resolveEnv(): Promise<Record<string, any>> {
25
+ const environment = await (async (): Promise<Record<string, any>> => {
27
26
  if (typeof process !== "undefined") {
28
27
  return process.env;
29
28
  }
@@ -35,4 +34,4 @@ async function resolveEnv(): Promise<Record<string, any>> {
35
34
  return import.meta.env;
36
35
  }
37
36
  throw new Error("No environment found");
38
- }
37
+ })();
@@ -0,0 +1 @@
1
+ export * from "./random-string.ts";
@@ -0,0 +1,126 @@
1
+ import { alchemy } from "../alchemy.ts";
2
+ import { Resource } from "../resource.ts";
3
+ import type { Secret } from "../secret.ts";
4
+
5
+ /**
6
+ * Properties for configuring a random string generation
7
+ */
8
+ export interface RandomStringProps {
9
+ /**
10
+ * The number of random bytes to generate.
11
+ * The actual string length will depend on the encoding:
12
+ * - hex: length * 2 characters
13
+ * - base64: approximately length * 1.33 characters
14
+ * @default 32
15
+ */
16
+ length?: number;
17
+
18
+ /**
19
+ * The encoding format for the random string.
20
+ * - "hex": Hexadecimal encoding (0-9, a-f)
21
+ * - "base64": Base64 encoding (A-Z, a-z, 0-9, +, /)
22
+ * @default "hex"
23
+ */
24
+ encoding?: "hex" | "base64";
25
+ }
26
+
27
+ /**
28
+ * A cryptographically secure random string resource
29
+ */
30
+ export interface RandomString extends Resource<"random::String"> {
31
+ /**
32
+ * The generated random string value, stored as a secret
33
+ */
34
+ value: Secret;
35
+
36
+ /**
37
+ * The length of the random string
38
+ */
39
+ length: number;
40
+
41
+ /**
42
+ * The encoding format of the random string
43
+ */
44
+ encoding: "hex" | "base64";
45
+ }
46
+
47
+ /**
48
+ * Generates a cryptographically secure random string that can be used
49
+ * for API keys, tokens, passwords, or other security-sensitive values.
50
+ * The generated value is automatically wrapped as a secret to prevent
51
+ * accidental exposure in logs or console output.
52
+ *
53
+ * @example
54
+ * // Generate a default random string (32 bytes, hex encoded)
55
+ * const apiKey = await RandomString("api-key");
56
+ * // Result: 64 character hex string (e.g., "a1b2c3d4...")
57
+ *
58
+ * @example
59
+ * // Generate a shorter random string for a verification code
60
+ * const verificationCode = await RandomString("email-verification", {
61
+ * length: 8,
62
+ * encoding: "hex"
63
+ * });
64
+ * // Result: 16 character hex string
65
+ *
66
+ * @example
67
+ * // Generate a base64-encoded random string for a session token
68
+ * const sessionToken = await RandomString("session-token", {
69
+ * length: 48,
70
+ * encoding: "base64"
71
+ * });
72
+ * // Result: ~64 character base64 string
73
+ *
74
+ * @example
75
+ * // Use with other resources that require secrets
76
+ * const database = await PostgresDatabase("mydb", {
77
+ * name: "production",
78
+ * password: (await RandomString("db-password")).value
79
+ * });
80
+ *
81
+ * @example
82
+ * // Generate multiple unique secrets
83
+ * const jwtSecret = await RandomString("jwt-secret", { length: 64 });
84
+ * const encryptionKey = await RandomString("encryption-key", {
85
+ * length: 32,
86
+ * encoding: "base64"
87
+ * });
88
+ *
89
+ * @param id - Unique identifier for this random string resource
90
+ * @param props - Configuration options for the random string generation
91
+ * @returns A RandomString resource containing the generated secret value
92
+ */
93
+ export const RandomString = Resource(
94
+ "random::String",
95
+ async function (
96
+ _id: string,
97
+ props: RandomStringProps = {},
98
+ ): Promise<RandomString> {
99
+ if (this.phase === "delete") {
100
+ return this.destroy();
101
+ }
102
+ const length = props.length ?? 32;
103
+ const encoding = props.encoding ?? "hex";
104
+
105
+ if (
106
+ this.phase === "update" &&
107
+ this.props.length === length &&
108
+ this.props.encoding === encoding
109
+ ) {
110
+ // this is an update (input props changed)
111
+ // but the length and encoding did not change
112
+ // so, we can just return the existing output (no-op)
113
+ return this(this.output);
114
+ }
115
+ const crypto = await import("node:crypto");
116
+ return this({
117
+ length,
118
+ encoding,
119
+ value: alchemy.secret(
120
+ crypto
121
+ .randomBytes(props.length ?? 32)
122
+ .toString(props.encoding ?? "hex"),
123
+ ),
124
+ });
125
+ },
126
+ );
@@ -10,8 +10,8 @@ import { isRuntime } from "./global.ts";
10
10
  /**
11
11
  * Get a Resource's Binding from the Environment.
12
12
  */
13
- export function getBinding<T extends Resource>(resource: T): Promise<Bound<T>> {
14
- return env[getBindKey(resource)] as Promise<Bound<T>>;
13
+ export function getBinding<T extends Resource>(resource: T): Bound<T> {
14
+ return env[getBindKey(resource)] as Bound<T>;
15
15
  }
16
16
 
17
17
  export function tryGetBinding<T extends Resource>(
package/src/scope.ts CHANGED
@@ -100,8 +100,6 @@ export class Scope {
100
100
  new AsyncLocalStorage<Scope>());
101
101
  public static globals: Scope[] = (globalThis.__ALCHEMY_GLOBALS__ ??= []);
102
102
 
103
- private static exitHandlerInstalled = false;
104
-
105
103
  public static getScope(): Scope | undefined {
106
104
  const scope = Scope.storage.getStore();
107
105
  if (!scope) {
@@ -547,7 +545,7 @@ export class Scope {
547
545
  * This should only be called on the root scope.
548
546
  */
549
547
  public async cleanup() {
550
- if (this.parent) return;
548
+ if (this.parent || this.cleanups.length === 0) return;
551
549
  this.logger.log(kleur.gray("Exiting..."));
552
550
  await Promise.allSettled(this.cleanups.map((cleanup) => cleanup()));
553
551
  }
package/src/secret.ts CHANGED
@@ -123,8 +123,8 @@ export function secret<S = string>(
123
123
 
124
124
  export namespace secret {
125
125
  export interface Env {
126
- [key: string]: Promise<Secret>;
127
- (name: string, value?: string, error?: string): Promise<Secret>;
126
+ [key: string]: Secret;
127
+ (name: string, value?: string, error?: string): Secret;
128
128
  }
129
129
 
130
130
  export const env = new Proxy(_env, {
@@ -132,12 +132,8 @@ export namespace secret {
132
132
  apply: (_, __, args: [string, any?, string?]) => _env(...args),
133
133
  }) as Env;
134
134
 
135
- async function _env(
136
- name: string,
137
- value?: string,
138
- error?: string,
139
- ): Promise<Secret> {
140
- const result = await alchemy.env(name, value, error);
135
+ function _env(name: string, value?: string, error?: string): Secret {
136
+ const result = alchemy.env(name, value, error);
141
137
  if (typeof result === "string") {
142
138
  return secret(result, name);
143
139
  }
@@ -105,11 +105,11 @@ const provision = memoize(
105
105
  const scriptName = options.scriptName ?? "alchemy-state-service";
106
106
  const token =
107
107
  options.stateToken ??
108
- (await alchemy.secret.env(
108
+ alchemy.secret.env(
109
109
  "ALCHEMY_STATE_TOKEN",
110
110
  undefined,
111
111
  "Missing token for DOStateStore. Please set ALCHEMY_STATE_TOKEN in the environment or set the `stateToken` option in the DOStateStore constructor.",
112
- ));
112
+ );
113
113
 
114
114
  const api = await createCloudflareApi(options);
115
115
  const [bundle, settings, subdomain] = await Promise.all([
@@ -66,7 +66,7 @@ export class DOFSStateStore implements StateStore {
66
66
  private async createClient() {
67
67
  const token =
68
68
  this.options.worker?.token ??
69
- (await alchemy.secret.env.ALCHEMY_STATE_TOKEN).unencrypted;
69
+ alchemy.secret.env.ALCHEMY_STATE_TOKEN.unencrypted;
70
70
  if (this.options.worker && "url" in this.options.worker) {
71
71
  const client = new DOFSStateStoreClient({
72
72
  app: this.scope.appName ?? "alchemy",
@@ -34,3 +34,14 @@ export async function detectPackageManager(
34
34
 
35
35
  return "npm";
36
36
  }
37
+
38
+ export async function getPackageManagerRunner(): Promise<string> {
39
+ const packageManager = await detectPackageManager();
40
+ return {
41
+ bun: "bun run",
42
+ pnpm: "pnpm",
43
+ yarn: "yarn",
44
+ npm: "npx",
45
+ deno: "deno run",
46
+ }[packageManager];
47
+ }