alchemy 0.56.0 → 0.57.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 (256) hide show
  1. package/bin/alchemy.js +14223 -14180
  2. package/bin/commands/create.ts +15 -17
  3. package/bin/commands/deploy.ts +3 -3
  4. package/bin/commands/destroy.ts +3 -3
  5. package/bin/commands/dev.ts +3 -4
  6. package/bin/commands/init.ts +9 -9
  7. package/bin/commands/login.ts +6 -6
  8. package/bin/commands/run.ts +3 -4
  9. package/bin/constants.ts +1 -1
  10. package/bin/services/execute-alchemy.ts +4 -6
  11. package/bin/trpc.ts +49 -0
  12. package/lib/apply.d.ts.map +1 -1
  13. package/lib/apply.js +3 -4
  14. package/lib/apply.js.map +1 -1
  15. package/lib/cloudflare/assets.js +5 -4
  16. package/lib/cloudflare/assets.js.map +1 -1
  17. package/lib/cloudflare/{astro.d.ts → astro/astro.d.ts} +6 -5
  18. package/lib/cloudflare/astro/astro.d.ts.map +1 -0
  19. package/lib/cloudflare/astro/astro.js +91 -0
  20. package/lib/cloudflare/astro/astro.js.map +1 -0
  21. package/lib/cloudflare/astro/plugin.d.ts +4 -0
  22. package/lib/cloudflare/astro/plugin.d.ts.map +1 -0
  23. package/lib/cloudflare/astro/plugin.js +10 -0
  24. package/lib/cloudflare/astro/plugin.js.map +1 -0
  25. package/lib/cloudflare/cloudflare-env-proxy.d.ts +4 -0
  26. package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -0
  27. package/lib/cloudflare/cloudflare-env-proxy.js +34 -0
  28. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -0
  29. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  30. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  31. package/lib/cloudflare/d1-local-migrations.js +1 -1
  32. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  33. package/lib/cloudflare/index.d.ts +8 -8
  34. package/lib/cloudflare/index.d.ts.map +1 -1
  35. package/lib/cloudflare/index.js +8 -8
  36. package/lib/cloudflare/index.js.map +1 -1
  37. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  38. package/lib/cloudflare/miniflare/miniflare-controller.js +2 -1
  39. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  40. package/lib/cloudflare/miniflare/paths.d.ts +5 -0
  41. package/lib/cloudflare/miniflare/paths.d.ts.map +1 -0
  42. package/lib/cloudflare/miniflare/paths.js +35 -0
  43. package/lib/cloudflare/miniflare/paths.js.map +1 -0
  44. package/lib/cloudflare/{nuxt.d.ts → nuxt/nuxt.d.ts} +4 -4
  45. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -0
  46. package/lib/cloudflare/{nuxt.js → nuxt/nuxt.js} +8 -13
  47. package/lib/cloudflare/nuxt/nuxt.js.map +1 -0
  48. package/lib/cloudflare/nuxt/plugin.d.ts +4 -0
  49. package/lib/cloudflare/nuxt/plugin.d.ts.map +1 -0
  50. package/lib/cloudflare/nuxt/plugin.js +14 -0
  51. package/lib/cloudflare/nuxt/plugin.js.map +1 -0
  52. package/lib/cloudflare/oauth.js +2 -2
  53. package/lib/cloudflare/oauth.js.map +1 -1
  54. package/lib/cloudflare/orange.d.ts +3 -3
  55. package/lib/cloudflare/orange.d.ts.map +1 -1
  56. package/lib/cloudflare/orange.js +11 -21
  57. package/lib/cloudflare/orange.js.map +1 -1
  58. package/lib/cloudflare/react-router/plugin.d.ts +4 -0
  59. package/lib/cloudflare/react-router/plugin.d.ts.map +1 -0
  60. package/lib/cloudflare/react-router/plugin.js +11 -0
  61. package/lib/cloudflare/react-router/plugin.js.map +1 -0
  62. package/lib/cloudflare/react-router/react-router.d.ts +17 -0
  63. package/lib/cloudflare/react-router/react-router.d.ts.map +1 -0
  64. package/lib/cloudflare/react-router/react-router.js +21 -0
  65. package/lib/cloudflare/react-router/react-router.js.map +1 -0
  66. package/lib/cloudflare/redwood/plugin.d.ts +4 -0
  67. package/lib/cloudflare/redwood/plugin.d.ts.map +1 -0
  68. package/lib/cloudflare/redwood/plugin.js +19 -0
  69. package/lib/cloudflare/redwood/plugin.js.map +1 -0
  70. package/lib/cloudflare/{redwood.d.ts → redwood/redwood.d.ts} +5 -5
  71. package/lib/cloudflare/redwood/redwood.d.ts.map +1 -0
  72. package/lib/cloudflare/{redwood.js → redwood/redwood.js} +12 -13
  73. package/lib/cloudflare/redwood/redwood.js.map +1 -0
  74. package/lib/cloudflare/sveltekit/plugin.d.ts +4 -0
  75. package/lib/cloudflare/sveltekit/plugin.d.ts.map +1 -0
  76. package/lib/cloudflare/sveltekit/plugin.js +11 -0
  77. package/lib/cloudflare/sveltekit/plugin.js.map +1 -0
  78. package/lib/cloudflare/{sveltekit.d.ts → sveltekit/sveltekit.d.ts} +5 -5
  79. package/lib/cloudflare/sveltekit/sveltekit.d.ts.map +1 -0
  80. package/lib/cloudflare/{sveltekit.js → sveltekit/sveltekit.js} +4 -4
  81. package/lib/cloudflare/sveltekit/sveltekit.js.map +1 -0
  82. package/lib/cloudflare/{local-dev-cloudflare-shim.d.ts → tanstack-start/plugin.d.ts} +3 -2
  83. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -0
  84. package/lib/cloudflare/{local-dev-cloudflare-shim.js → tanstack-start/plugin.js} +6 -9
  85. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -0
  86. package/lib/cloudflare/{tanstack-start.d.ts → tanstack-start/tanstack-start.d.ts} +5 -5
  87. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -0
  88. package/lib/cloudflare/tanstack-start/tanstack-start.js +11 -0
  89. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -0
  90. package/lib/cloudflare/vite/plugin.d.ts +4 -0
  91. package/lib/cloudflare/vite/plugin.d.ts.map +1 -0
  92. package/lib/cloudflare/vite/plugin.js +23 -0
  93. package/lib/cloudflare/vite/plugin.js.map +1 -0
  94. package/lib/cloudflare/{vite.d.ts → vite/vite.d.ts} +4 -4
  95. package/lib/cloudflare/vite/vite.d.ts.map +1 -0
  96. package/lib/cloudflare/vite/vite.js +19 -0
  97. package/lib/cloudflare/vite/vite.js.map +1 -0
  98. package/lib/cloudflare/website.d.ts +92 -70
  99. package/lib/cloudflare/website.d.ts.map +1 -1
  100. package/lib/cloudflare/website.js +225 -94
  101. package/lib/cloudflare/website.js.map +1 -1
  102. package/lib/cloudflare/worker-bundle.d.ts +1 -1
  103. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  104. package/lib/cloudflare/worker-bundle.js +23 -13
  105. package/lib/cloudflare/worker-bundle.js.map +1 -1
  106. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  107. package/lib/cloudflare/worker-stub.js +3 -6
  108. package/lib/cloudflare/worker-stub.js.map +1 -1
  109. package/lib/cloudflare/worker.d.ts +2 -7
  110. package/lib/cloudflare/worker.d.ts.map +1 -1
  111. package/lib/cloudflare/worker.js +5 -124
  112. package/lib/cloudflare/worker.js.map +1 -1
  113. package/lib/cloudflare/wrangler.json.d.ts +6 -0
  114. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  115. package/lib/cloudflare/wrangler.json.js +23 -6
  116. package/lib/cloudflare/wrangler.json.js.map +1 -1
  117. package/lib/env.d.ts +2 -2
  118. package/lib/env.d.ts.map +1 -1
  119. package/lib/env.js +5 -6
  120. package/lib/env.js.map +1 -1
  121. package/lib/random/index.d.ts +2 -0
  122. package/lib/random/index.d.ts.map +1 -0
  123. package/lib/random/index.js +2 -0
  124. package/lib/random/index.js.map +1 -0
  125. package/lib/random/random-string.d.ts +87 -0
  126. package/lib/random/random-string.d.ts.map +1 -0
  127. package/lib/random/random-string.js +72 -0
  128. package/lib/random/random-string.js.map +1 -0
  129. package/lib/runtime/bind.d.ts +1 -1
  130. package/lib/runtime/bind.d.ts.map +1 -1
  131. package/lib/runtime/bind.js.map +1 -1
  132. package/lib/scope.d.ts +0 -1
  133. package/lib/scope.d.ts.map +1 -1
  134. package/lib/scope.js +1 -2
  135. package/lib/scope.js.map +1 -1
  136. package/lib/secret.d.ts +2 -2
  137. package/lib/secret.d.ts.map +1 -1
  138. package/lib/secret.js +2 -2
  139. package/lib/secret.js.map +1 -1
  140. package/lib/state/cloudflare-state-store.js +1 -1
  141. package/lib/state/cloudflare-state-store.js.map +1 -1
  142. package/lib/state/dofs-state-store/index.js +1 -1
  143. package/lib/state/dofs-state-store/index.js.map +1 -1
  144. package/lib/util/detect-package-manager.d.ts +1 -0
  145. package/lib/util/detect-package-manager.d.ts.map +1 -1
  146. package/lib/util/detect-package-manager.js +10 -0
  147. package/lib/util/detect-package-manager.js.map +1 -1
  148. package/lib/util/telemetry/client.d.ts +1 -1
  149. package/lib/util/telemetry/client.d.ts.map +1 -1
  150. package/lib/util/telemetry/client.js +22 -1
  151. package/lib/util/telemetry/client.js.map +1 -1
  152. package/lib/util/telemetry/constants.d.ts.map +1 -1
  153. package/lib/util/telemetry/constants.js +2 -3
  154. package/lib/util/telemetry/constants.js.map +1 -1
  155. package/lib/util/telemetry/context.d.ts +1 -1
  156. package/lib/util/telemetry/context.d.ts.map +1 -1
  157. package/lib/util/telemetry/types.d.ts +7 -3
  158. package/lib/util/telemetry/types.d.ts.map +1 -1
  159. package/lib/vercel/api.d.ts.map +1 -1
  160. package/lib/vercel/api.js +1 -1
  161. package/lib/vercel/api.js.map +1 -1
  162. package/package.json +44 -6
  163. package/src/apply.ts +3 -7
  164. package/src/cloudflare/assets.ts +7 -4
  165. package/src/cloudflare/{astro.ts → astro/astro.ts} +46 -29
  166. package/src/cloudflare/astro/plugin.ts +11 -0
  167. package/src/cloudflare/cloudflare-env-proxy.ts +50 -0
  168. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  169. package/src/cloudflare/d1-local-migrations.ts +1 -1
  170. package/src/cloudflare/index.ts +8 -8
  171. package/src/cloudflare/miniflare/miniflare-controller.ts +2 -1
  172. package/src/cloudflare/miniflare/paths.ts +52 -0
  173. package/src/cloudflare/{nuxt.ts → nuxt/nuxt.ts} +11 -16
  174. package/src/cloudflare/nuxt/plugin.ts +18 -0
  175. package/src/cloudflare/oauth.ts +2 -2
  176. package/src/cloudflare/orange.ts +13 -23
  177. package/src/cloudflare/react-router/plugin.ts +13 -0
  178. package/src/cloudflare/react-router/react-router.ts +41 -0
  179. package/src/cloudflare/redwood/plugin.ts +20 -0
  180. package/src/cloudflare/{redwood.ts → redwood/redwood.ts} +16 -19
  181. package/src/cloudflare/sveltekit/plugin.ts +12 -0
  182. package/src/cloudflare/{sveltekit.ts → sveltekit/sveltekit.ts} +9 -8
  183. package/src/cloudflare/{local-dev-cloudflare-shim.ts → tanstack-start/plugin.ts} +8 -9
  184. package/src/cloudflare/tanstack-start/tanstack-start.ts +24 -0
  185. package/src/cloudflare/vite/plugin.ts +30 -0
  186. package/src/cloudflare/vite/vite.ts +34 -0
  187. package/src/cloudflare/website.ts +379 -204
  188. package/src/cloudflare/worker-bundle.ts +27 -13
  189. package/src/cloudflare/worker-stub.ts +11 -14
  190. package/src/cloudflare/worker.ts +8 -155
  191. package/src/cloudflare/wrangler.json.ts +32 -4
  192. package/src/env.ts +8 -9
  193. package/src/random/index.ts +1 -0
  194. package/src/random/random-string.ts +126 -0
  195. package/src/runtime/bind.ts +2 -2
  196. package/src/scope.ts +1 -3
  197. package/src/secret.ts +4 -8
  198. package/src/state/cloudflare-state-store.ts +2 -2
  199. package/src/state/dofs-state-store/index.ts +1 -1
  200. package/src/util/detect-package-manager.ts +11 -0
  201. package/src/util/telemetry/client.ts +25 -2
  202. package/src/util/telemetry/constants.ts +2 -4
  203. package/src/util/telemetry/context.ts +1 -1
  204. package/src/util/telemetry/types.ts +8 -2
  205. package/src/vercel/api.ts +2 -4
  206. package/templates/astro/alchemy.run.ts +1 -3
  207. package/templates/astro/astro.config.mjs +2 -2
  208. package/templates/astro/package.json +1 -1
  209. package/templates/nuxt/alchemy.run.ts +1 -3
  210. package/templates/nuxt/nuxt.config.ts +4 -5
  211. package/templates/nuxt/package.json +1 -2
  212. package/templates/react-router/alchemy.run.ts +1 -4
  213. package/templates/react-router/vite.config.ts +2 -2
  214. package/templates/rwsdk/alchemy.run.ts +0 -1
  215. package/templates/rwsdk/package.json +1 -3
  216. package/templates/rwsdk/vite.config.mts +2 -2
  217. package/templates/sveltekit/alchemy.run.ts +1 -3
  218. package/templates/sveltekit/package.json +1 -1
  219. package/templates/sveltekit/svelte.config.js +2 -2
  220. package/templates/tanstack-start/package.json +1 -1
  221. package/templates/tanstack-start/vite.config.ts +2 -2
  222. package/templates/typescript/package.json +1 -1
  223. package/templates/vite/alchemy.run.ts +1 -1
  224. package/templates/vite/package.json +1 -1
  225. package/templates/vite/vite.config.ts +4 -5
  226. package/workers/cloudflare-state-store.js +12 -14
  227. package/lib/cloudflare/astro.d.ts.map +0 -1
  228. package/lib/cloudflare/astro.js +0 -69
  229. package/lib/cloudflare/astro.js.map +0 -1
  230. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +0 -1
  231. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +0 -1
  232. package/lib/cloudflare/nuxt.d.ts.map +0 -1
  233. package/lib/cloudflare/nuxt.js.map +0 -1
  234. package/lib/cloudflare/react-router.d.ts +0 -17
  235. package/lib/cloudflare/react-router.d.ts.map +0 -1
  236. package/lib/cloudflare/react-router.js +0 -35
  237. package/lib/cloudflare/react-router.js.map +0 -1
  238. package/lib/cloudflare/redwood.d.ts.map +0 -1
  239. package/lib/cloudflare/redwood.js.map +0 -1
  240. package/lib/cloudflare/sveltekit.d.ts.map +0 -1
  241. package/lib/cloudflare/sveltekit.js.map +0 -1
  242. package/lib/cloudflare/tanstack-start.d.ts.map +0 -1
  243. package/lib/cloudflare/tanstack-start.js +0 -21
  244. package/lib/cloudflare/tanstack-start.js.map +0 -1
  245. package/lib/cloudflare/vite.d.ts.map +0 -1
  246. package/lib/cloudflare/vite.js +0 -20
  247. package/lib/cloudflare/vite.js.map +0 -1
  248. package/src/cloudflare/react-router.ts +0 -56
  249. package/src/cloudflare/tanstack-start.ts +0 -35
  250. package/src/cloudflare/vite.ts +0 -35
  251. package/templates/react-router/wrangler.jsonc +0 -6
  252. package/templates/rwsdk/wrangler.jsonc +0 -31
  253. package/templates/sveltekit/wrangler.jsonc +0 -7
  254. package/templates/tanstack-start/wrangler.jsonc +0 -7
  255. package/templates/typescript/index.ts +0 -1
  256. package/templates/vite/wrangler.jsonc +0 -6
