alchemy 0.37.2 → 0.38.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 (262) hide show
  1. package/bin/alchemy.mjs +44306 -48055
  2. package/bin/alchemy.ts +80 -85
  3. package/bin/commands/create.ts +263 -0
  4. package/bin/constants.ts +13 -0
  5. package/bin/errors.ts +11 -0
  6. package/bin/services/dependencies.ts +47 -0
  7. package/bin/services/get-package-version.ts +11 -0
  8. package/bin/services/package-manager.ts +113 -0
  9. package/bin/services/template-manager.ts +137 -0
  10. package/bin/types.ts +83 -0
  11. package/lib/alchemy.d.ts.map +1 -1
  12. package/lib/alchemy.js +12 -3
  13. package/lib/alchemy.js.map +1 -1
  14. package/lib/apply.d.ts +2 -0
  15. package/lib/apply.d.ts.map +1 -1
  16. package/lib/apply.js +47 -12
  17. package/lib/apply.js.map +1 -1
  18. package/lib/build-date.d.ts +6 -0
  19. package/lib/build-date.d.ts.map +1 -0
  20. package/lib/build-date.js +8 -0
  21. package/lib/build-date.js.map +1 -0
  22. package/lib/cloudflare/api.d.ts.map +1 -1
  23. package/lib/cloudflare/api.js +2 -1
  24. package/lib/cloudflare/api.js.map +1 -1
  25. package/lib/cloudflare/do-state-store/internal.js +5 -3
  26. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  27. package/lib/cloudflare/do-state-store/worker.d.ts.map +1 -1
  28. package/lib/cloudflare/do-state-store/worker.js +6 -1
  29. package/lib/cloudflare/do-state-store/worker.js.map +1 -1
  30. package/lib/cloudflare/pipeline.d.ts +1 -1
  31. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  32. package/lib/cloudflare/pipeline.js +6 -1
  33. package/lib/cloudflare/pipeline.js.map +1 -1
  34. package/lib/cloudflare/worker-stub.d.ts +23 -2
  35. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  36. package/lib/cloudflare/worker-stub.js +16 -2
  37. package/lib/cloudflare/worker-stub.js.map +1 -1
  38. package/lib/cloudflare/worker.d.ts +5 -4
  39. package/lib/cloudflare/worker.d.ts.map +1 -1
  40. package/lib/cloudflare/worker.js +4 -7
  41. package/lib/cloudflare/worker.js.map +1 -1
  42. package/lib/context.d.ts +2 -2
  43. package/lib/context.d.ts.map +1 -1
  44. package/lib/destroy.d.ts +8 -2
  45. package/lib/destroy.d.ts.map +1 -1
  46. package/lib/destroy.js +19 -5
  47. package/lib/destroy.js.map +1 -1
  48. package/lib/scope.d.ts +26 -4
  49. package/lib/scope.d.ts.map +1 -1
  50. package/lib/scope.js +104 -9
  51. package/lib/scope.js.map +1 -1
  52. package/lib/sentry/project.js +1 -1
  53. package/lib/sentry/project.js.map +1 -1
  54. package/lib/util/logger.js +1 -1
  55. package/lib/util/logger.js.map +1 -1
  56. package/lib/util/mutex.d.ts +9 -0
  57. package/lib/util/mutex.d.ts.map +1 -0
  58. package/lib/util/mutex.js +35 -0
  59. package/lib/util/mutex.js.map +1 -0
  60. package/package.json +16 -6
  61. package/src/alchemy.ts +17 -5
  62. package/src/apply.ts +67 -17
  63. package/src/build-date.ts +8 -0
  64. package/src/cloudflare/api.ts +2 -1
  65. package/src/cloudflare/do-state-store/internal.ts +5 -3
  66. package/src/cloudflare/do-state-store/worker.ts +13 -3
  67. package/src/cloudflare/pipeline.ts +6 -0
  68. package/src/cloudflare/worker-stub.ts +31 -2
  69. package/src/cloudflare/worker.ts +6 -15
  70. package/src/context.ts +2 -2
  71. package/src/destroy.ts +30 -7
  72. package/src/scope.ts +145 -10
  73. package/src/sentry/project.ts +1 -1
  74. package/src/util/logger.ts +1 -1
  75. package/src/util/mutex.ts +33 -0
  76. package/templates/astro/.vscode/extensions.json +4 -0
  77. package/templates/astro/.vscode/launch.json +11 -0
  78. package/templates/astro/README.md +48 -0
  79. package/templates/astro/_env.example +1 -0
  80. package/templates/astro/_gitignore +27 -0
  81. package/templates/astro/alchemy.run.ts +16 -0
  82. package/templates/astro/astro.config.mjs +8 -0
  83. package/templates/astro/package.json +23 -0
  84. package/templates/astro/public/favicon.svg +9 -0
  85. package/templates/astro/src/assets/astro.svg +1 -0
  86. package/templates/astro/src/assets/background.svg +1 -0
  87. package/templates/astro/src/components/Welcome.astro +210 -0
  88. package/templates/astro/src/layouts/Layout.astro +22 -0
  89. package/templates/astro/src/pages/api/hello.ts +19 -0
  90. package/templates/astro/src/pages/index.astro +11 -0
  91. package/templates/astro/tsconfig.json +18 -0
  92. package/templates/astro/types/env.d.ts +16 -0
  93. package/templates/astro/wrangler.jsonc +6 -0
  94. package/templates/nuxt/README.md +75 -0
  95. package/templates/nuxt/_env +1 -0
  96. package/templates/nuxt/_env.example +1 -0
  97. package/templates/nuxt/_gitignore +27 -0
  98. package/templates/nuxt/alchemy.run.ts +16 -0
  99. package/templates/nuxt/app.vue +6 -0
  100. package/templates/nuxt/nuxt.config.ts +13 -0
  101. package/templates/nuxt/package.json +26 -0
  102. package/templates/nuxt/public/favicon.ico +0 -0
  103. package/templates/nuxt/public/robots.txt +2 -0
  104. package/templates/nuxt/server/api/hello.ts +7 -0
  105. package/templates/nuxt/server/middleware/auth.ts +5 -0
  106. package/templates/nuxt/server/middleware/hello.ts +5 -0
  107. package/templates/nuxt/server/tsconfig.json +3 -0
  108. package/templates/nuxt/tsconfig.json +15 -0
  109. package/templates/nuxt/types/env.d.ts +16 -0
  110. package/templates/react-router/.vscode/settings.json +5 -0
  111. package/templates/react-router/README.md +79 -0
  112. package/templates/react-router/_env +1 -0
  113. package/templates/react-router/_env.example +1 -0
  114. package/templates/react-router/_gitignore +15 -0
  115. package/templates/react-router/alchemy.run.ts +17 -0
  116. package/templates/react-router/app/app.css +15 -0
  117. package/templates/react-router/app/entry.server.tsx +43 -0
  118. package/templates/react-router/app/root.tsx +75 -0
  119. package/templates/react-router/app/routes/home.tsx +17 -0
  120. package/templates/react-router/app/routes.ts +3 -0
  121. package/templates/react-router/app/welcome/logo-dark.svg +23 -0
  122. package/templates/react-router/app/welcome/logo-light.svg +23 -0
  123. package/templates/react-router/app/welcome/welcome.tsx +90 -0
  124. package/templates/react-router/package.json +34 -0
  125. package/templates/react-router/public/favicon.ico +0 -0
  126. package/templates/react-router/react-router.config.ts +8 -0
  127. package/templates/react-router/tsconfig.cloudflare.json +27 -0
  128. package/templates/react-router/tsconfig.json +17 -0
  129. package/templates/react-router/tsconfig.node.json +20 -0
  130. package/templates/react-router/types/env.d.ts +16 -0
  131. package/templates/react-router/vite.config.ts +14 -0
  132. package/templates/react-router/workers/app.ts +23 -0
  133. package/templates/react-router/wrangler.jsonc +6 -0
  134. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-interruptors.mdc +247 -0
  135. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-middleware.mdc +75 -0
  136. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-react.mdc +110 -0
  137. package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-request-response.mdc +213 -0
  138. package/templates/rwsdk/.devcontainer/Dockerfile +8 -0
  139. package/templates/rwsdk/.devcontainer/devcontainer.json +21 -0
  140. package/templates/rwsdk/README.md +62 -0
  141. package/templates/rwsdk/_env +5 -0
  142. package/templates/rwsdk/_env.example +5 -0
  143. package/templates/rwsdk/_gitignore +71 -0
  144. package/templates/rwsdk/alchemy.run.ts +29 -0
  145. package/templates/rwsdk/package.json +61 -0
  146. package/templates/rwsdk/prisma/schema.prisma +43 -0
  147. package/templates/rwsdk/src/app/Document.tsx +16 -0
  148. package/templates/rwsdk/src/app/headers.ts +32 -0
  149. package/templates/rwsdk/src/app/pages/Home.tsx +13 -0
  150. package/templates/rwsdk/src/app/pages/user/Login.tsx +79 -0
  151. package/templates/rwsdk/src/app/pages/user/functions.ts +171 -0
  152. package/templates/rwsdk/src/app/pages/user/routes.ts +17 -0
  153. package/templates/rwsdk/src/app/shared/links.ts +3 -0
  154. package/templates/rwsdk/src/client.tsx +3 -0
  155. package/templates/rwsdk/src/db.ts +26 -0
  156. package/templates/rwsdk/src/scripts/seed.ts +21 -0
  157. package/templates/rwsdk/src/session/durableObject.ts +63 -0
  158. package/templates/rwsdk/src/session/store.ts +13 -0
  159. package/templates/rwsdk/src/worker.tsx +63 -0
  160. package/templates/rwsdk/tsconfig.json +47 -0
  161. package/templates/rwsdk/types/env.d.ts +16 -0
  162. package/templates/rwsdk/types/rw.d.ts +5 -0
  163. package/templates/rwsdk/types/vite.d.ts +4 -0
  164. package/templates/rwsdk/vite.config.mts +6 -0
  165. package/templates/rwsdk/wrangler.jsonc +28 -0
  166. package/templates/sveltekit/README.md +38 -0
  167. package/templates/sveltekit/_env +1 -0
  168. package/templates/sveltekit/_env.example +1 -0
  169. package/templates/sveltekit/_gitignore +26 -0
  170. package/templates/sveltekit/_npmrc +1 -0
  171. package/templates/sveltekit/_prettierrc +3 -0
  172. package/templates/sveltekit/alchemy.run.ts +16 -0
  173. package/templates/sveltekit/bun.lock +1099 -0
  174. package/templates/sveltekit/package.json +33 -0
  175. package/templates/sveltekit/src/app.css +1 -0
  176. package/templates/sveltekit/src/app.d.ts +13 -0
  177. package/templates/sveltekit/src/app.html +12 -0
  178. package/templates/sveltekit/src/routes/+layout.svelte +7 -0
  179. package/templates/sveltekit/src/routes/+page.svelte +2 -0
  180. package/templates/sveltekit/static/favicon.png +0 -0
  181. package/templates/sveltekit/svelte.config.js +12 -0
  182. package/templates/sveltekit/tsconfig.json +27 -0
  183. package/templates/sveltekit/types/env.d.ts +16 -0
  184. package/templates/sveltekit/vite.config.ts +6 -0
  185. package/templates/sveltekit/wrangler.jsonc +7 -0
  186. package/templates/tanstack-start/.vscode/settings.json +11 -0
  187. package/templates/tanstack-start/README.md +72 -0
  188. package/templates/tanstack-start/_env +1 -0
  189. package/templates/tanstack-start/_env.example +1 -0
  190. package/templates/tanstack-start/_gitignore +22 -0
  191. package/templates/tanstack-start/_prettierignore +4 -0
  192. package/templates/tanstack-start/alchemy.run.ts +16 -0
  193. package/templates/tanstack-start/package.json +37 -0
  194. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  195. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  196. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  197. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  198. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  199. package/templates/tanstack-start/public/favicon.ico +0 -0
  200. package/templates/tanstack-start/public/favicon.png +0 -0
  201. package/templates/tanstack-start/public/site.webmanifest +19 -0
  202. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +53 -0
  203. package/templates/tanstack-start/src/components/NotFound.tsx +25 -0
  204. package/templates/tanstack-start/src/components/PostError.tsx +5 -0
  205. package/templates/tanstack-start/src/components/UserError.tsx +5 -0
  206. package/templates/tanstack-start/src/router.tsx +22 -0
  207. package/templates/tanstack-start/src/routes/__root.tsx +146 -0
  208. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +10 -0
  209. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +10 -0
  210. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
  211. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +16 -0
  212. package/templates/tanstack-start/src/routes/api/users.$userId.ts +28 -0
  213. package/templates/tanstack-start/src/routes/api/users.ts +59 -0
  214. package/templates/tanstack-start/src/routes/customScript[.]js.ts +10 -0
  215. package/templates/tanstack-start/src/routes/deferred.tsx +62 -0
  216. package/templates/tanstack-start/src/routes/index.tsx +12 -0
  217. package/templates/tanstack-start/src/routes/posts.$postId.tsx +34 -0
  218. package/templates/tanstack-start/src/routes/posts.index.tsx +8 -0
  219. package/templates/tanstack-start/src/routes/posts.tsx +38 -0
  220. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +29 -0
  221. package/templates/tanstack-start/src/routes/redirect.tsx +9 -0
  222. package/templates/tanstack-start/src/routes/users.$userId.tsx +44 -0
  223. package/templates/tanstack-start/src/routes/users.index.tsx +18 -0
  224. package/templates/tanstack-start/src/routes/users.tsx +49 -0
  225. package/templates/tanstack-start/src/styles/app.css +12 -0
  226. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +41 -0
  227. package/templates/tanstack-start/src/utils/posts.tsx +40 -0
  228. package/templates/tanstack-start/src/utils/seo.ts +33 -0
  229. package/templates/tanstack-start/src/utils/users.tsx +5 -0
  230. package/templates/tanstack-start/tsconfig.json +31 -0
  231. package/templates/tanstack-start/types/env.d.ts +16 -0
  232. package/templates/tanstack-start/vite.config.ts +33 -0
  233. package/templates/tanstack-start/wrangler.jsonc +7 -0
  234. package/templates/typescript/README.md +15 -0
  235. package/templates/typescript/_gitignore +37 -0
  236. package/templates/typescript/alchemy.run.ts +15 -0
  237. package/templates/typescript/index.ts +1 -0
  238. package/templates/typescript/package.json +18 -0
  239. package/templates/typescript/src/worker.js +5 -0
  240. package/templates/typescript/src/worker.ts +7 -0
  241. package/templates/typescript/tsconfig.json +21 -0
  242. package/templates/typescript/types/env.d.ts +16 -0
  243. package/templates/vite/README.md +54 -0
  244. package/templates/vite/_env.example +1 -0
  245. package/templates/vite/_gitignore +27 -0
  246. package/templates/vite/alchemy.run.ts +17 -0
  247. package/templates/vite/eslint.config.js +28 -0
  248. package/templates/vite/index.html +13 -0
  249. package/templates/vite/package.json +35 -0
  250. package/templates/vite/public/vite.svg +1 -0
  251. package/templates/vite/src/App.css +42 -0
  252. package/templates/vite/src/App.tsx +35 -0
  253. package/templates/vite/src/assets/react.svg +1 -0
  254. package/templates/vite/src/index.css +68 -0
  255. package/templates/vite/src/main.tsx +10 -0
  256. package/templates/vite/src/vite-env.d.ts +1 -0
  257. package/templates/vite/tsconfig.json +34 -0
  258. package/templates/vite/types/env.d.ts +16 -0
  259. package/templates/vite/vite.config.ts +9 -0
  260. package/templates/vite/worker/index.ts +12 -0
  261. package/templates/vite/wrangler.jsonc +6 -0
  262. package/bin/create-alchemy.ts +0 -1220
