alchemy 0.52.0 → 0.54.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 (127) hide show
  1. package/bin/alchemy.js +196353 -0
  2. package/bin/alchemy.ts +2 -2
  3. package/bin/commands/init.ts +1190 -0
  4. package/bin/constants.ts +18 -0
  5. package/bin/services/execute-alchemy.ts +7 -2
  6. package/bin/services/package-manager.ts +1 -1
  7. package/bin/types.ts +14 -5
  8. package/lib/cloudflare/astro.d.ts.map +1 -1
  9. package/lib/cloudflare/astro.js +4 -3
  10. package/lib/cloudflare/astro.js.map +1 -1
  11. package/lib/cloudflare/bundle/externals.d.ts +3 -0
  12. package/lib/cloudflare/bundle/externals.d.ts.map +1 -0
  13. package/lib/cloudflare/bundle/externals.js +35 -0
  14. package/lib/cloudflare/bundle/externals.js.map +1 -0
  15. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +8 -1
  16. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
  17. package/lib/cloudflare/bundle/internal-worker-bundle.js +11 -4
  18. package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
  19. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +4 -1
  20. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
  21. package/lib/cloudflare/bundle/plugin-hot-reload.js +27 -11
  22. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
  23. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +5 -1
  24. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
  25. package/lib/cloudflare/bundle/plugin-wasm.d.ts +6 -2
  26. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  27. package/lib/cloudflare/bundle/plugin-wasm.js +21 -47
  28. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  29. package/lib/cloudflare/bundle/validate-node-compat.d.ts +2 -1
  30. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -1
  31. package/lib/cloudflare/bundle/validate-node-compat.js +5 -4
  32. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -1
  33. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  34. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  35. package/lib/cloudflare/d1-database.js +1 -1
  36. package/lib/cloudflare/d1-database.js.map +1 -1
  37. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  38. package/lib/cloudflare/d1-migrations.js +8 -10
  39. package/lib/cloudflare/d1-migrations.js.map +1 -1
  40. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +3 -3
  41. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
  42. package/lib/cloudflare/miniflare/miniflare-worker-options.js +23 -6
  43. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
  44. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
  45. package/lib/cloudflare/miniflare/miniflare.js +2 -4
  46. package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
  47. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  48. package/lib/cloudflare/miniflare/remote-binding-proxy.js +4 -6
  49. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  50. package/lib/cloudflare/react-router.d.ts +4 -0
  51. package/lib/cloudflare/react-router.d.ts.map +1 -1
  52. package/lib/cloudflare/react-router.js +8 -5
  53. package/lib/cloudflare/react-router.js.map +1 -1
  54. package/lib/cloudflare/worker-bundle.d.ts +80 -0
  55. package/lib/cloudflare/worker-bundle.d.ts.map +1 -0
  56. package/lib/cloudflare/worker-bundle.js +266 -0
  57. package/lib/cloudflare/worker-bundle.js.map +1 -0
  58. package/lib/cloudflare/worker.d.ts +1 -1
  59. package/lib/cloudflare/worker.d.ts.map +1 -1
  60. package/lib/cloudflare/worker.js +81 -195
  61. package/lib/cloudflare/worker.js.map +1 -1
  62. package/lib/cloudflare/wrangler.json.js +2 -3
  63. package/lib/cloudflare/wrangler.json.js.map +1 -1
  64. package/lib/esbuild/bundle.d.ts +1 -0
  65. package/lib/esbuild/bundle.d.ts.map +1 -1
  66. package/lib/state/cloudflare-state-store.js +1 -5
  67. package/lib/state/cloudflare-state-store.js.map +1 -1
  68. package/lib/state/dofs-state-store/store.d.ts.map +1 -1
  69. package/lib/state/dofs-state-store/store.js +4 -4
  70. package/lib/state/dofs-state-store/store.js.map +1 -1
  71. package/package.json +10 -5
  72. package/src/cloudflare/astro.ts +4 -3
  73. package/src/cloudflare/bundle/externals.ts +36 -0
  74. package/src/cloudflare/bundle/internal-worker-bundle.ts +12 -3
  75. package/src/cloudflare/bundle/plugin-hot-reload.ts +32 -12
  76. package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +6 -1
  77. package/src/cloudflare/bundle/plugin-wasm.ts +26 -54
  78. package/src/cloudflare/bundle/validate-node-compat.ts +6 -5
  79. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  80. package/src/cloudflare/d1-database.ts +1 -1
  81. package/src/cloudflare/d1-migrations.ts +19 -18
  82. package/src/cloudflare/miniflare/miniflare-worker-options.ts +27 -13
  83. package/src/cloudflare/miniflare/miniflare.ts +2 -4
  84. package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -7
  85. package/src/cloudflare/react-router.ts +14 -6
  86. package/src/cloudflare/worker-bundle.ts +364 -0
  87. package/src/cloudflare/worker.ts +104 -241
  88. package/src/cloudflare/wrangler.json.ts +2 -3
  89. package/src/state/cloudflare-state-store.ts +1 -5
  90. package/src/state/dofs-state-store/store.ts +4 -4
  91. package/templates/nuxt/package.json +1 -1
  92. package/templates/nuxt/types/env.d.ts +11 -0
  93. package/templates/react-router/workers/app.ts +2 -2
  94. package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +385 -0
  95. package/templates/rwsdk/wrangler.jsonc +10 -7
  96. package/templates/typescript/tsconfig.json +1 -2
  97. package/bin/alchemy.mjs +0 -51766
  98. package/lib/cloudflare/bundle/esbuild.d.ts +0 -16
  99. package/lib/cloudflare/bundle/esbuild.d.ts.map +0 -1
  100. package/lib/cloudflare/bundle/esbuild.js +0 -159
  101. package/lib/cloudflare/bundle/esbuild.js.map +0 -1
  102. package/lib/cloudflare/bundle/fs.d.ts +0 -18
  103. package/lib/cloudflare/bundle/fs.d.ts.map +0 -1
  104. package/lib/cloudflare/bundle/fs.js +0 -78
  105. package/lib/cloudflare/bundle/fs.js.map +0 -1
  106. package/lib/cloudflare/bundle/index.d.ts +0 -3
  107. package/lib/cloudflare/bundle/index.d.ts.map +0 -1
  108. package/lib/cloudflare/bundle/index.js +0 -2
  109. package/lib/cloudflare/bundle/index.js.map +0 -1
  110. package/lib/cloudflare/bundle/inline.d.ts +0 -12
  111. package/lib/cloudflare/bundle/inline.d.ts.map +0 -1
  112. package/lib/cloudflare/bundle/inline.js +0 -32
  113. package/lib/cloudflare/bundle/inline.js.map +0 -1
  114. package/lib/cloudflare/bundle/normalize.d.ts +0 -21
  115. package/lib/cloudflare/bundle/normalize.d.ts.map +0 -1
  116. package/lib/cloudflare/bundle/normalize.js +0 -49
  117. package/lib/cloudflare/bundle/normalize.js.map +0 -1
  118. package/lib/cloudflare/bundle/shared.d.ts +0 -30
  119. package/lib/cloudflare/bundle/shared.d.ts.map +0 -1
  120. package/lib/cloudflare/bundle/shared.js +0 -34
  121. package/lib/cloudflare/bundle/shared.js.map +0 -1
  122. package/src/cloudflare/bundle/esbuild.ts +0 -194
  123. package/src/cloudflare/bundle/fs.ts +0 -103
  124. package/src/cloudflare/bundle/index.ts +0 -6
  125. package/src/cloudflare/bundle/inline.ts +0 -42
  126. package/src/cloudflare/bundle/normalize.ts +0 -76
  127. package/src/cloudflare/bundle/shared.ts +0 -73