@@ -1,114 +1,110 @@
1
+ import assert from "node:assert";
2
+ import { spawn } from "node:child_process";
3
+ import { once } from "node:events";
4
+ import fs from "node:fs/promises";
1
5
  import path from "node:path";
2
6
  import { alchemy } from "../alchemy.ts";
3
- import { Exec } from "../os/exec.ts";
7
+ import { Exec } from "../os/index.ts";
4
8
  import { Scope } from "../scope.ts";
5
- import { isSecret } from "../secret.ts";
6
- import { detectPackageManager } from "../util/detect-package-manager.ts";
9
+ import { isSecret, type Secret } from "../secret.ts";
10
+ import { dedent } from "../util/dedent.ts";
11
+ import { DeferredPromise } from "../util/deferred-promise.ts";
12
+ import { exists } from "../util/exists.ts";
13
+ import { logger } from "../util/logger.ts";
7
14
  import { Assets } from "./assets.ts";
8
15
  import type { Bindings } from "./bindings.ts";
9
16
  import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
10
- import { Worker, type AssetsConfig, type WorkerProps } from "./worker.ts";
17
+ import { DEFAULT_PERSIST_PATH } from "./miniflare/paths.ts";
18
+ import { type AssetsConfig, Worker, type WorkerProps } from "./worker.ts";
11
19
  import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