@@ -1,1220 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { confirm, input, select } from "@inquirer/prompts";
4
- import { applyEdits, modify } from "jsonc-parser";
5
- import { execSync } from "node:child_process";
6
- import { existsSync } from "node:fs";
7
- import * as fs from "node:fs/promises";
8
- import { join, resolve } from "node:path";
9
-
10
- const isTest = process.env.NODE_ENV === "test";
11
-
12
- // Package manager detection
13
- type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
14
-
15
- // CLI options interface
16
- interface CliOptions {
17
- name?: string;
18
- template?: string;
19
- yes?: boolean;
20
- overwrite?: boolean;
21
- help?: boolean;
22
- version?: boolean;
23
- }
24
-
25
- // Mutable state that will be initialised inside `createAlchemy()` and
26
- // reused by the helper utilities defined later in the file. Keeping these
27
- // at module scope preserves the existing references inside helper
28
- // functions without requiring any further changes.
29
- let options: CliOptions = { yes: isTest };
30
- let pm: PackageManager;
31
- let alchemyVersion: string;
32
- let projectPath: string;
33
- let projectName: string;
34
- let template: string;
35
-
36
- // Define templates
37
- const templates: Template[] = [
38
- {
39
- name: "typescript",
40
- description: "Basic TypeScript Worker project",
41
- init: initTypescriptProject,
42
- },
43
- {
44
- name: "vite",
45
- description: "React Vite.js application",
46
- init: initViteProject,
47
- },
48
- {
49
- name: "astro",
50
- description: "Astro application with SSR",
51
- init: initAstroProject,
52
- },
53
- {
54
- name: "react-router",
55
- description: "React Router application",
56
- init: initReactRouterProject,
57
- },
58
- {
59
- name: "sveltekit",
60
- description: "SvelteKit application",
61
- init: initSvelteKitProject,
62
- },
63
- {
64
- name: "tanstack-start",
65
- description: "TanStack Start application",
66
- init: initTanstackStartProject,
67
- },
68
- {
69
- name: "rwsdk",
70
- description: "Redwood SDK application",
71
- init: initRedwoodProject,
72
- },
73
- {
74
- name: "nuxt",
75
- description: "Nuxt.js application",
76
- init: initNuxtProject,
77
- },
78
- ];
79
-
80
- // -------------------------------------------------------------------------------------------------
81
- // Public API – this is invoked by the parent `alchemy` CLI wrapper. It reproduces the original
82
- // behaviour but relies on the caller to provide the already-parsed command-line options.
83
- // -------------------------------------------------------------------------------------------------
84
-
85
- export async function createAlchemy(
86
- cliOptions: Partial<CliOptions> = {},
87
- ): Promise<void> {
88
- // Merge with defaults so helper functions can keep referencing the global `options`
89
- options = { yes: isTest, ...cliOptions };
90
-
91
- // Handle help / version flags early
92
- if (options.help) {
93
- console.log(`
94
- Usage: alchemy create <project-name> [options]
95
-
96
- Options:
97
- -h, --help Show help
98
- -v, --version Show version
99
- <project-name> Project name (positional / non-interactive)
100
- --template=<name> Template name (non-interactive)
101
- -y, --yes Skip confirmations (non-interactive)
102
- --overwrite Overwrite existing directory
103
-
104
- Templates:
105
- ${templates.map((t) => ` ${t.name.padEnd(15)} ${t.description}`).join("\n")}
106
- `);
107
- return;
108
- }
109
-
110
- if (options.version) {
111
- console.log("0.28.0");
112
- return;
113
- }
114
-
115
- console.log("🧪 Welcome to Alchemy!");
116
- console.log("Creating a new Alchemy project...\n");
117
-
118
- pm = detectPackageManager();
119
- console.log(`Detected package manager: ${pm}\n`);
120
-
121
- // Acquire project name – prompt if not provided
122
- if (options.name) {
123
- projectName = options.name;
124
- console.log(`Using project name: ${projectName}`);
125
- } else {
126
- projectName = await input({
127
- message: "What is your project name?",
128
- default: "my-alchemy-app",
129
- validate: (input) => {
130
- if (!input.trim()) return "Project name is required";
131
- if (!/^[a-z0-9-_]+$/i.test(input))
132
- return "Project name can only contain letters, numbers, hyphens, and underscores";
133
- return true;
134
- },
135
- });
136
- }
137
-
138
- // Validate project name (even if provided non-interactively)
139
- if (!projectName.trim()) {
140
- throw new Error("Project name is required");
141
- }
142
- if (!/^[a-z0-9-_]+$/i.test(projectName)) {
143
- throw new Error(
144
- "Project name can only contain letters, numbers, hyphens, and underscores",
145
- );
146
- }
147
-
148
- // Select template – prompt if not provided
149
- if (options.template) {
150
- template = options.template;
151
- console.log(`Using template: ${template}`);
152
-
153
- if (!templates.find((t) => t.name === template)) {
154
- throw new Error(
155
- `Template '${template}' not found. Available templates: ${templates.map((t) => t.name).join(", ")}`,
156
- );
157
- }
158
- } else {
159
- template = await select({
160
- message: "Which template would you like to use?",
161
- choices: templates.map((t) => ({
162
- name: t.description,
163
- value: t.name,
164
- })),
165
- });
166
- }
167
-
168
- const selectedTemplate = templates.find((t) => t.name === template)!;
169
-
170
- // Prepare working directory
171
- projectPath = resolve(process.cwd(), projectName);
172
-
173
- if (existsSync(projectPath)) {
174
- let overwriteConfirmed: boolean;
175
- if (options.overwrite || options.yes) {
176
- overwriteConfirmed = true;
177
- console.log(
178
- `Directory ${projectName} already exists. Overwriting due to CLI flag.`,
179
- );
180
- } else {
181
- overwriteConfirmed = await confirm({
182
- message: `Directory ${projectName} already exists. Overwrite?`,
183
- default: false,
184
- });
185
- }
186
-
187
- if (!overwriteConfirmed) {
188
- console.log("Cancelled.");
189
- return;
190
- }
191
- }
192
-
193
- console.log(`\n🔨 Creating ${template} project in ${projectPath}...`);
194
-
195
- alchemyVersion = `alchemy${isTest ? "@file:../../alchemy" : ""}`;
196
-
197
- // Execute the template initialisation
198
- await selectedTemplate.init(projectName, projectPath);
199
-
200
- // Ensure a .gitignore exists
201
- const gitignorePath = join(projectPath, ".gitignore");
202
- if (!existsSync(gitignorePath)) {
203
- await fs.writeFile(
204
- gitignorePath,
205
- "node_modules/\n.env\n.env.local\ndist/\nlib/\n.wrangler/\nwrangler.jsonc\n*.tsbuildinfo\n",
206
- );
207
- }
208
-
209
- console.log(`\n✅ Project ${projectName} created successfully!`);
210
- console.log("\n📁 Navigate to your project:");
211
- console.log(` cd ${projectName}`);
212
- console.log("\n🚀 Deploy your project:");
213
- console.log(` ${pm} run deploy`);
214
- console.log("\n🧹 Destroy your project:");
215
- console.log(` ${pm} run destroy`);
216
- console.log("\n📚 Learn more: https://alchemy.run");
217
- }
218
-
219
- // Template definitions
220
- interface Template {
221
- name: string;
222
- description: string;
223
- init: (projectName: string, projectPath: string) => Promise<void>;
224
- }
225
-
226
- async function initTypescriptProject(
227
- projectName: string,
228
- projectPath: string,
229
- ): Promise<void> {
230
- await mkdir(projectPath);
231
-
232
- const commands = getPackageManagerCommands(pm);
233
-
234
- // Initialize project
235
- execCommand(commands.init, projectPath);
236
-
237
- await createEnvTs(projectPath);
238
- await initWranglerRunTs(projectPath, {
239
- entrypoint: "src/worker.ts",
240
- });
241
- await appendGitignore(projectPath);
242
-
243
- // Create basic project structure
244
- await mkdir(projectPath, "src");
245
-
246
- // Create worker.ts
247
- await fs.writeFile(
248
- join(projectPath, "src", "worker.ts"),
249
- `import type { worker } from "../alchemy.run.ts";
250
-
251
- export default {
252
- async fetch(request: Request, env: typeof worker.Env, ctx: ExecutionContext): Promise<Response> {
253
- return new Response("Hello World from ${projectName}!");
254
- },
255
- };
256
- `,
257
- );
258
-
259
- // Create tsconfig.json
260
- await writeJsonFile(join(projectPath, "tsconfig.json"), {
261
- compilerOptions: {
262
- target: "ESNext",
263
- module: "ESNext",
264
- moduleResolution: "Bundler",
265
- strict: true,
266
- esModuleInterop: true,
267
- skipLibCheck: true,
268
- allowImportingTsExtensions: true,
269
- rewriteRelativeImportExtensions: true,
270
- types: ["@cloudflare/workers-types", "@types/node"],
271
- },
272
- include: ["src/**/*", "types/**/*", "alchemy.run.ts"],
273
- });
274
-
275
- await writeJsonFile(join(projectPath, "package.json"), {
276
- name: projectName,
277
- version: "0.0.0",
278
- description: "Alchemy Typescript Project",
279
- type: "module",
280
- scripts: {
281
- build: "tsc -b",
282
- deploy: "tsx ./alchemy.run.ts",
283
- destroy: "tsx ./alchemy.run.ts --destroy",
284
- },
285
- devDependencies: {
286
- "@cloudflare/workers-types": "latest",
287
- "@types/node": "^24.0.1",
288
- alchemy: "^0.28.0",
289
- typescript: "^5.8.3",
290
- },
291
- });
292
-
293
- // Install dependencies
294
- install({
295
- devDependencies: [
296
- alchemyVersion,
297
- "@cloudflare/workers-types",
298
- "@types/node",
299
- "typescript",
300
- ],
301
- });
302
- }
303
-
304
- async function initViteProject(
305
- projectName: string,
306
- projectPath: string,
307
- ): Promise<void> {
308
- npx(`create-vite@6.5.0 ${projectName} --template react-ts`);
309
- const root = projectPath;
310
- await rm(join(root, "tsconfig.app.json"));
311
- await rm(join(root, "tsconfig.node.json"));
312
-
313
- await initWebsiteProject(projectPath, {
314
- entrypoint: "worker/index.ts",
315
- devDependencies: ["@cloudflare/vite-plugin"],
316
- });
317
-
318
- await fs.writeFile(
319
- join(root, "vite.config.ts"),
320
- `import { defineConfig } from 'vite'
321
- import react from '@vitejs/plugin-react'
322
-
323
- import { cloudflare } from "@cloudflare/vite-plugin";
324
-
325
- // https://vite.dev/config/
326
- export default defineConfig({
327
- plugins: [react(), cloudflare()],
328
- });
329
- `,
330
- );
331
- await writeJsonFile(join(root, "tsconfig.json"), {
332
- exclude: ["test"],
333
- include: ["types/**/*.ts", "src/**/*.ts", "alchemy.run.ts"],
334
- compilerOptions: {
335
- target: "es2021",
336
- lib: ["es2021"],
337
- jsx: "react-jsx",
338
- module: "es2022",
339
- moduleResolution: "Bundler",
340
- resolveJsonModule: true,
341
- allowJs: true,
342
- checkJs: false,
343
- noEmit: true,
344
- isolatedModules: true,
345
- allowSyntheticDefaultImports: true,
346
- forceConsistentCasingInFileNames: true,
347
- allowImportingTsExtensions: true,
348
- rewriteRelativeImportExtensions: true,
349
- strict: true,
350
- skipLibCheck: true,
351
- types: ["@cloudflare/workers-types", "./types/env.d.ts"],
352
- },
353
- });
354
- await mkdir(root, "worker");
355
- await fs.writeFile(
356
- join(root, "worker", "index.ts"),
357
- `export default {
358
- fetch(request) {
359
- const url = new URL(request.url);
360
-
361
- if (url.pathname.startsWith("/api/")) {
362
- return Response.json({
363
- name: "Cloudflare",
364
- });
365
- }
366
- return new Response(null, { status: 404 });
367
- },
368
- } satisfies ExportedHandler<Env>;
369
- `,
370
- );
371
- }
372
-
373
- async function initAstroProject(
374
- projectName: string,
375
- projectPath: string,
376
- ): Promise<void> {
377
- create(
378
- pm,
379
- `astro@latest ${projectName} ${pm === "npm" ? "--" : ""} --no-git --no-deploy --install ${options.yes ? "--yes" : ""}`,
380
- );
381
-
382
- await initWebsiteProject(projectPath, {
383
- scripts: {
384
- dev: "astro dev",
385
- build: "astro check && astro build",
386
- },
387
- devDependencies: ["@astrojs/cloudflare"],
388
- });
389
-
390
- // Update astro.config.mjs
391
- await fs.writeFile(
392
- join(projectPath, "astro.config.mjs"),
393
- `import { defineConfig } from 'astro/config';
394
- import cloudflare from '@astrojs/cloudflare';
395
-
396
- // https://astro.build/config
397
- export default defineConfig({
398
- output: 'server',
399
- adapter: cloudflare(),
400
- });
401
- `,
402
- );
403
-
404
- // Create API route example
405
- await mkdir(projectPath, "src", "pages", "api");
406
- await fs.writeFile(
407
- join(projectPath, "src", "pages", "api", "hello.ts"),
408
- `import type { APIRoute } from 'astro';
409
-
410
- export const GET: APIRoute = async ({ request }) => {
411
- // Access Cloudflare runtime context
412
- const runtime = request.cf;
413
-
414
- return new Response(JSON.stringify({
415
- message: "Hello from Astro API on Cloudflare!",
416
- timestamp: new Date().toISOString(),
417
- colo: runtime?.colo || "unknown",
418
- country: runtime?.country || "unknown",
419
- city: runtime?.city || "unknown",
420
- }), {
421
- status: 200,
422
- headers: {
423
- 'Content-Type': 'application/json',
424
- },
425
- });
426
- };
427
- `,
428
- );
429
- }
430
-
431
- async function initReactRouterProject(
432
- projectName: string,
433
- projectPath: string,
434
- ): Promise<void> {
435
- create(
436
- pm,
437
- `cloudflare@2.49.3 ${projectName} ${pm === "npm" ? "--" : ""} --framework=react-router --no-git --no-deploy ${options.yes ? "--yes" : ""}`,
438
- );
439
-
440
- await initWebsiteProject(projectPath, {
441
- entrypoint: "workers/app.ts",
442
- devDependencies: ["@cloudflare/vite-plugin"],
443
- tsconfig: "tsconfig.node.json",
444
- });
445
-
446
- await modifyTsConfig(projectPath, {
447
- tsconfig: "tsconfig.node.json",
448
- });
449
-
450
- await modifyJsoncFile(join(projectPath, "tsconfig.json"), {
451
- "compilerOptions.types": undefined,
452
- "compilerOptions.noEmit": undefined,
453
- });
454
-
455
- await fs.writeFile(
456
- join(projectPath, "vite.config.ts"),
457
- `import { reactRouter } from "@react-router/dev/vite";
458
- import { cloudflare } from "@cloudflare/vite-plugin";
459
- import tailwindcss from "@tailwindcss/vite";
460
- import { defineConfig } from "vite";
461
- import tsconfigPaths from "vite-tsconfig-paths";
462
-
463
- export default defineConfig({
464
- plugins: [
465
- cloudflare({ viteEnvironment: { name: "ssr" } }),
466
- tailwindcss(),
467
- reactRouter(),
468
- tsconfigPaths(),
469
- ],
470
- });
471
- `,
472
- );
473
- }
474
-
475
- async function initSvelteKitProject(
476
- projectName: string,
477
- projectPath: string,
478
- ): Promise<void> {
479
- npx(
480
- `-y sv@latest create --install=${pm} --types=ts ${options.yes ? "--template minimal --no-add-ons" : ""} ${projectName}`,
481
- );
482
-
483
- await initWebsiteProject(projectPath, {
484
- // entrypoint: "src/routes/index.svelte",
485
- });
486
-
487
- install({
488
- devDependencies: [
489
- "@sveltejs/adapter-cloudflare",
490
- "@sveltejs/vite-plugin-svelte",
491
- ],
492
- });
493
-
494
- // Update svelte.config.js
495
- await fs.writeFile(
496
- join(projectPath, "svelte.config.js"),
497
- `import adapter from '@sveltejs/adapter-cloudflare';
498
- import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
499
-
500
- /** @type {import('@sveltejs/kit').Config} */
501
- const config = {
502
- preprocess: vitePreprocess(),
503
- kit: {
504
- adapter: adapter()
505
- }
506
- };
507
-
508
- export default config;
509
- `,
510
- );
511
-
512
- // Create vite.config.ts
513
- await fs.writeFile(
514
- join(projectPath, "vite.config.ts"),
515
- `import { sveltekit } from '@sveltejs/kit/vite';
516
- import { defineConfig } from 'vite';
517
-
518
- export default defineConfig({
519
- \tplugins: [sveltekit()],
520
- });
521
- `,
522
- );
523
- }
524
-
525
- async function initRedwoodProject(): Promise<void> {
526
- npx(`-y create-rwsdk@latest ${projectName}`);
527
- install();
528
- execCommand(`${getPackageManagerCommands(pm).run} dev:init`, projectPath);
529
- await initWebsiteProject(projectPath, {
530
- scripts: {
531
- deploy: "tsx --env-file .env ./alchemy.run.ts",
532
- destroy: "tsx --env-file .env ./alchemy.run.ts --destroy",
533
- },
534
- });
535
- await modifyJsoncFile(join(projectPath, "tsconfig.json"), {
536
- include: undefined,
537
- "compilerOptions.types": ["@cloudflare/workers-types", "types/**/*.ts"],
538
- });
539
- }
540
-
541
- async function initTanstackStartProject(
542
- projectName: string,
543
- projectPath: string,
544
- ): Promise<void> {
545
- npx(
546
- `gitpick TanStack/router/tree/main/examples/react/start-basic ${projectName}`,
547
- );
548
-
549
- await initWebsiteProject(projectPath);
550
-
551
- await Promise.all([
552
- rm(join(projectPath, "postcss.config.mjs")),
553
- rm(join(projectPath, "tailwind.config.mjs")),
554
- ]);
555
-
556
- install({
557
- dependencies: ["tailwind-merge@3"],
558
- devDependencies: ["tailwindcss@4", "@tailwindcss/vite@latest", "postcss"],
559
- });
560
-
561
- await Promise.all([
562
- fs.writeFile(
563
- join(projectPath, "vite.config.ts"),
564
- `import tailwindcss from "@tailwindcss/vite";
565
- import { tanstackStart } from "@tanstack/react-start/plugin/vite";
566
- import { cloudflareWorkersDevEnvironmentShim } from "alchemy/cloudflare";
567
- import { defineConfig, PluginOption } from "vite";
568
- import tsConfigPaths from "vite-tsconfig-paths";
569
-
570
- export default defineConfig({
571
- server: {
572
- port: 3000,
573
- },
574
- build: {
575
- target: "esnext",
576
- rollupOptions: {
577
- external: ["node:async_hooks", "cloudflare:workers"],
578
- },
579
- },
580
- plugins: [
581
- tailwindcss() as PluginOption,
582
- cloudflareWorkersDevEnvironmentShim(),
583
- tsConfigPaths({
584
- projects: ["./tsconfig.json"],
585
- }),
586
- tanstackStart({
587
- target: "cloudflare-module",
588
- tsr: {
589
- routeTreeFileHeader: [
590
- "/** biome-ignore-all lint/suspicious/noExplicitAny: code generated by @tanstack/react-start */",
591
- ],
592
- quoteStyle: "double",
593
- },
594
- }),
595
- ],
596
- });
597
- `,
598
- ),
599
-
600
- fs.writeFile(
601
- join(projectPath, "src", "styles", "app.css"),
602
- `@import "tailwindcss";
603
-
604
- :root {
605
- --border: var(--color-zinc-200);
606
- --popover: var(--color-white);
607
- --popover-foreground: var(--color-zinc-950);
608
- }
609
-
610
- @theme {
611
- --font-sans: var(--font-sans, Inter), ui-sans-serif, system-ui, sans-serif,
612
- "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
613
- }
614
- `,
615
- ),
616
- ]);
617
- }
618
-
619
- async function initNuxtProject(): Promise<void> {
620
- // create(
621
- // `cloudflare@latest -- ${projectName} --framework=nuxt --no-git --no-deploy`,
622
- // );
623
- npx(
624
- `nuxi@3.25.1 init nuxt --packageManager npm --no-install --no-gitInit ${isTest ? "--template=ui -M @nuxt/ui" : ""}`,
625
- );
626
- // npx nuxi@3.25.1 init nuxt --packageManager npm --no-install --no-gitInit
627
- /*
628
- -M, --modules Nuxt modules to install (comma separated without spaces)
629
-
630
- ◻ @nuxt/content – The file-based CMS with support for Markdown, YAML, JSON
631
- ◻ @nuxt/eslint – Project-aware, easy-to-use, extensible and future-proof ESLint integration
632
- ◻ @nuxt/fonts – Add custom web fonts with performance in mind
633
- ◻ @nuxt/icon – Icon module for Nuxt with 200,000+ ready to use icons from Iconify
634
- ◻ @nuxt/image – Add images with progressive processing, lazy-loading, resizing and providers
635
- support
636
- ◻ @nuxt/scripts – Add 3rd-party scripts without sacrificing performance
637
- ◻ @nuxt/test-utils – Test utilities for Nuxt
638
- ◻ @nuxt/ui – The Intuitive UI Library powered by Reka UI and Tailwind CSS
639
- */
640
-
641
- await initWebsiteProject(projectPath, {
642
- scripts: {
643
- build: "nuxt build",
644
- },
645
- include: ["server/**/*.ts"],
646
- });
647
-
648
- await fs.writeFile(
649
- join(projectPath, "nuxt.config.ts"),
650
- `// https://nuxt.com/docs/api/configuration/nuxt-config
651
- export default defineNuxtConfig({
652
- compatibilityDate: '2025-05-15',
653
- devtools: { enabled: true },
654
- nitro: {
655
- preset: "cloudflare_module",
656
- cloudflare: {
657
- deployConfig: true,
658
- nodeCompat: true
659
- }
660
- },
661
- modules: ["nitro-cloudflare-dev"],
662
- });
663
- `,
664
- );
665
-
666
- install({
667
- devDependencies: ["nitro-cloudflare-dev"],
668
- });
669
-
670
- install();
671
-
672
- await mkdir(projectPath, "server", "api");
673
- await fs.writeFile(
674
- join(projectPath, "server", "api", "hello.ts"),
675
- `// see: https://nuxt.com/docs/guide/directory-structure/server
676
-
677
- export default defineEventHandler((event) => {
678
- return {
679
- hello: "world",
680
- };
681
- });
682
- `,
683
- );
684
-
685
- await mkdir(projectPath, "server", "middleware");
686
- await fs.writeFile(
687
- join(projectPath, "server", "middleware", "hello.ts"),
688
- `// see: https://nuxt.com/docs/guide/directory-structure/server#server-middleware
689
-
690
- export default defineEventHandler((event) => {
691
- console.log('New request: ' + getRequestURL(event))
692
- })
693
- `,
694
- );
695
- await fs.writeFile(
696
- join(projectPath, "server", "middleware", "auth.ts"),
697
- `// see: https://nuxt.com/docs/guide/directory-structure/server#server-middleware
698
-
699
- export default defineEventHandler((event) => {
700
- event.context.auth = { user: 123 }
701
- });
702
- `,
703
- );
704
- }
705
-
706
- interface WebsiteOptions {
707
- entrypoint?: string;
708
- tsconfig?: string;
709
- scripts?: Record<string, string>;
710
- include?: string[];
711
- types?: string[];
712
- devDependencies?: string[];
713
- dependencies?: string[];
714
- }
715
-
716
- /**
717
- * Unified initialization function for website projects that use create-cloudflare
718
- */
719
- async function initWebsiteProject(
720
- projectPath: string,
721
- options: WebsiteOptions = {},
722
- ): Promise<void> {
723
- await createEnvTs(projectPath);
724
- await cleanupWrangler(projectPath);
725
- await modifyTsConfig(projectPath, options);
726
- await modifyPackageJson(projectPath, options?.scripts);
727
-
728
- // Create alchemy.run.ts
729
- await initWranglerRunTs(projectPath, options);
730
-
731
- await appendGitignore(projectPath);
732
- await appendEnv(projectPath);
733
-
734
- install({
735
- dependencies: options.dependencies,
736
- devDependencies: [
737
- "@cloudflare/workers-types",
738
- alchemyVersion,
739
- ...(pm === "bun" ? [] : ["tsx"]),
740
- "typescript",
741
- ...(options.devDependencies ?? []),
742
- ],
743
- });
744
- }
745
-
746
- async function appendGitignore(projectPath: string): Promise<void> {
747
- try {
748
- await fs.writeFile(
749
- join(projectPath, ".gitignore"),
750
- [
751
- await fs.readFile(join(projectPath, ".gitignore"), "utf-8"),
752
- ".alchemy/",
753
- ".env",
754
- ].join("\n"),
755
- );
756
- } catch {
757
- await fs.writeFile(join(projectPath, ".gitignore"), ".alchemy/");
758
- }
759
- }
760
-
761
- async function initWranglerRunTs(
762
- projectPath: string,
763
- options?: {
764
- entrypoint?: string;
765
- },
766
- ): Promise<void> {
767
- // Create alchemy.run.ts
768
- await fs.writeFile(
769
- join(projectPath, "alchemy.run.ts"),
770
- createAlchemyRunTs(projectName, options),
771
- );
772
- }
773
-
774
- function createAlchemyRunTs(
775
- projectName: string,
776
- options?: {
777
- entrypoint?: string;
778
- },
779
- ): string {
780
- const adopt = isTest ? "\n adopt: true," : "";
781
- if (template === "typescript") {
782
- return `/// <reference types="@types/node" />
783
-
784
- import alchemy from "alchemy";
785
- import { Worker } from "alchemy/cloudflare";
786
-
787
- const app = await alchemy("${projectName}");
788
-
789
- export const worker = await Worker("worker", {
790
- name: "${projectName}",${adopt}
791
- entrypoint: "${options?.entrypoint || "./src/worker.ts"}",
792
- });
793
-
794
- console.log(worker.url);
795
-
796
- await app.finalize();
797
- `;
798
- } else if (template === "rwsdk") {
799
- return `/// <reference types="@types/node" />
800
- import alchemy from "alchemy";
801
- import { D1Database, DurableObjectNamespace, Redwood } from "alchemy/cloudflare";
802
-
803
- const app = await alchemy("${projectName}");
804
-
805
- const database = await D1Database("database", {
806
- name: "${projectName}-db",${adopt}
807
- migrationsDir: "migrations",
808
- });
809
-
810
- export const worker = await Redwood("website", {
811
- name: "${projectName}-website",
812
- command: "${detectPackageManager()} run build",${adopt}
813
- bindings: {
814
- AUTH_SECRET_KEY: alchemy.secret(process.env.AUTH_SECRET_KEY),
815
- DB: database,
816
- SESSION_DURABLE_OBJECT: new DurableObjectNamespace("session", {
817
- className: "SessionDurableObject",
818
- }),
819
- },
820
- });
821
-
822
- console.log({
823
- url: worker.url,
824
- });
825
-
826
- await app.finalize();
827
- `;
828
- }
829
-
830
- // Map template names to their corresponding resource names
831
- const resourceMap: Record<string, string> = {
832
- vite: "Vite",
833
- astro: "Astro",
834
- "react-router": "ReactRouter",
835
- sveltekit: "SvelteKit",
836
- "tanstack-start": "TanStackStart",
837
- rwsdk: "Redwood",
838
- nuxt: "Nuxt",
839
- };
840
-
841
- const resourceName = resourceMap[template];
842
- if (!resourceName) {
843
- throw new Error(`Unknown template: ${template}`);
844
- }
845
-
846
- // Special configuration for Vite template
847
- const config =
848
- options?.entrypoint !== undefined
849
- ? `{
850
- main: "${options?.entrypoint || "./src/index.ts"}",
851
- command: "${detectPackageManager()} run build",${adopt}
852
- }`
853
- : `{
854
- command: "${detectPackageManager()} run build",${adopt}
855
- }`;
856
-
857
- return `/// <reference types="@types/node" />
858
-
859
- import alchemy from "alchemy";
860
- import { ${resourceName} } from "alchemy/cloudflare";
861
-
862
- const app = await alchemy("${projectName}");
863
-
864
- export const worker = await ${resourceName}("website", ${config});
865
-
866
- console.log({
867
- url: worker.url,
868
- });
869
-
870
- await app.finalize();
871
- `;
872
- }
873
-
874
- async function tryReadFile(path: string): Promise<string | undefined> {
875
- try {
876
- return await fs.readFile(path, "utf-8");
877
- } catch {
878
- return undefined;
879
- }
880
- }
881
-
882
- async function appendFile(path: string, content: string): Promise<void> {
883
- const existingContent = await tryReadFile(path);
884
- await fs.writeFile(
885
- path,
886
- `${existingContent ? `${existingContent}\n` : ""}${content}`,
887
- );
888
- }
889
-
890
- async function appendEnv(projectPath: string): Promise<void> {
891
- await appendFile(join(projectPath, ".env"), "ALCHEMY_PASSWORD=change-me");
892
- await appendFile(
893
- join(projectPath, ".env.example"),
894
- "ALCHEMY_PASSWORD=change-me",
895
- );
896
- }
897
-
898
- async function createEnvTs(
899
- projectPath: string,
900
- identifier = "worker",
901
- ): Promise<void> {
902
- // Create env.d.ts for proper typing
903
- await mkdir(projectPath, "types");
904
- await fs.writeFile(
905
- join(projectPath, "types", "env.d.ts"),
906
- `// This file infers types for the cloudflare:workers environment from your Alchemy Worker.
907
- // @see https://alchemy.run/docs/concepts/bindings.html#type-safe-bindings
908
-
909
- import type { ${identifier} } from "../alchemy.run.ts";
910
-
911
- export type CloudflareEnv = typeof ${identifier}.Env;
912
-
913
- declare global {
914
- type Env = CloudflareEnv;
915
- }
916
-
917
- declare module "cloudflare:workers" {
918
- namespace Cloudflare {
919
- export interface Env extends CloudflareEnv {}
920
- }
921
- }
922
- `,
923
- );
924
- }
925
-
926
- async function writeJsonFile(file: string, content: any): Promise<void> {
927
- await fs.writeFile(file, JSON.stringify(content, null, 2));
928
- }
929
-
930
- async function cleanupWrangler(projectPath: string): Promise<void> {
931
- if (existsSync(join(projectPath, "worker-configuration.d.ts"))) {
932
- await fs.unlink(join(projectPath, "worker-configuration.d.ts"));
933
- }
934
- if (existsSync(join(projectPath, "wrangler.jsonc"))) {
935
- await fs.unlink(join(projectPath, "wrangler.jsonc"));
936
- }
937
- }
938
-
939
- /**
940
- * Modifies a JSON/JSONC file with the given modifications
941
- */
942
- async function modifyJsoncFile(
943
- file: string,
944
- modifications: Record<string, unknown>,
945
- ): Promise<void> {
946
- if (!existsSync(file)) {
947
- return; // No file to modify
948
- }
949
-
950
- const content = await fs.readFile(file, "utf-8");
951
- let modifiedContent = content;
952
-
953
- for (const [path, value] of Object.entries(modifications)) {
954
- const pathArray = path.split(".");
955
- const edits = modify(modifiedContent, pathArray, value, {
956
- formattingOptions: {
957
- tabSize: 2,
958
- insertSpaces: true,
959
- eol: "\n",
960
- },
961
- });
962
- modifiedContent = applyEdits(modifiedContent, edits);
963
- }
964
-
965
- await fs.writeFile(file, modifiedContent);
966
- }
967
-
968
- /**
969
- * Modifies tsconfig.json to set proper Cloudflare Workers types and remove worker-configuration.d.ts
970
- */
971
- async function modifyTsConfig(
972
- projectPath: string,
973
- options: WebsiteOptions = {},
974
- ): Promise<void> {
975
- const tsconfigPath = join(projectPath, options.tsconfig ?? "tsconfig.json");
976
-
977
- if (!existsSync(tsconfigPath)) {
978
- return; // No tsconfig.json to modify
979
- }
980
-
981
- const tsconfigContent = await fs.readFile(tsconfigPath, "utf-8");
982
-
983
- // Set compilerOptions.types to ["@cloudflare/workers-types"]
984
- const typesEdit = modify(
985
- tsconfigContent,
986
- ["compilerOptions", "types"],
987
- ["@cloudflare/workers-types", "./types/env.d.ts", ...(options.types ?? [])],
988
- {
989
- formattingOptions: {
990
- tabSize: 2,
991
- insertSpaces: true,
992
- eol: "\n",
993
- },
994
- },
995
- );
996
-
997
- let modifiedContent = applyEdits(tsconfigContent, typesEdit);
998
-
999
- // Parse the JSON to get the current includes array
1000
- const { parseTree, getNodeValue, findNodeAtLocation } = await import(
1001
- "jsonc-parser"
1002
- );
1003
- const tree = parseTree(modifiedContent);
1004
- const includeNode = tree ? findNodeAtLocation(tree, ["include"]) : undefined;
1005
- const currentIncludes = includeNode ? getNodeValue(includeNode) : [];
1006
-
1007
- // Filter out worker-configuration.d.ts and ensure required files are included
1008
- let newIncludes = Array.isArray(currentIncludes) ? [...currentIncludes] : [];
1009
-
1010
- // Remove worker-configuration.d.ts if it exists
1011
- newIncludes = newIncludes.filter(
1012
- (include) =>
1013
- include !== "worker-configuration.d.ts" &&
1014
- include !== "./worker-configuration.d.ts",
1015
- );
1016
-
1017
- await fs.writeFile(
1018
- tsconfigPath,
1019
- applyEdits(
1020
- modifiedContent,
1021
- modify(
1022
- modifiedContent,
1023
- ["include"],
1024
- Array.from(
1025
- new Set([
1026
- "alchemy.run.ts",
1027
- "types/**/*.ts",
1028
- ...newIncludes.filter(
1029
- (include) =>
1030
- include !== "worker-configuration.d.ts" &&
1031
- include !== "./worker-configuration.d.ts",
1032
- ),
1033
- ...(options.include ?? []),
1034
- ]),
1035
- ),
1036
- {
1037
- formattingOptions: {
1038
- tabSize: 2,
1039
- insertSpaces: true,
1040
- eol: "\n",
1041
- },
1042
- },
1043
- ),
1044
- ),
1045
- );
1046
- }
1047
-
1048
- /**
1049
- * Modifies package.json for website projects to add proper scripts and type: "module"
1050
- */
1051
- async function modifyPackageJson(
1052
- projectPath: string,
1053
- scripts?: Record<string, string>,
1054
- ): Promise<void> {
1055
- const packageJsonPath = join(projectPath, "package.json");
1056
-
1057
- if (!existsSync(packageJsonPath)) {
1058
- return; // No package.json to modify
1059
- }
1060
-
1061
- const packageJson = {
1062
- type: "module",
1063
- ...JSON.parse(await fs.readFile(packageJsonPath, "utf-8")),
1064
- };
1065
-
1066
- // Determine deploy command based on package manager
1067
- const deployCommand =
1068
- pm === "bun"
1069
- ? "bun --env-file=./.env ./alchemy.run.ts"
1070
- : "tsx --env-file=./.env ./alchemy.run.ts";
1071
-
1072
- // Add/update scripts
1073
- if (!packageJson.scripts) {
1074
- packageJson.scripts = {};
1075
- }
1076
-
1077
- packageJson.scripts.build = scripts?.build || "vite build";
1078
- packageJson.scripts.deploy = scripts?.deploy || deployCommand;
1079
- packageJson.scripts.destroy =
1080
- scripts?.destroy || `${deployCommand} --destroy`;
1081
-
1082
- packageJson.scripts = {
1083
- ...Object.fromEntries(
1084
- Object.entries(packageJson.scripts).sort(([a], [b]) =>
1085
- a.localeCompare(b),
1086
- ),
1087
- ),
1088
- };
1089
-
1090
- // Write back to file with proper formatting
1091
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
1092
- }
1093
-
1094
- function detectPackageManager(): PackageManager {
1095
- // Check npm_execpath for bun
1096
- if (process.env.npm_execpath?.includes("bun")) {
1097
- return "bun";
1098
- }
1099
-
1100
- // Check npm_config_user_agent
1101
- const userAgent = process.env.npm_config_user_agent;
1102
- if (userAgent) {
1103
- if (userAgent.startsWith("bun")) return "bun";
1104
- if (userAgent.startsWith("pnpm")) return "pnpm";
1105
- if (userAgent.startsWith("yarn")) return "yarn";
1106
- if (userAgent.startsWith("npm")) return "npm";
1107
- }
1108
-
1109
- // Default fallback
1110
- return "npm";
1111
- }
1112
-
1113
- function getPackageManagerCommands(pm: PackageManager) {
1114
- const commands = {
1115
- bun: {
1116
- init: "bun init -y",
1117
- install: "bun install",
1118
- add: "bun add",
1119
- addDev: "bun add -D",
1120
- run: "bun run",
1121
- create: "bun create",
1122
- x: "bunx",
1123
- },
1124
- npm: {
1125
- init: "npm init -y",
1126
- install: "npm install",
1127
- add: "npm install",
1128
- addDev: "npm install --save-dev",
1129
- run: "npm run",
1130
- create: "npm create",
1131
- x: "npx",
1132
- },
1133
- pnpm: {
1134
- init: "pnpm init",
1135
- install: "pnpm install",
1136
- add: "pnpm add",
1137
- addDev: "pnpm add -D",
1138
- run: "pnpm run",
1139
- create: "pnpm create",
1140
- x: "pnpm dlx",
1141
- },
1142
- yarn: {
1143
- init: "yarn init -y",
1144
- install: "yarn install",
1145
- add: "yarn add",
1146
- addDev: "yarn add -D",
1147
- run: "yarn",
1148
- create: "yarn create",
1149
- x: "yarn dlx",
1150
- },
1151
- };
1152
-
1153
- return commands[pm];
1154
- }
1155
-
1156
- async function rm(path: string): Promise<void> {
1157
- if (existsSync(path)) {
1158
- await fs.rm(path, { recursive: true });
1159
- }
1160
- }
1161
-
1162
- async function mkdir(...path: string[]): Promise<void> {
1163
- await fs.mkdir(join(...path), {
1164
- recursive: true,
1165
- });
1166
- }
1167
-
1168
- function execCommand(command: string, cwd: string = process.cwd()): void {
1169
- console.log(command.replaceAll(/ +/g, " "));
1170
- try {
1171
- execSync(command, { stdio: "inherit", cwd });
1172
- } catch {
1173
- console.error(`Failed to execute: ${command}`);
1174
- process.exit(1);
1175
- }
1176
- }
1177
-
1178
- function install({
1179
- dependencies,
1180
- devDependencies,
1181
- cwd = projectPath,
1182
- }: {
1183
- dependencies?: string[];
1184
- devDependencies?: string[];
1185
- cwd?: string;
1186
- } = {}) {
1187
- if (!dependencies && !devDependencies) {
1188
- execCommand(getPackageManagerCommands(pm).install, cwd);
1189
- }
1190
- if (dependencies) {
1191
- execCommand(
1192
- `${getPackageManagerCommands(pm).add} ${dependencies.join(" ")}`,
1193
- cwd,
1194
- );
1195
- }
1196
- if (devDependencies) {
1197
- execCommand(
1198
- `${getPackageManagerCommands(pm).addDev} ${devDependencies.join(" ")}`,
1199
- cwd,
1200
- );
1201
- }
1202
- }
1203
-
1204
- function npx(command: string, cwd: string = process.cwd()): void {
1205
- execCommand(
1206
- `${getPackageManagerCommands(pm).x} ${options.yes ? "--yes" : ""} ${command}`,
1207
- cwd,
1208
- );
1209
- }
1210
-
1211
- function create(
1212
- pm: PackageManager,
1213
- command: string,
1214
- cwd: string = process.cwd(),
1215
- ): void {
1216
- execCommand(
1217
- `${getPackageManagerCommands(pm).create} ${options.yes ? "-y" : ""} ${command}`,
1218
- cwd,
1219
- );
1220
- }