@@ -0,0 +1,1190 @@
1
+ import {
2
+ cancel,
3
+ confirm,
4
+ intro,
5
+ isCancel,
6
+ log,
7
+ note,
8
+ outro,
9
+ select,
10
+ } from "@clack/prompts";
11
+ import * as fs from "fs-extra";
12
+ import { parse as parseJsonc } from "jsonc-parse";
13
+ import { dirname, relative, resolve } from "node:path";
14
+ import pc from "picocolors";
15
+ import { IndentationText, Node, Project, QuoteKind } from "ts-morph";
16
+ import z from "zod";
17
+ import { detectPackageManager } from "../../src/util/detect-package-manager.ts";
18
+ import type { DependencyVersionMap } from "../constants.ts";
19
+ import { throwWithContext } from "../errors.ts";
20
+ import { addPackageDependencies } from "../services/dependencies.ts";
21
+ import { t } from "../trpc.ts";
22
+ import {
23
+ TemplateSchema,
24
+ type InitContext,
25
+ type TemplateType,
26
+ } from "../types.ts";
27
+
28
+ export const init = t.procedure
29
+ .meta({
30
+ description: "Initialize Alchemy in an existing project",
31
+ })
32
+ .input(
33
+ z.tuple([
34
+ z.object({
35
+ framework: TemplateSchema.optional().describe(
36
+ "Force a specific framework instead of auto-detection",
37
+ ),
38
+ yes: z.boolean().optional().describe("Skip prompts and use defaults"),
39
+ }),
40
+ ]),
41
+ )
42
+ .mutation(async ({ input: [options] }) => {
43
+ try {
44
+ intro(pc.cyan("🧪 Initializing Alchemy in your project"));
45
+
46
+ const context = await createInitContext(options);
47
+
48
+ if (!context.hasPackageJson) {
49
+ log.warn(
50
+ "No package.json found. Please run in a project with package.json.",
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ await checkExistingAlchemyFiles(context);
56
+ const updatedConfig = await updateProjectConfiguration(context);
57
+ await createAlchemyRunFile({
58
+ ...context,
59
+ main: updatedConfig?.main,
60
+ });
61
+ await updatePackageJson(context);
62
+
63
+ displaySuccessMessage(context);
64
+ } catch (_error) {
65
+ console.error("Failed to initialize Alchemy:", _error);
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ function sanitizeProjectName(name: string): string {
71
+ return name
72
+ .toLowerCase()
73
+ .replace(/[@/]/g, "")
74
+ .replace(/[^a-z0-9-]/g, "-")
75
+ .replace(/-+/g, "-")
76
+ .replace(/^-|-$/g, "")
77
+ .substring(0, 63)
78
+ .replace(/-$/, "");
79
+ }
80
+
81
+ async function readJsonc(filePath: string): Promise<any> {
82
+ try {
83
+ const content = await fs.readFile(filePath, "utf-8");
84
+ return parseJsonc(content);
85
+ } catch (error) {
86
+ throw new Error(`Failed to read or parse ${filePath}: ${error}`);
87
+ }
88
+ }
89
+
90
+ async function writeJsonWithSpaces(filePath: string, data: any): Promise<void> {
91
+ await fs.writeJson(filePath, data, { spaces: 2 });
92
+ }
93
+
94
+ async function safelyUpdateJson(
95
+ filePath: string,
96
+ updater: (data: any) => void,
97
+ fallbackData: any = {},
98
+ ): Promise<void> {
99
+ try {
100
+ let data = fallbackData;
101
+ if (await fs.pathExists(filePath)) {
102
+ data = await readJsonc(filePath);
103
+ }
104
+ updater(data);
105
+ await writeJsonWithSpaces(filePath, data);
106
+ } catch (error) {
107
+ console.warn(`Failed to update ${filePath}:`, error);
108
+ }
109
+ }
110
+
111
+ async function createInitContext(options: {
112
+ framework?: TemplateType;
113
+ yes?: boolean;
114
+ }): Promise<InitContext> {
115
+ const cwd = resolve(process.cwd());
116
+ const packageJsonPath = resolve(cwd, "package.json");
117
+ const hasPackageJson = await fs.pathExists(packageJsonPath);
118
+
119
+ let projectName = "my-alchemy-app";
120
+ if (hasPackageJson) {
121
+ try {
122
+ const packageJson = await readJsonc(packageJsonPath);
123
+ if (packageJson?.name) {
124
+ projectName = sanitizeProjectName(packageJson.name);
125
+ }
126
+ } catch (_error) {}
127
+ }
128
+
129
+ const useTypeScript = await fs.pathExists(resolve(cwd, "tsconfig.json"));
130
+ const framework =
131
+ options.framework ||
132
+ (await detectFramework(cwd, hasPackageJson, options.yes));
133
+ const packageManager = await detectPackageManager(cwd);
134
+
135
+ return {
136
+ cwd,
137
+ framework,
138
+ useTypeScript,
139
+ projectName,
140
+ hasPackageJson,
141
+ packageManager,
142
+ };
143
+ }
144
+
145
+ const FRAMEWORK_DETECTION_MAP: Record<string, TemplateType> = {
146
+ rwsdk: "rwsdk",
147
+ astro: "astro",
148
+ nuxt: "nuxt",
149
+ "react-router": "react-router",
150
+ "@sveltejs/kit": "sveltekit",
151
+ "@tanstack/react-start": "tanstack-start",
152
+ vite: "vite",
153
+ };
154
+
155
+ async function detectFramework(
156
+ cwd: string,
157
+ hasPackageJson: boolean,
158
+ skipPrompts?: boolean,
159
+ ): Promise<TemplateType> {
160
+ if (!hasPackageJson) {
161
+ return "typescript";
162
+ }
163
+
164
+ const detectedFramework = await detectFrameworkFromPackageJson(cwd);
165
+
166
+ if (skipPrompts) {
167
+ return detectedFramework;
168
+ }
169
+
170
+ const frameworkResult = await select({
171
+ message: "Which framework are you using?",
172
+ options: [
173
+ { label: "TypeScript Worker", value: "typescript" },
174
+ { label: "Vite", value: "vite" },
175
+ { label: "Astro", value: "astro" },
176
+ { label: "React Router", value: "react-router" },
177
+ { label: "SvelteKit", value: "sveltekit" },
178
+ { label: "TanStack Start", value: "tanstack-start" },
179
+ { label: "Redwood SDK", value: "rwsdk" },
180
+ { label: "Nuxt.js", value: "nuxt" },
181
+ ] as const,
182
+ initialValue: detectedFramework,
183
+ });
184
+
185
+ if (isCancel(frameworkResult)) {
186
+ cancel(pc.red("Operation cancelled."));
187
+ process.exit(0);
188
+ }
189
+
190
+ return frameworkResult as TemplateType;
191
+ }
192
+
193
+ async function detectFrameworkFromPackageJson(
194
+ cwd: string,
195
+ ): Promise<TemplateType> {
196
+ const packageJsonPath = resolve(cwd, "package.json");
197
+
198
+ try {
199
+ const packageJson = await readJsonc(packageJsonPath);
200
+ const allDeps = {
201
+ ...packageJson?.dependencies,
202
+ ...packageJson?.devDependencies,
203
+ ...packageJson?.peerDependencies,
204
+ };
205
+
206
+ for (const [dep, framework] of Object.entries(FRAMEWORK_DETECTION_MAP)) {
207
+ if (dep in allDeps) return framework;
208
+ }
209
+
210
+ return "typescript";
211
+ } catch (_error) {
212
+ return "typescript";
213
+ }
214
+ }
215
+
216
+ async function checkExistingAlchemyFiles(context: InitContext): Promise<void> {
217
+ const alchemyFiles = ["alchemy.run.ts", "alchemy.run.js"];
218
+ const existingFile = alchemyFiles.find((file) =>
219
+ fs.pathExistsSync(resolve(context.cwd, file)),
220
+ );
221
+
222
+ if (existingFile) {
223
+ const overwriteResult = await confirm({
224
+ message: `${pc.yellow(existingFile)} already exists. Overwrite?`,
225
+ initialValue: false,
226
+ });
227
+
228
+ if (isCancel(overwriteResult) || !overwriteResult) {
229
+ cancel(pc.red("Operation cancelled."));
230
+ process.exit(0);
231
+ }
232
+ }
233
+ }
234
+
235
+ const ALCHEMY_RUN_TEMPLATES: Record<
236
+ TemplateType,
237
+ (context: InitContext) => string
238
+ > = {
239
+ typescript: (context) => `/// <reference types="@types/node" />
240
+
241
+ import alchemy from "alchemy";
242
+ import { Worker } from "alchemy/cloudflare";
243
+
244
+ const app = await alchemy("${context.projectName}");
245
+
246
+ export const worker = await Worker("worker", {
247
+ name: \`\${app.name}-\${app.stage}\`,
248
+ entrypoint: "src/worker.ts",
249
+ });
250
+
251
+ console.log(worker.url);
252
+ await app.finalize();
253
+ `,
254
+
255
+ vite: (context) => `import alchemy from "alchemy";
256
+ import { Vite } from "alchemy/cloudflare";
257
+
258
+ const app = await alchemy("${context.projectName}");
259
+
260
+ export const worker = await Vite("${context.projectName}", {
261
+ name: \`\${app.name}-\${app.stage}-website\`,
262
+ // replace if different from default:
263
+ //
264
+ // main: "./.output/server/index.mjs",
265
+ // command: "vite build",
266
+ // dev: { command: "vite dev" },
267
+ assets: "dist",
268
+ wrangler: false,
269
+ });
270
+
271
+ console.log({
272
+ url: worker.url,
273
+ });
274
+
275
+ await app.finalize();
276
+ `,
277
+
278
+ astro: (context) => `/// <reference types="@types/node" />
279
+
280
+ import alchemy from "alchemy";
281
+ import { Astro } from "alchemy/cloudflare";
282
+
283
+ const app = await alchemy("${context.projectName}");
284
+
285
+ export const worker = await Astro("website", {
286
+ name: \`\${app.name}-\${app.stage}-website\`,
287
+ // replace if different from default:
288
+ //
289
+ // main: "./.output/server/index.mjs",
290
+ // command: "astro build",
291
+ // dev: { command: "astro dev" },
292
+ });
293
+
294
+ console.log({
295
+ url: worker.url,
296
+ });
297
+
298
+ await app.finalize();
299
+ `,
300
+
301
+ "react-router": (context) => `/// <reference types="@types/node" />
302
+
303
+ import alchemy from "alchemy";
304
+ import { ReactRouter } from "alchemy/cloudflare";
305
+
306
+ const app = await alchemy("${context.projectName}");
307
+
308
+ export const worker = await ReactRouter("website", {
309
+ name: \`\${app.name}-\${app.stage}-website\`,
310
+ // replace if different from default:
311
+ //
312
+ // main: "build/server/index.js",
313
+ // command: "react-router build",
314
+ // dev: { command: "react-router dev" },
315
+ ${context.main ? `wrangler: { main: "${context.main}" },` : ""}
316
+ });
317
+
318
+ console.log({
319
+ url: worker.url,
320
+ });
321
+
322
+ await app.finalize();
323
+ `,
324
+
325
+ sveltekit: (context) => `/// <reference types="@types/node" />
326
+
327
+ import alchemy from "alchemy";
328
+ import { SvelteKit } from "alchemy/cloudflare";
329
+
330
+ const app = await alchemy("${context.projectName}");
331
+
332
+ export const worker = await SvelteKit("website", {
333
+ name: \`\${app.name}-\${app.stage}-website\`,
334
+ // replace if different from default:
335
+ //
336
+ // main: "./.output/server/index.mjs",
337
+ // command: "RWSDK_DEPLOY=1 vite build",
338
+ // dev: { command: "vite dev" },
339
+ });
340
+
341
+ console.log({
342
+ url: worker.url,
343
+ });
344
+
345
+ await app.finalize();
346
+ `,
347
+
348
+ "tanstack-start": (context) => `/// <reference types="@types/node" />
349
+
350
+ import alchemy from "alchemy";
351
+ import { TanStackStart } from "alchemy/cloudflare";
352
+
353
+ const app = await alchemy("${context.projectName}");
354
+
355
+ export const worker = await TanStackStart("website", {
356
+ name: \`\${app.name}-\${app.stage}-website\`,
357
+ // replace if different from default:
358
+ //
359
+ // main: "./.output/server/index.mjs",
360
+ // command: "RWSDK_DEPLOY=1 vite build",
361
+ // dev: { command: "vite dev" },
362
+ });
363
+
364
+ console.log({
365
+ url: worker.url,
366
+ });
367
+
368
+ await app.finalize();
369
+ `,
370
+
371
+ rwsdk: (context) => `/// <reference types="@types/node" />
372
+
373
+ import alchemy from "alchemy";
374
+ import { D1Database, DurableObjectNamespace, Redwood } from "alchemy/cloudflare";
375
+
376
+ const app = await alchemy("${context.projectName}");
377
+
378
+ const database = await D1Database("database", {
379
+ name: \`\${app.name}-\${app.stage}-db\`,
380
+ migrationsDir: "migrations",
381
+ });
382
+
383
+ export const worker = await Redwood("website", {
384
+ name: \`\${app.name}-\${app.stage}-website\`,
385
+ bindings: {
386
+ AUTH_SECRET_KEY: alchemy.secret(process.env.AUTH_SECRET_KEY),
387
+ DB: database,
388
+ SESSION_DURABLE_OBJECT: DurableObjectNamespace("session", {
389
+ className: "SessionDurableObject",
390
+ }),
391
+ },
392
+ // replace if different from default:
393
+ //
394
+ // main: "./.output/server/index.mjs",
395
+ // command: "RWSDK_DEPLOY=1 vite build",
396
+ // dev: { command: "vite dev" },
397
+ });
398
+
399
+ console.log({
400
+ url: worker.url,
401
+ });
402
+
403
+ await app.finalize();
404
+ `,
405
+
406
+ nuxt: (context) => `/// <reference types="@types/node" />
407
+
408
+ import alchemy from "alchemy";
409
+ import { Nuxt } from "alchemy/cloudflare";
410
+
411
+ const app = await alchemy("${context.projectName}");
412
+
413
+ export const website = await Nuxt("website", {
414
+ name: \`\${app.name}-\${app.stage}-website\`,
415
+ // replace if different from default:
416
+ //
417
+ // main: "./.output/server/index.mjs",
418
+ // command: "nuxt build",
419
+ // dev: { command: "nuxt dev" },
420
+ });
421
+
422
+ console.log({
423
+ url: website.url,
424
+ });
425
+
426
+ await app.finalize();
427
+ `,
428
+ };
429
+
430
+ async function createAlchemyRunFile(context: InitContext): Promise<void> {
431
+ try {
432
+ const content = ALCHEMY_RUN_TEMPLATES[context.framework](context);
433
+ const outputFileName = context.useTypeScript
434
+ ? "alchemy.run.ts"
435
+ : "alchemy.run.js";
436
+ const outputPath = resolve(context.cwd, outputFileName);
437
+ await fs.writeFile(outputPath, content, "utf-8");
438
+ } catch (error) {
439
+ throwWithContext(error, "Failed to create alchemy.run file");
440
+ }
441
+ }
442
+
443
+ const FRAMEWORK_DEPENDENCIES: Record<TemplateType, DependencyVersionMap[]> = {
444
+ nuxt: ["alchemy", "nitro-cloudflare-dev"],
445
+ sveltekit: ["alchemy", "@sveltejs/adapter-cloudflare"],
446
+ typescript: ["alchemy"],
447
+ vite: ["alchemy"],
448
+ astro: ["alchemy"],
449
+ "react-router": ["alchemy", "@cloudflare/vite-plugin"],
450
+ "tanstack-start": ["alchemy"],
451
+ rwsdk: ["alchemy"],
452
+ };
453
+
454
+ const DEFAULT_SCRIPTS = {
455
+ deploy: "alchemy deploy",
456
+ destroy: "alchemy destroy",
457
+ "alchemy:dev": "alchemy dev",
458
+ };
459
+
460
+ async function updatePackageJson(context: InitContext): Promise<void> {
461
+ try {
462
+ const devDependencies = FRAMEWORK_DEPENDENCIES[context.framework];
463
+ await addPackageDependencies({
464
+ devDependencies,
465
+ projectDir: context.cwd,
466
+ });
467
+
468
+ const packageJsonPath = resolve(context.cwd, "package.json");
469
+ await safelyUpdateJson(packageJsonPath, (packageJson) => {
470
+ if (!packageJson.scripts) {
471
+ packageJson.scripts = {};
472
+ }
473
+
474
+ for (const [script, command] of Object.entries(DEFAULT_SCRIPTS)) {
475
+ if (!packageJson.scripts[script]) {
476
+ packageJson.scripts[script] = command;
477
+ }
478
+ }
479
+ });
480
+ } catch (error) {
481
+ throwWithContext(error, "Failed to update package.json");
482
+ }
483
+ }
484
+
485
+ interface TsConfigUpdate {
486
+ include?: string[];
487
+ exclude?: string[];
488
+ compilerOptions?: {
489
+ types?: string[];
490
+ };
491
+ }
492
+
493
+ async function updateTsConfig(
494
+ configPath: string,
495
+ updates: TsConfigUpdate,
496
+ ): Promise<void> {
497
+ await safelyUpdateJson(configPath, (tsConfig) => {
498
+ if (updates.include) {
499
+ if (!tsConfig.include) tsConfig.include = [];
500
+ updates.include.forEach((item) => {
501
+ if (!tsConfig.include.includes(item)) {
502
+ tsConfig.include.push(item);
503
+ }
504
+ });
505
+ }
506
+
507
+ if (updates.exclude) {
508
+ if (tsConfig.include) {
509
+ tsConfig.include = tsConfig.include.filter(
510
+ (p: string) => !updates.exclude!.includes(p),
511
+ );
512
+ }
513
+ }
514
+
515
+ if (updates.compilerOptions?.types) {
516
+ if (!tsConfig.compilerOptions) tsConfig.compilerOptions = {};
517
+ if (!tsConfig.compilerOptions.types) tsConfig.compilerOptions.types = [];
518
+
519
+ updates.compilerOptions.types.forEach((type) => {
520
+ if (!tsConfig.compilerOptions.types.includes(type)) {
521
+ tsConfig.compilerOptions.types.push(type);
522
+ }
523
+ });
524
+ }
525
+ });
526
+ }
527
+
528
+ async function updateProjectConfiguration(context: InitContext): Promise<{
529
+ main: string | undefined;
530
+ } | void> {
531
+ return {
532
+ typescript: () => updateTypescriptProject(context),
533
+ vite: () => updateViteProject(context),
534
+ astro: () => updateAstroProject(context),
535
+ "react-router": () => updateReactRouterProject(context),
536
+ sveltekit: () => updateSvelteKitProject(context),
537
+ "tanstack-start": () => updateTanStackStartProject(context),
538
+ rwsdk: () => updateRwsdkProject(context),
539
+ nuxt: () => updateNuxtProject(context),
540
+ }[context.framework]();
541
+ }
542
+
543
+ async function updateTypescriptProject(context: InitContext): Promise<void> {
544
+ const tsConfigPath = resolve(context.cwd, "tsconfig.json");
545
+ if (await fs.pathExists(tsConfigPath)) {
546
+ await updateTsConfig(tsConfigPath, {
547
+ include: ["alchemy.run.ts"],
548
+ });
549
+ }
550
+ }
551
+
552
+ async function updateViteProject(context: InitContext): Promise<void> {
553
+ // const tsConfigPath = resolve(context.cwd, "tsconfig.json");
554
+ // const tsConfigNodePath = resolve(context.cwd, "tsconfig.node.json");
555
+ // if (await fs.pathExists(tsConfigPath)) {
556
+ // await updateTsConfig(tsConfigPath, {
557
+ // exclude: ["alchemy.run.ts", "./types/env.d.ts"],
558
+ // });
559
+ // }
560
+ // if ((await fs.pathExists(tsConfigNodePath)) || context.framework === "vite") {
561
+ // await updateTsConfig(tsConfigNodePath, {
562
+ // include: ["alchemy.run.ts"],
563
+ // });
564
+ // }
565
+ }
566
+
567
+ async function updateSvelteKitProject(context: InitContext): Promise<void> {
568
+ await updateSvelteConfig(context);
569
+
570
+ // const tsConfigPath = resolve(context.cwd, "tsconfig.json");
571
+ // await updateTsConfig(tsConfigPath, {
572
+ // include: ["alchemy.run.ts"],
573
+ // });
574
+ }
575
+
576
+ async function updateRwsdkProject(context: InitContext): Promise<void> {
577
+ await updateEnvFile(context);
578
+
579
+ // const tsConfigPath = resolve(context.cwd, "tsconfig.json");
580
+ // await updateTsConfig(tsConfigPath, {
581
+ // include: ["alchemy.run.ts"],
582
+ // });
583
+ }
584
+
585
+ async function updateNuxtProject(context: InitContext): Promise<void> {
586
+ await updateNuxtConfig(context);
587
+
588
+ // const tsConfigPath = resolve(context.cwd, "tsconfig.json");
589
+ // await updateTsConfig(tsConfigPath, {
590
+ // include: ["alchemy.run.ts"],
591
+ // });
592
+ }
593
+
594
+ async function updateAstroProject(context: InitContext): Promise<void> {
595
+ await updateAstroConfig(context);
596
+
597
+ // const tsConfigPath = resolve(context.cwd, "tsconfig.json");
598
+ // await updateTsConfig(tsConfigPath, {
599
+ // include: ["alchemy.run.ts", "types/**/*.ts"],
600
+ // compilerOptions: {
601
+ // types: ["@cloudflare/workers-types", "./types/env.d.ts"],
602
+ // },
603
+ // });
604
+ }
605
+
606
+ async function updateReactRouterProject(context: InitContext): Promise<{
607
+ main: string | undefined;
608
+ }> {
609
+ const tsConfigPath = resolve(context.cwd, "tsconfig.json");
610
+ const workersDir = resolve(context.cwd, "workers");
611
+ const wranglerJsonCPath = resolve(context.cwd, "wrangler.jsonc");
612
+ const wranglerJsonPath = resolve(context.cwd, "wrangler.json");
613
+ const envTsPath = resolve(workersDir, "env.ts");
614
+
615
+ await fs.ensureDir(workersDir);
616
+
617
+ let main: string | undefined;
618
+ if (await fs.pathExists(wranglerJsonCPath)) {
619
+ const wranglerJsonC = await readJsonc(wranglerJsonCPath);
620
+ main = wranglerJsonC.main;
621
+ } else if (await fs.pathExists(wranglerJsonPath)) {
622
+ const wranglerJson = await readJsonc(wranglerJsonPath);
623
+ main = wranglerJson.main;
624
+ } else {
625
+ await fs.writeFile(
626
+ resolve(workersDir, "app.ts"),
627
+ `
628
+ import { createRequestHandler } from "react-router";
629
+
630
+ declare module "react-router" {
631
+ export interface AppLoadContext {
632
+ cloudflare: {
633
+ env: Env;
634
+ ctx: ExecutionContext;
635
+ };
636
+ }
637
+ }
638
+
639
+ const requestHandler = createRequestHandler(
640
+ () => import("virtual:react-router/server-build"),
641
+ import.meta.env.MODE
642
+ );
643
+
644
+ export default {
645
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
646
+ return requestHandler(request, {
647
+ cloudflare: { env, ctx },
648
+ });
649
+ },
650
+ }
651
+ `,
652
+ );
653
+ }
654
+
655
+ await fs.writeFile(
656
+ envTsPath,
657
+ `
658
+ import type { website } from "../alchemy.run.ts";
659
+
660
+ export type CloudflareEnv = typeof website.Env;
661
+
662
+ declare global {
663
+ type Env = CloudflareEnv
664
+ }
665
+
666
+ declare module "cloudflare:workers" {
667
+ namespace Cloudflare {
668
+ export interface Env extends CloudflareEnv {}
669
+ }
670
+ }`,
671
+ );
672
+
673
+ await updateTsConfig(tsConfigPath, {
674
+ include: ["alchemy.run.ts", "workers/**/*.ts"],
675
+ compilerOptions: {
676
+ types: [
677
+ "@cloudflare/workers-types",
678
+ relative(dirname(tsConfigPath), envTsPath),
679
+ ],
680
+ },
681
+ });
682
+
683
+ await updateViteConfig(context);
684
+ await updateReactRouterConfigTS(context);
685
+
686
+ return {
687
+ main,
688
+ };
689
+ }
690
+
691
+ async function updateViteConfig(context: InitContext): Promise<void> {
692
+ const viteConfigPath = resolve(context.cwd, "vite.config.ts");
693
+ if (!(await fs.pathExists(viteConfigPath))) return;
694
+
695
+ try {
696
+ const project = new Project({
697
+ manipulationSettings: {
698
+ indentationText: IndentationText.TwoSpaces,
699
+ quoteKind: QuoteKind.Double,
700
+ },
701
+ });
702
+
703
+ project.addSourceFileAtPath(viteConfigPath);
704
+ const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
705
+
706
+ // Check if cloudflare import already exists
707
+ const cloudflareImport = sourceFile.getImportDeclaration(
708
+ "@cloudflare/vite-plugin",
709
+ );
710
+ if (!cloudflareImport) {
711
+ // Add cloudflare import
712
+ sourceFile.addImportDeclaration({
713
+ moduleSpecifier: "@cloudflare/vite-plugin",
714
+ namedImports: ["cloudflare"],
715
+ });
716
+ }
717
+
718
+ // Find the defineConfig call
719
+ const exportAssignment = sourceFile.getExportAssignment(
720
+ (d) => !d.isExportEquals(),
721
+ );
722
+ if (!exportAssignment) return;
723
+
724
+ const defineConfigCall = exportAssignment.getExpression();
725
+ if (
726
+ !Node.isCallExpression(defineConfigCall) ||
727
+ defineConfigCall.getExpression().getText() !== "defineConfig"
728
+ )
729
+ return;
730
+
731
+ let configObject = defineConfigCall.getArguments()[0];
732
+ if (!configObject) {
733
+ configObject = defineConfigCall.addArgument("{}");
734
+ }
735
+
736
+ if (Node.isObjectLiteralExpression(configObject)) {
737
+ const pluginsProperty = configObject.getProperty("plugins");
738
+ if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
739
+ const initializer = pluginsProperty.getInitializer();
740
+ if (Node.isArrayLiteralExpression(initializer)) {
741
+ // Check if cloudflare plugin is already configured
742
+ const hasCloudflarePlugin = initializer
743
+ .getElements()
744
+ .some((el) => el.getText().includes("cloudflare("));
745
+
746
+ if (!hasCloudflarePlugin) {
747
+ // Add cloudflare plugin
748
+ initializer.addElement(
749
+ 'cloudflare({ viteEnvironment: { name: "ssr" } })',
750
+ );
751
+ }
752
+ }
753
+ } else if (!pluginsProperty) {
754
+ // If no plugins property exists, create one with cloudflare plugin
755
+ configObject.addPropertyAssignment({
756
+ name: "plugins",
757
+ initializer: '[cloudflare({ viteEnvironment: { name: "ssr" } })]',
758
+ });
759
+ }
760
+ }
761
+
762
+ await project.save();
763
+ } catch (error) {
764
+ console.warn("Failed to update vite.config.ts:", error);
765
+ }
766
+ }
767
+
768
+ async function updateReactRouterConfigTS(context: InitContext): Promise<void> {
769
+ const configPath = resolve(context.cwd, "react-router.config.ts");
770
+ if (!(await fs.pathExists(configPath))) return;
771
+
772
+ try {
773
+ const project = new Project({
774
+ manipulationSettings: {
775
+ indentationText: IndentationText.TwoSpaces,
776
+ quoteKind: QuoteKind.Double,
777
+ },
778
+ });
779
+
780
+ project.addSourceFileAtPath(configPath);
781
+ const sourceFile = project.getSourceFileOrThrow(configPath);
782
+
783
+ // Find the default export
784
+ const exportAssignment = sourceFile.getExportAssignment(
785
+ (d) => !d.isExportEquals(),
786
+ );
787
+ if (!exportAssignment) return;
788
+
789
+ const configExpression = exportAssignment.getExpression();
790
+ let configObject: Node | undefined;
791
+
792
+ // Handle both direct object literal and satisfies expression
793
+ if (Node.isObjectLiteralExpression(configExpression)) {
794
+ configObject = configExpression;
795
+ } else if (Node.isSatisfiesExpression(configExpression)) {
796
+ const expression = configExpression.getExpression();
797
+ if (Node.isObjectLiteralExpression(expression)) {
798
+ configObject = expression;
799
+ }
800
+ }
801
+
802
+ if (!configObject || !Node.isObjectLiteralExpression(configObject)) return;
803
+
804
+ // Check if future property exists
805
+ let futureProperty = configObject.getProperty("future");
806
+
807
+ if (!futureProperty) {
808
+ // Add future property with unstable_viteEnvironmentApi
809
+ configObject.addPropertyAssignment({
810
+ name: "future",
811
+ initializer: `{
812
+ unstable_viteEnvironmentApi: true,
813
+ }`,
814
+ });
815
+ } else if (Node.isPropertyAssignment(futureProperty)) {
816
+ // Future property exists, check if it has unstable_viteEnvironmentApi
817
+ const futureInitializer = futureProperty.getInitializer();
818
+
819
+ if (Node.isObjectLiteralExpression(futureInitializer)) {
820
+ const viteEnvApiProp = futureInitializer.getProperty(
821
+ "unstable_viteEnvironmentApi",
822
+ );
823
+
824
+ if (!viteEnvApiProp) {
825
+ // Add unstable_viteEnvironmentApi
826
+ futureInitializer.addPropertyAssignment({
827
+ name: "unstable_viteEnvironmentApi",
828
+ initializer: "true",
829
+ });
830
+ } else if (Node.isPropertyAssignment(viteEnvApiProp)) {
831
+ // Check if it's false and update to true
832
+ const value = viteEnvApiProp.getInitializer()?.getText();
833
+ if (value === "false") {
834
+ viteEnvApiProp.setInitializer("true");
835
+ }
836
+ }
837
+ }
838
+ }
839
+
840
+ await project.save();
841
+ } catch (error) {
842
+ console.warn("Failed to update react-router.config.ts:", error);
843
+ }
844
+ }
845
+
846
+ async function updateTanStackStartProject(context: InitContext): Promise<void> {
847
+ await updateTanStackViteConfig(context);
848
+ await updateEnvFile(context);
849
+ }
850
+
851
+ async function updateSvelteConfig(context: InitContext): Promise<void> {
852
+ const svelteConfigPath = resolve(context.cwd, "svelte.config.js");
853
+ if (!(await fs.pathExists(svelteConfigPath))) return;
854
+
855
+ try {
856
+ const project = new Project({
857
+ manipulationSettings: {
858
+ indentationText: IndentationText.TwoSpaces,
859
+ quoteKind: QuoteKind.Single,
860
+ },
861
+ });
862
+
863
+ project.addSourceFileAtPath(svelteConfigPath);
864
+ const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
865
+
866
+ const importDeclarations = sourceFile.getImportDeclarations();
867
+ const adapterImport = importDeclarations.find((imp) =>
868
+ imp.getModuleSpecifierValue().includes("@sveltejs/adapter"),
869
+ );
870
+
871
+ if (adapterImport) {
872
+ adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
873
+ } else {
874
+ sourceFile.insertImportDeclaration(0, {
875
+ moduleSpecifier: "@sveltejs/adapter-cloudflare",
876
+ defaultImport: "adapter",
877
+ });
878
+ }
879
+
880
+ await project.save();
881
+ } catch (error) {
882
+ console.warn("Failed to update svelte.config.js:", error);
883
+ }
884
+ }
885
+
886
+ async function updateEnvFile(context: InitContext): Promise<void> {
887
+ const envPath = resolve(context.cwd, ".env");
888
+ await fs.ensureFile(envPath);
889
+
890
+ const envVars = ["ALCHEMY_PASSWORD=change-me"];
891
+ if (context.framework === "rwsdk") {
892
+ envVars.push("AUTH_SECRET_KEY=your-development-secret-key");
893
+ }
894
+
895
+ let envContent = "";
896
+ if (await fs.pathExists(envPath)) {
897
+ try {
898
+ envContent = await fs.readFile(envPath, "utf-8");
899
+ } catch (error) {
900
+ console.warn("Failed to read .env:", error);
901
+ }
902
+ }
903
+
904
+ let needsUpdate = false;
905
+ for (const envVar of envVars) {
906
+ const [key] = envVar.split("=");
907
+ if (!envContent.includes(`${key}=`)) {
908
+ if (envContent && !envContent.endsWith("\n")) {
909
+ envContent += "\n";
910
+ }
911
+ envContent += `${envVar}\n`;
912
+ needsUpdate = true;
913
+ }
914
+ }
915
+
916
+ if (needsUpdate) {
917
+ try {
918
+ await fs.writeFile(envPath, envContent, "utf-8");
919
+ } catch (error) {
920
+ console.warn("Failed to update .env:", error);
921
+ }
922
+ }
923
+ }
924
+
925
+ async function updateNuxtConfig(context: InitContext): Promise<void> {
926
+ const nuxtConfigPath = resolve(context.cwd, "nuxt.config.ts");
927
+ if (!(await fs.pathExists(nuxtConfigPath))) return;
928
+
929
+ try {
930
+ const project = new Project({
931
+ manipulationSettings: {
932
+ indentationText: IndentationText.TwoSpaces,
933
+ quoteKind: QuoteKind.Double,
934
+ },
935
+ });
936
+
937
+ project.addSourceFileAtPath(nuxtConfigPath);
938
+ const sourceFile = project.getSourceFileOrThrow(nuxtConfigPath);
939
+
940
+ const exportAssignment = sourceFile.getExportAssignment(
941
+ (d) => !d.isExportEquals(),
942
+ );
943
+ if (!exportAssignment) return;
944
+
945
+ const defineConfigCall = exportAssignment.getExpression();
946
+ if (
947
+ !Node.isCallExpression(defineConfigCall) ||
948
+ defineConfigCall.getExpression().getText() !== "defineNuxtConfig"
949
+ )
950
+ return;
951
+
952
+ let configObject = defineConfigCall.getArguments()[0];
953
+ if (!configObject) {
954
+ configObject = defineConfigCall.addArgument("{}");
955
+ }
956
+
957
+ if (Node.isObjectLiteralExpression(configObject)) {
958
+ if (!configObject.getProperty("nitro")) {
959
+ configObject.addPropertyAssignment({
960
+ name: "nitro",
961
+ initializer: `{
962
+ preset: "cloudflare_module",
963
+ cloudflare: {
964
+ deployConfig: true,
965
+ nodeCompat: true
966
+ }
967
+ }`,
968
+ });
969
+ }
970
+
971
+ const modulesProperty = configObject.getProperty("modules");
972
+ if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
973
+ const initializer = modulesProperty.getInitializer();
974
+ if (Node.isArrayLiteralExpression(initializer)) {
975
+ const hasModule = initializer
976
+ .getElements()
977
+ .some(
978
+ (el) =>
979
+ el.getText() === '"nitro-cloudflare-dev"' ||
980
+ el.getText() === "'nitro-cloudflare-dev'",
981
+ );
982
+ if (!hasModule) {
983
+ initializer.addElement('"nitro-cloudflare-dev"');
984
+ }
985
+ }
986
+ } else if (!modulesProperty) {
987
+ configObject.addPropertyAssignment({
988
+ name: "modules",
989
+ initializer: '["nitro-cloudflare-dev"]',
990
+ });
991
+ }
992
+ }
993
+
994
+ await project.save();
995
+ } catch (error) {
996
+ console.warn("Failed to update nuxt.config.ts:", error);
997
+ }
998
+ }
999
+
1000
+ async function updateAstroConfig(context: InitContext): Promise<void> {
1001
+ const astroConfigPath = resolve(context.cwd, "astro.config.mjs");
1002
+ if (!(await fs.pathExists(astroConfigPath))) return;
1003
+
1004
+ try {
1005
+ const project = new Project({
1006
+ manipulationSettings: {
1007
+ indentationText: IndentationText.TwoSpaces,
1008
+ quoteKind: QuoteKind.Double,
1009
+ },
1010
+ });
1011
+
1012
+ project.addSourceFileAtPath(astroConfigPath);
1013
+ const sourceFile = project.getSourceFileOrThrow(astroConfigPath);
1014
+
1015
+ sourceFile.addImportDeclaration({
1016
+ moduleSpecifier: "@astrojs/cloudflare",
1017
+ defaultImport: "cloudflare",
1018
+ });
1019
+
1020
+ const exportAssignment = sourceFile.getExportAssignment(
1021
+ (d) => !d.isExportEquals(),
1022
+ );
1023
+ if (!exportAssignment) return;
1024
+
1025
+ const defineConfigCall = exportAssignment.getExpression();
1026
+ if (
1027
+ !Node.isCallExpression(defineConfigCall) ||
1028
+ defineConfigCall.getExpression().getText() !== "defineConfig"
1029
+ )
1030
+ return;
1031
+
1032
+ let configObject = defineConfigCall.getArguments()[0];
1033
+ if (!configObject) {
1034
+ configObject = defineConfigCall.addArgument("{}");
1035
+ }
1036
+
1037
+ if (Node.isObjectLiteralExpression(configObject)) {
1038
+ if (!configObject.getProperty("output")) {
1039
+ configObject.addPropertyAssignment({
1040
+ name: "output",
1041
+ initializer: "'server'",
1042
+ });
1043
+ }
1044
+ if (!configObject.getProperty("adapter")) {
1045
+ configObject.addPropertyAssignment({
1046
+ name: "adapter",
1047
+ initializer: "cloudflare()",
1048
+ });
1049
+ }
1050
+ }
1051
+
1052
+ await project.save();
1053
+ } catch (error) {
1054
+ console.warn("Failed to update astro.config.mjs:", error);
1055
+ }
1056
+ }
1057
+
1058
+ async function updateTanStackViteConfig(context: InitContext): Promise<void> {
1059
+ const viteConfigPath = resolve(context.cwd, "vite.config.ts");
1060
+ if (!(await fs.pathExists(viteConfigPath))) return;
1061
+
1062
+ try {
1063
+ const project = new Project({
1064
+ manipulationSettings: {
1065
+ indentationText: IndentationText.TwoSpaces,
1066
+ quoteKind: QuoteKind.Double,
1067
+ },
1068
+ });
1069
+
1070
+ project.addSourceFileAtPath(viteConfigPath);
1071
+ const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
1072
+
1073
+ const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare");
1074
+ if (!alchemyImport) {
1075
+ sourceFile.addImportDeclaration({
1076
+ moduleSpecifier: "alchemy/cloudflare",
1077
+ namedImports: ["cloudflareWorkersDevEnvironmentShim"],
1078
+ });
1079
+ } else {
1080
+ const hasShim = alchemyImport
1081
+ .getNamedImports()
1082
+ .some((ni) => ni.getName() === "cloudflareWorkersDevEnvironmentShim");
1083
+ if (!hasShim) {
1084
+ alchemyImport.addNamedImport("cloudflareWorkersDevEnvironmentShim");
1085
+ }
1086
+ }
1087
+
1088
+ const exportAssignment = sourceFile.getExportAssignment(
1089
+ (d) => !d.isExportEquals(),
1090
+ );
1091
+ if (!exportAssignment) return;
1092
+
1093
+ const defineConfigCall = exportAssignment.getExpression();
1094
+ if (
1095
+ !Node.isCallExpression(defineConfigCall) ||
1096
+ defineConfigCall.getExpression().getText() !== "defineConfig"
1097
+ )
1098
+ return;
1099
+
1100
+ let configObject = defineConfigCall.getArguments()[0];
1101
+ if (!configObject) {
1102
+ configObject = defineConfigCall.addArgument("{}");
1103
+ }
1104
+
1105
+ if (Node.isObjectLiteralExpression(configObject)) {
1106
+ // Add build configuration
1107
+ if (!configObject.getProperty("build")) {
1108
+ configObject.addPropertyAssignment({
1109
+ name: "build",
1110
+ initializer: `{
1111
+ target: "esnext",
1112
+ rollupOptions: {
1113
+ external: ["node:async_hooks", "cloudflare:workers"],
1114
+ },
1115
+ }`,
1116
+ });
1117
+ }
1118
+
1119
+ const pluginsProperty = configObject.getProperty("plugins");
1120
+ if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
1121
+ const initializer = pluginsProperty.getInitializer();
1122
+ if (Node.isArrayLiteralExpression(initializer)) {
1123
+ const hasShim = initializer
1124
+ .getElements()
1125
+ .some((el) =>
1126
+ el.getText().includes("cloudflareWorkersDevEnvironmentShim"),
1127
+ );
1128
+ if (!hasShim) {
1129
+ initializer.addElement("cloudflareWorkersDevEnvironmentShim()");
1130
+ }
1131
+
1132
+ const tanstackElements = initializer
1133
+ .getElements()
1134
+ .filter((el) => el.getText().includes("tanstackStart"));
1135
+
1136
+ tanstackElements.forEach((element) => {
1137
+ if (Node.isCallExpression(element)) {
1138
+ const args = element.getArguments();
1139
+ if (args.length === 0) {
1140
+ element.addArgument(`{
1141
+ target: "cloudflare-module",
1142
+ customViteReactPlugin: true,
1143
+ }`);
1144
+ } else if (
1145
+ args.length === 1 &&
1146
+ Node.isObjectLiteralExpression(args[0])
1147
+ ) {
1148
+ const configObj = args[0];
1149
+ if (!configObj.getProperty("target")) {
1150
+ configObj.addPropertyAssignment({
1151
+ name: "target",
1152
+ initializer: '"cloudflare-module"',
1153
+ });
1154
+ }
1155
+ if (!configObj.getProperty("customViteReactPlugin")) {
1156
+ configObj.addPropertyAssignment({
1157
+ name: "customViteReactPlugin",
1158
+ initializer: "true",
1159
+ });
1160
+ }
1161
+ }
1162
+ }
1163
+ });
1164
+ }
1165
+ }
1166
+ }
1167
+
1168
+ await project.save();
1169
+ } catch (error) {
1170
+ console.warn("Failed to update vite.config.ts:", error);
1171
+ }
1172
+ }
1173
+
1174
+ function displaySuccessMessage(context: InitContext): void {
1175
+ const fileExtension = context.useTypeScript ? "ts" : "js";
1176
+ const runFile = `alchemy.run.${fileExtension}`;
1177
+
1178
+ note(`${pc.cyan("📁 Files created:")}
1179
+ ${runFile} - Your infrastructure configuration
1180
+
1181
+ ${pc.cyan("🚀 Next steps:")}
1182
+ Edit ${runFile} to configure your infrastructure
1183
+ Run ${pc.yellow(`${context.packageManager} run deploy`)} to deploy
1184
+ Run ${pc.yellow(`${context.packageManager} run destroy`)} to clean up
1185
+
1186
+ ${pc.cyan("📚 Learn more:")}
1187
+ https://alchemy.run`);
1188
+
1189
+ outro(pc.green("Alchemy initialized successfully!"));
1190
+ }