12
20
 
13
21
  export interface WebsiteProps<B extends Bindings>
14
- extends Omit<WorkerProps<B>, "name" | "assets" | "entrypoint"> {
22
+ extends Omit<WorkerProps<B>, "assets" | "dev"> {
15
23
  /**
16
- * The command to run to build the site
24
+ * Configuration for the build command
17
25
  *
18
- * If one is not provided, the build is assumed to have already happened.
26
+ * If not provided, the build is assumed to have already happened.
19
27
  */
20
- command?: string;
21
-
22
- /**
23
- * Additional environment variables to set when running the command
24
- */
25
- commandEnv?: Record<string, string>;
26
-
27
- /**
28
- * Whether to memoize the command (only re-run if the command changes)
29
- *
30
- * When set to `true`, the command will only be re-executed if the command string changes.
31
- *
32
- * When set to an object with `patterns`, the command will be re-executed if either:
33
- * 1. The command string changes, or
34
- * 2. The contents of any files matching the glob patterns change
35
- *
36
- * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
37
- * will not be produced if the command is memoized. This is because the command is not
38
- * actually executed when memoized. Consider disabling memoization in CI environments:
39
- *
40
- * @example
41
- * // Disable memoization in CI to ensure build outputs are always produced
42
- * await Website("my-website", {
43
- * command: "vite build",
44
- * memoize: process.env.CI ? false : {
45
- * patterns: ["./src/**"]
46
- * }
47
- * });
48
- *
49
- * @default false
50
- */
51
- memoize?: boolean | { patterns: string[] };
52
-
53
- /**
54
- * The name of the worker
55
- *
56
- * @default id
57
- */
58
- name?: string;
59
- /**
60
- * The entrypoint to your server
61
- *
62
- * @default - a simple server that serves static assets is generated
63
- */
64
- main?: string;
65
- /**
66
- * The directory containing your static assets
67
- *
68
- * @default "./dist"
69
- */
70
- assets?:
28
+ build?:
71
29
  | string
72
- | ({
73
- dist?: string;
74
- } & AssetsConfig);
75
- /**
76
- * @default process.cwd()
77
- */
78
- cwd?: string;
79
-
30
+ | {
31
+ /**
32
+ * The command to run to build the site
33
+ */
34
+ command: string;
35
+ /**
36
+ * Additional environment variables to set when running the build command
37
+ */
38
+ env?: Record<string, string>;
39
+ /**
40
+ * Whether to memoize the command (only re-run if the command changes)
41
+ *
42
+ * When set to `true`, the command will only be re-executed if the command string changes.
43
+ *
44
+ * When set to an object with `patterns`, the command will be re-executed if either:
45
+ * 1. The command string changes, or
46
+ * 2. The contents of any files matching the glob patterns change
47
+ *
48
+ * ⚠️ **Important Note**: When using memoization with build commands, the build outputs
49
+ * will not be produced if the command is memoized. This is because the command is not
50
+ * actually executed when memoized. Consider disabling memoization in CI environments:
51
+ *
52
+ * @example
53
+ * // Disable memoization in CI to ensure build outputs are always produced
54
+ * await Website("my-website", {
55
+ * command: "vite build",
56
+ * memoize: process.env.CI ? false : {
57
+ * patterns: ["./src/**"]
58
+ * }
59
+ * });
60
+ *
61
+ * @default false
62
+ */
63
+ memoize?: boolean | { patterns: string[] };
64
+ };
80
65
  /**
81
- * Write a wrangler.jsonc file
82
- *
83
- * @default - no wrangler.jsonc file is written
66
+ * Configuration for the dev command
84
67
  */
85
- wrangler?:
86
- | boolean
68
+ dev?:
87
69
  | string
88
70
  | {
89
- path?: string;
90
- // override main
91
- main?: string;
71
+ /**
72
+ * The command to run to start the dev server
73
+ */
74
+ command: string;
75
+ /**
76
+ * Additional environment variables to set when running the dev command
77
+ */
78
+ env?: Record<string, string>;
92
79
  };
93
-
80
+ /**
81
+ * The directory containing static assets
82
+ *
83
+ * @default dist
84
+ */
85
+ assets?: string | ({ directory?: string } & AssetsConfig);
94
86
  /**
95
87
  * Configures default routing to support client-side routing for Single Page Applications (SPA)
96
88
  *
97
89
  * @default false
98
90
  */
99
91
  spa?: boolean;
100
-
101
- /**
102
- * Configure the command to use in development mode
103
- */
104
- dev?: {
105
- command: string;
106
- };
107
-
108
92
  /**
109
- * Transform hooks to modify generated configuration files
93
+ * Configuration for the wrangler.json file
110
94
  */
111
- transform?: {
95
+ wrangler?: {
96
+ /**
97
+ * Path to the wrangler.json file
98
+ *
99
+ * @default .alchemy/local/wrangler.jsonc
100
+ */
101
+ path?: string;
102
+ /**
103
+ * The main entry point for the worker
104
+ *
105
+ * @default worker.entrypoint
106
+ */
107
+ main?: string;
112
108
  /**
113
109
  * Hook to modify the wrangler.json object before it's written
114
110
  *
@@ -119,20 +115,33 @@ export interface WebsiteProps<B extends Bindings>
119
115
  * @param spec - The generated wrangler.json specification
120
116
  * @returns The modified wrangler.json specification
121
117
  */
122
- wrangler?: (
118
+ transform?: (
123
119
  spec: WranglerJsonSpec,
124
120
  ) => WranglerJsonSpec | Promise<WranglerJsonSpec>;
121
+ /**
122
+ * Whether to include secrets in the wrangler.json file
123
+ *
124
+ * @default true if no path is specified, false otherwise
125
+ */
126
+ secrets?: boolean;
125
127
  };
126
- }
127
128
 
128
- const packageManager = await detectPackageManager();
129
- const devCommand = {
130
- npm: "npx vite dev",
131
- bun: "bun vite dev",
132
- pnpm: "pnpm vite dev",
133
- yarn: "yarn vite dev",
134
- deno: "deno task dev",
135
- }[packageManager];
129
+ /**
130
+ * The command to run to build the site.
131
+ * @deprecated Use `build` or `build.command` instead
132
+ */
133
+ command?: string;
134
+ /**
135
+ * Additional environment variables to set when running the build command.
136
+ * @deprecated Use `build.env` instead
137
+ */
138
+ commandEnv?: Record<string, string>;
139
+ /**
140
+ * Whether to memoize the command (only re-run if the command changes)
141
+ * @deprecated Use `build.memoize` instead
142
+ */
143
+ memoize?: boolean | { patterns: string[] };
144
+ }
136
145
 
137
146
  export type Website<B extends Bindings> = B extends { ASSETS: any }
138
147
  ? never
@@ -141,128 +150,294 @@ export type Website<B extends Bindings> = B extends { ASSETS: any }
141
150
  export async function Website<B extends Bindings>(
142
151
  id: string,
143
152
  props: WebsiteProps<B>,
144
- ): Promise<Website<B>> {
145
- if (props.bindings?.ASSETS) {
146
- throw new Error("ASSETS binding is reserved for internal use");
153
+ ) {
154
+ const {
155
+ name = id,
156
+ build: buildProps,
157
+ assets,
158
+ dev,
159
+ script,
160
+ spa = true,
161
+ command,
162
+ commandEnv,
163
+ memoize,
164
+ ...workerProps
165
+ } = props;
166
+
167
+ assert(
168
+ !workerProps.bindings?.ASSETS,
169
+ "ASSETS binding is reserved for internal use",
170
+ );
171
+ if (command) {
172
+ logger.warnOnce(
173
+ "[website] The `command` prop is deprecated. Use `build.command` instead.",
174
+ );
175
+ }
176
+ if (commandEnv) {
177
+ logger.warnOnce(
178
+ "[website] The `commandEnv` prop is deprecated. Use `build.env` instead.",
179
+ );
180
+ }
181
+ if (memoize) {
182
+ logger.warnOnce(
183
+ "[website] The `memoize` prop is deprecated. Use `build.memoize` instead.",
184
+ );
147
185
  }
148
- const wrangler = props.wrangler ?? true;
149
186
 
150
- return alchemy.run(
151
- id,
152
- {
153
- parent: Scope.current,
187
+ const build = (() => {
188
+ if (typeof buildProps === "string") {
189
+ return { command: buildProps, env: commandEnv, memoize };
190
+ }
191
+ if (buildProps) {
192
+ return buildProps;
193
+ }
194
+ if (command) {
195
+ return {
196
+ command,
197
+ env: commandEnv,
198
+ memoize,
199
+ };
200
+ }
201
+ return undefined;
202
+ })();
203
+ const paths = (() => {
204
+ const cwd = props.cwd ?? process.cwd();
205
+ return {
206
+ cwd,
207
+ assets: path.resolve(
208
+ cwd,
209
+ typeof assets === "string" ? assets : (assets?.directory ?? "dist"),
210
+ ),
211
+ local: path.resolve(cwd, ".alchemy/local"),
212
+ entrypoint: path.resolve(
213
+ cwd,
214
+ props.entrypoint ?? ".alchemy/local/worker.js",
215
+ ),
216
+ get wrangler() {
217
+ return {
218
+ path: path.resolve(
219
+ cwd,
220
+ props.wrangler?.path ?? ".alchemy/local/wrangler.jsonc",
221
+ ),
222
+ main: props.wrangler?.main
223
+ ? path.resolve(cwd, props.wrangler.main)
224
+ : this.entrypoint,
225
+ };
226
+ },
227
+ };
228
+ })();
229
+ const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
230
+ const env = {
231
+ ...(process.env ?? {}),
232
+ ...(props.env ?? {}),
233
+ ...Object.fromEntries(
234
+ Object.entries(props.bindings ?? {}).flatMap(([key, value]) => {
235
+ if (typeof value === "string" || (isSecret(value) && secrets)) {
236
+ return [[key, value]];
237
+ }
238
+ return [];
239
+ }),
240
+ ),
241
+ };
242
+ const worker = {
243
+ ...workerProps,
244
+ name,
245
+ cwd: path.relative(process.cwd(), paths.cwd),
246
+ compatibilityDate:
247
+ workerProps.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
248
+ assets: {
249
+ html_handling: "auto-trailing-slash",
250
+ not_found_handling: spa ? "single-page-application" : "none",
251
+ run_worker_first: false,
252
+ ...(typeof props.assets === "string" ? {} : props.assets),
154
253
  },
155
- async (scope) => {
156
- const cwd = path.resolve(props.cwd || process.cwd());
254
+ entrypoint: path.relative(paths.cwd, paths.entrypoint),
255
+ } as WorkerProps<B> & { name: string };
157
256
 
158
- function resolveAbsPath<S extends string | undefined>(f: S): S {
159
- return (
160
- f ? (path.isAbsolute(f) ? f : path.resolve(cwd, f)) : undefined
161
- ) as S;
162
- }
257
+ return await alchemy.run(id, { parent: Scope.current }, async (scope) => {
258
+ if (!workerProps.entrypoint) {
259
+ await fs.mkdir(path.dirname(paths.entrypoint), { recursive: true });
260
+ const content =
261
+ script ??
262
+ dedent`
263
+ export default {
264
+ async fetch(request, env) {
265
+ return new Response("Not Found", { status: 404 });
266
+ },
267
+ };`;
268
+ await fs.writeFile(paths.entrypoint, content);
269
+ }
163
270
 
164
- const mainPath = resolveAbsPath(props.main);
165
- const assetsDirPath = resolveAbsPath(
166
- typeof props.assets === "string"
167
- ? props.assets
168
- : (props.assets?.dist ?? "dist"),
169
- );
170
- const wranglerJsonPath = resolveAbsPath(
171
- typeof wrangler === "boolean"
172
- ? "wrangler.jsonc"
173
- : typeof wrangler === "string"
174
- ? wrangler
175
- : (wrangler?.path ?? "wrangler.jsonc"),
176
- );
271
+ await writeMiniflareSymlink(paths.cwd);
177
272
 
178
- const workerName = props.name ?? id;
273
+ await WranglerJson("wrangler.jsonc", {
274
+ path: path.relative(paths.cwd, paths.wrangler.path),
275
+ worker,
276
+ assets: {
277
+ binding: "ASSETS",
278
+ directory: path.relative(paths.cwd, paths.assets),
279
+ },
280
+ main: path.relative(paths.cwd, paths.wrangler.main),
281
+ secrets,
282
+ transform: {
283
+ wrangler: props.wrangler?.transform,
284
+ },
285
+ });
179
286
 
180
- const workerProps = {
181
- ...props,
182
- compatibilityDate:
183
- props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
184
- name: workerName,
185
- cwd: path.relative(process.cwd(), cwd),
186
- entrypoint: mainPath,
187
- assets: {
188
- html_handling: "auto-trailing-slash",
189
- not_found_handling: props.spa ? "single-page-application" : "none",
190
- run_worker_first: false,
191
- ...(typeof props.assets === "string" ? {} : props.assets),
287
+ if (build && !scope.local) {
288
+ await Exec("build", {
289
+ cwd: path.relative(process.cwd(), paths.cwd),
290
+ command: build.command,
291
+ env: {
292
+ ...env,
293
+ ...(typeof build === "object" ? build.env : {}),
294
+ NODE_ENV: "production",
192
295
  },
193
- script: props.main
194
- ? undefined
195
- : `
196
- export default {
197
- async fetch(request, env) {
198
- return new Response("Not Found", { status: 404 });
199
- },
200
- };`,
201
- url: props.url ?? true,
202
- adopt: props.adopt ?? true,
203
- dev: {
204
- command: props.dev?.command ?? devCommand,
296
+ memoize: typeof build === "object" ? build.memoize : undefined,
297
+ });
298
+ }
299
+
300
+ let url: string | undefined;
301
+ if (dev && scope.local) {
302
+ url = await runDevCommand(scope, {
303
+ id,
304
+ command: typeof dev === "string" ? dev : dev.command,
305
+ env: {
306
+ ...env,
307
+ ...(typeof dev === "object" ? dev.env : {}),
205
308
  },
206
- } as WorkerProps<any> & { name: string };
309
+ cwd: paths.cwd,
310
+ });
311
+ }
207
312
 
208
- if (wrangler) {
209
- const wranglerAbsPath = path.resolve(cwd, wranglerJsonPath);
210
- const wranglerAbsDir = path.dirname(wranglerAbsPath);
313
+ return (await Worker("worker", {
314
+ ...worker,
315
+ bindings: {
316
+ ...worker.bindings,
317
+ ...(!scope.local
318
+ ? {
319
+ ASSETS: await Assets("assets", {
320
+ path: path.relative(process.cwd(), paths.assets),
321
+ }),
322
+ }
323
+ : {}),
324
+ },
325
+ dev: url ? { url } : undefined,
326
+ })) as Website<B>;
327
+ });
328
+ }
211
329
 
212
- await WranglerJson("wrangler.jsonc", {
213
- path: path.relative(cwd, wranglerJsonPath),
214
- worker: workerProps,
215
- // @ts-expect-error - props.wrangler can be string | object, this is fine
216
- main: props.wrangler?.main ?? props.main,
217
- // hard-code the assets directory because we haven't yet included the assets binding
218
- assets: {
219
- binding: "ASSETS",
220
- // path must be relative to the wrangler.jsonc file
221
- directory: path.relative(wranglerAbsDir, assetsDirPath),
222
- },
223
- transform: props.transform,
224
- });
225
- }
330
+ async function writeMiniflareSymlink(cwd: string) {
331
+ const target = path.resolve(DEFAULT_PERSIST_PATH);
332
+ await fs.mkdir(target, { recursive: true });
226
333
 
227
- const isDev = scope.local;
334
+ if (cwd === process.cwd()) {
335
+ return;
336
+ }
228
337
 
229
- if (props.command && !isDev) {
230
- await Exec("build", {
231
- cwd,
232
- command: props.command,
233
- env: {
234
- ...(process.env ?? {}),
235
- ...(props.env ?? {}),
236
- ...(props.commandEnv ?? {}),
237
- ...Object.fromEntries(
238
- Object.entries(props.bindings ?? {}).flatMap(([key, value]) => {
239
- if (isSecret(value)) {
240
- return [[key, value.unencrypted]];
241
- } else if (typeof value === "string") {
242
- return [[key, value]];
243
- }
244
- return [];
245
- }),
246
- ),
247
- },
248
- memoize: props.memoize,
249
- });
250
- }
338
+ const persistPath = path.resolve(cwd, DEFAULT_PERSIST_PATH);
339
+ await fs.mkdir(path.dirname(persistPath), { recursive: true });
340
+ await fs.symlink(target, persistPath).catch((e) => {
341
+ if (e.code !== "EEXIST") {
342
+ throw e;
343
+ }
344
+ });
345
+ }
251
346
 
252
- return (await Worker("worker", {
253
- ...workerProps,
254
- bindings: {
255
- ...workerProps.bindings,
256
- // we don't include the Assets binding until after build to make sure the asset manifest is correct
257
- // we generate the wrangler.json using all the bind
258
- ASSETS: isDev
259
- ? undefined
260
- : await Assets("assets", {
261
- // Assets are discovered from proces.cwd(), not Website.cwd or wrangler.jsonc
262
- path: path.relative(process.cwd(), assetsDirPath),
263
- }),
264
- },
265
- } as WorkerProps<any> & { name: string })) as Website<B>;
347
+ async function runDevCommand(
348
+ scope: Scope,
349
+ props: {
350
+ id: string;
351
+ command: string;
352
+ env: Record<string, string | Secret<string> | undefined>;
353
+ cwd?: string;
354
+ },
355
+ ) {
356
+ const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
357
+ if (await exists(persistFile)) {
358
+ const pid = Number.parseInt(await fs.readFile(persistFile, "utf8"));
359
+ try {
360
+ // Actually kill the process if it's alive
361
+ process.kill(pid, "SIGTERM");
362
+ } catch {
363
+ // ignore
364
+ }
365
+ try {
366
+ await fs.unlink(persistFile);
367
+ } catch {
368
+ // ignore
369
+ }
370
+ }
371
+ const command = props.command.split(" ");
372
+ const [cmd, ...args] = command;
373
+
374
+ const childProcess = spawn(cmd, args, {
375
+ cwd: props.cwd,
376
+ shell: true,
377
+ env: {
378
+ FORCE_COLOR: "1",
379
+ ...process.env,
380
+ ...Object.fromEntries(
381
+ Object.entries(props.env ?? {}).flatMap(([key, value]) => {
382
+ if (isSecret(value)) {
383
+ return [[key, value.unencrypted]];
384
+ }
385
+ if (typeof value === "string") {
386
+ return [[key, value]];
387
+ }
388
+ return [];
389
+ }),
390
+ ),
266
391
  },
267
- );
392
+ stdio: "pipe",
393
+ });
394
+ await once(childProcess, "spawn");
395
+ scope.onCleanup(async () => {
396
+ if (childProcess.exitCode === null && !childProcess.killed) {
397
+ childProcess.kill("SIGTERM");
398
+ await Promise.any([
399
+ once(childProcess, "exit"),
400
+ new Promise((resolve) => setTimeout(resolve, 5000)),
401
+ ]);
402
+ if (!childProcess.killed) {
403
+ childProcess.kill("SIGKILL");
404
+ }
405
+ }
406
+ try {
407
+ await fs.unlink(persistFile);
408
+ } catch {
409
+ // ignore
410
+ }
411
+ });
412
+ if (childProcess.pid) {
413
+ await fs.mkdir(path.dirname(persistFile), { recursive: true });
414
+ await fs.writeFile(persistFile, childProcess.pid.toString());
415
+ }
416
+ const URL_REGEX =
417
+ /http:\/\/(?:(?:localhost|0\.0\.0\.0|127\.0\.0\.1)|(?:\d{1,3}\.){3}\d{1,3}):\d+(?:\/)?/;
418
+ const promise = new DeferredPromise<string>();
419
+ childProcess.stdout.on("data", (data) => {
420
+ if (promise.status === "pending") {
421
+ const match = data
422
+ .toString()
423
+ .replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
424
+ .match(URL_REGEX);
425
+ if (match) {
426
+ promise.resolve(match[0]);
427
+ }
428
+ }
429
+ process.stdout.write(data);
430
+ });
431
+ childProcess.stderr.on("data", (data) => {
432
+ process.stderr.write(data);
433
+ });
434
+
435
+ return await Promise.race([
436
+ promise.value,
437
+ once(childProcess, "exit").then(([code, signal]) => {
438
+ throw new Error(
439
+ `Dev command "${props.command}" for "${props.id}" failed to start (code: ${code}, signal: ${signal})`,
440
+ );
441
+ }),
442
+ ]);
268
443
  }