alchemy 0.64.0 → 0.65.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 (117) hide show
  1. package/bin/alchemy.js +7570 -5773
  2. package/bin/commands/init.ts +130 -9
  3. package/bin/constants.ts +4 -0
  4. package/bin/services/cdp-manager/cdp-proxy.ts +69 -0
  5. package/bin/services/cdp-manager/server.ts +173 -0
  6. package/bin/services/execute-alchemy.ts +83 -4
  7. package/bin/types.ts +1 -0
  8. package/lib/cloudflare/bundle/plugin-node-compat.js +2 -2
  9. package/lib/cloudflare/bundle/plugin-node-compat.js.map +1 -1
  10. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  11. package/lib/cloudflare/bundle/plugin-wasm.js +20 -11
  12. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  13. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  14. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  15. package/lib/cloudflare/hyperdrive.d.ts +7 -2
  16. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  17. package/lib/cloudflare/hyperdrive.js +39 -57
  18. package/lib/cloudflare/hyperdrive.js.map +1 -1
  19. package/lib/cloudflare/index.d.ts +1 -0
  20. package/lib/cloudflare/index.d.ts.map +1 -1
  21. package/lib/cloudflare/index.js +1 -0
  22. package/lib/cloudflare/index.js.map +1 -1
  23. package/lib/cloudflare/next.d.ts +13 -0
  24. package/lib/cloudflare/next.d.ts.map +1 -0
  25. package/lib/cloudflare/next.js +91 -0
  26. package/lib/cloudflare/next.js.map +1 -0
  27. package/lib/cloudflare/worker-assets.d.ts +3 -3
  28. package/lib/cloudflare/worker-assets.d.ts.map +1 -1
  29. package/lib/cloudflare/worker-assets.js +22 -47
  30. package/lib/cloudflare/worker-assets.js.map +1 -1
  31. package/lib/cloudflare/wrangler.json.d.ts +2 -2
  32. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  33. package/lib/cloudflare/wrangler.json.js +4 -1
  34. package/lib/cloudflare/wrangler.json.js.map +1 -1
  35. package/lib/planetscale/branch.d.ts.map +1 -1
  36. package/lib/planetscale/branch.js +36 -25
  37. package/lib/planetscale/branch.js.map +1 -1
  38. package/lib/planetscale/database.d.ts +23 -6
  39. package/lib/planetscale/database.d.ts.map +1 -1
  40. package/lib/planetscale/database.js +11 -5
  41. package/lib/planetscale/database.js.map +1 -1
  42. package/lib/planetscale/index.d.ts +1 -0
  43. package/lib/planetscale/index.d.ts.map +1 -1
  44. package/lib/planetscale/index.js +1 -0
  45. package/lib/planetscale/index.js.map +1 -1
  46. package/lib/planetscale/password.d.ts +3 -1
  47. package/lib/planetscale/password.d.ts.map +1 -1
  48. package/lib/planetscale/password.js +3 -1
  49. package/lib/planetscale/password.js.map +1 -1
  50. package/lib/planetscale/role.d.ts +126 -0
  51. package/lib/planetscale/role.d.ts.map +1 -0
  52. package/lib/planetscale/role.js +144 -0
  53. package/lib/planetscale/role.js.map +1 -0
  54. package/lib/planetscale/utils.d.ts +17 -3
  55. package/lib/planetscale/utils.d.ts.map +1 -1
  56. package/lib/planetscale/utils.js +110 -31
  57. package/lib/planetscale/utils.js.map +1 -1
  58. package/lib/secret.d.ts +0 -3
  59. package/lib/secret.d.ts.map +1 -1
  60. package/lib/secret.js +0 -2
  61. package/lib/secret.js.map +1 -1
  62. package/lib/state/cloudflare-state-store.js +3 -3
  63. package/lib/state/cloudflare-state-store.js.map +1 -1
  64. package/lib/test/bun.d.ts +2 -2
  65. package/lib/test/bun.d.ts.map +1 -1
  66. package/lib/test/vitest.d.ts +2 -2
  67. package/lib/test/vitest.d.ts.map +1 -1
  68. package/lib/test/vitest.js +4 -4
  69. package/lib/test/vitest.js.map +1 -1
  70. package/lib/util/content-type.js +2 -2
  71. package/lib/util/content-type.js.map +1 -1
  72. package/lib/util/dedent.d.ts.map +1 -1
  73. package/lib/util/dedent.js +30 -18
  74. package/lib/util/dedent.js.map +1 -1
  75. package/lib/util/memoize.d.ts +1 -1
  76. package/lib/util/memoize.d.ts.map +1 -1
  77. package/lib/util/memoize.js +13 -3
  78. package/lib/util/memoize.js.map +1 -1
  79. package/package.json +5 -1
  80. package/src/cloudflare/bundle/plugin-node-compat.ts +2 -2
  81. package/src/cloudflare/bundle/plugin-wasm.ts +34 -14
  82. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  83. package/src/cloudflare/hyperdrive.ts +136 -95
  84. package/src/cloudflare/index.ts +1 -0
  85. package/src/cloudflare/next.ts +123 -0
  86. package/src/cloudflare/worker-assets.ts +43 -105
  87. package/src/cloudflare/wrangler.json.ts +6 -3
  88. package/src/planetscale/branch.ts +52 -27
  89. package/src/planetscale/database.ts +75 -46
  90. package/src/planetscale/index.ts +1 -0
  91. package/src/planetscale/password.ts +3 -1
  92. package/src/planetscale/role.ts +278 -0
  93. package/src/planetscale/utils.ts +172 -30
  94. package/src/secret.ts +0 -4
  95. package/src/state/cloudflare-state-store.ts +4 -4
  96. package/src/test/bun.ts +2 -2
  97. package/src/test/vitest.ts +6 -6
  98. package/src/util/content-type.ts +2 -2
  99. package/src/util/dedent.ts +33 -21
  100. package/src/util/memoize.ts +15 -3
  101. package/templates/nextjs/README.md +85 -0
  102. package/templates/nextjs/_env +1 -0
  103. package/templates/nextjs/_env.example +1 -0
  104. package/templates/nextjs/_gitignore +46 -0
  105. package/templates/nextjs/alchemy.run.ts +16 -0
  106. package/templates/nextjs/eslint.config.mjs +16 -0
  107. package/templates/nextjs/next-env.d.ts +5 -0
  108. package/templates/nextjs/next.config.ts +10 -0
  109. package/templates/nextjs/open-next.config.ts +5 -0
  110. package/templates/nextjs/package.json +31 -0
  111. package/templates/nextjs/src/app/api/hello/route.ts +19 -0
  112. package/templates/nextjs/src/app/favicon.ico +0 -0
  113. package/templates/nextjs/src/app/globals.css +72 -0
  114. package/templates/nextjs/src/app/layout.tsx +19 -0
  115. package/templates/nextjs/src/app/page.tsx +55 -0
  116. package/templates/nextjs/tsconfig.json +33 -0
  117. package/templates/nextjs/types/env.d.ts +14 -0
@@ -146,6 +146,7 @@ async function createInitContext(options: {
146
146
  const FRAMEWORK_DETECTION_MAP: Record<string, TemplateType> = {
147
147
  rwsdk: "rwsdk",
148
148
  astro: "astro",
149
+ next: "nextjs",
149
150
  nuxt: "nuxt",
150
151
  "react-router": "react-router",
151
152
  "@sveltejs/kit": "sveltekit",
@@ -179,6 +180,7 @@ async function detectFramework(
179
180
  { label: "TanStack Start", value: "tanstack-start" },
180
181
  { label: "Redwood SDK", value: "rwsdk" },
181
182
  { label: "Nuxt.js", value: "nuxt" },
183
+ { label: "Next.js", value: "nextjs" },
182
184
  ] as const,
183
185
  initialValue: detectedFramework,
184
186
  });
@@ -234,7 +236,7 @@ async function checkExistingAlchemyFiles(context: InitContext): Promise<void> {
234
236
  }
235
237
 
236
238
  const ALCHEMY_RUN_TEMPLATES: Record<
237
- TemplateType,
239
+ Exclude<TemplateType, "hono">,
238
240
  (context: InitContext) => string
239
241
  > = {
240
242
  typescript: (context) => `/// <reference types="@types/node" />
@@ -400,6 +402,24 @@ console.log({
400
402
  url: worker.url,
401
403
  });
402
404
 
405
+ await app.finalize();
406
+ `,
407
+
408
+ nextjs: (context) => `/// <reference types="@types/node" />
409
+
410
+ import alchemy from "alchemy";
411
+ import { Nextjs } from "alchemy/cloudflare";
412
+
413
+ const app = await alchemy("${context.projectName}");
414
+
415
+ export const website = await Nextjs("website", {
416
+ name: \`\${app.name}-\${app.stage}-website\`,
417
+ });
418
+
419
+ console.log({
420
+ url: website.url,
421
+ });
422
+
403
423
  await app.finalize();
404
424
  `,
405
425
 
@@ -441,6 +461,8 @@ async function createAlchemyRunFile(context: InitContext): Promise<void> {
441
461
  }
442
462
 
443
463
  const FRAMEWORK_DEPENDENCIES: Record<TemplateType, DependencyVersionMap[]> = {
464
+ nextjs: ["alchemy", "@opennextjs/cloudflare", "sharp"],
465
+ hono: ["alchemy"],
444
466
  nuxt: ["alchemy", "nitro-cloudflare-dev"],
445
467
  sveltekit: ["alchemy", "@sveltejs/adapter-cloudflare"],
446
468
  typescript: ["alchemy"],
@@ -461,7 +483,7 @@ async function updatePackageJson(context: InitContext): Promise<void> {
461
483
  try {
462
484
  const devDependencies = FRAMEWORK_DEPENDENCIES[context.framework];
463
485
  await addPackageDependencies({
464
- devDependencies,
486
+ devDependencies: [...devDependencies, "@cloudflare/workers-types"],
465
487
  projectDir: context.cwd,
466
488
  });
467
489
 
@@ -494,16 +516,26 @@ async function updateGitignore(context: InitContext) {
494
516
  }
495
517
 
496
518
  const lines = gitignoreContent.split("\n").map((line) => line.trim());
497
- const hasAlchemy = lines.some(
498
- (line) => line === ".alchemy" || line === ".alchemy/",
499
- );
519
+ const hasDirectory = (dir: string) =>
520
+ lines.some((line) => line === dir || line === `${dir}/`);
500
521
 
501
- if (!hasAlchemy) {
502
- if (gitignoreContent && !gitignoreContent.endsWith("\n")) {
503
- gitignoreContent += "\n";
522
+ if (!hasDirectory(".alchemy")) {
523
+ lines.push("# alchemy", ".alchemy", "");
524
+ }
525
+
526
+ if (context.framework === "nextjs") {
527
+ if (!hasDirectory(".next")) {
528
+ lines.push("# next", ".next", "");
529
+ }
530
+ if (!hasDirectory(".open-next")) {
531
+ lines.push("# open-next", ".open-next", "");
532
+ }
533
+ if (!lines.some((line) => line === "wrangler.jsonc")) {
534
+ lines.push("# wrangler", "wrangler.jsonc", "");
504
535
  }
505
- await fs.appendFile(gitignorePath, ".alchemy\n", "utf-8");
506
536
  }
537
+
538
+ await fs.writeFile(gitignorePath, lines.join("\n"), "utf-8");
507
539
  } catch (error) {
508
540
  throwWithContext(error, "Failed to update .gitignore");
509
541
  }
@@ -564,6 +596,7 @@ async function updateProjectConfiguration(context: InitContext): Promise<{
564
596
  "tanstack-start": () => updateTanStackStartProject(context),
565
597
  rwsdk: () => updateRwsdkProject(context),
566
598
  nuxt: () => updateNuxtProject(context),
599
+ nextjs: () => updateNextjsProject(context),
567
600
  }[context.framework]();
568
601
  }
569
602
 
@@ -630,6 +663,94 @@ async function updateAstroProject(context: InitContext): Promise<void> {
630
663
  // });
631
664
  }
632
665
 
666
+ async function updateNextjsProject(context: InitContext): Promise<void> {
667
+ const resolveFile = async (name: string) => {
668
+ const candidates = ["ts", "js", "cjs", "mjs"].map((ext) =>
669
+ resolve(context.cwd, `${name}.${ext}`),
670
+ );
671
+ for (const candidate of candidates) {
672
+ if (await fs.pathExists(candidate)) {
673
+ return { path: candidate, exists: true };
674
+ }
675
+ }
676
+ return { path: candidates[0], exists: false };
677
+ };
678
+
679
+ const nextConfig = await resolveFile("next.config");
680
+ const openNextConfig = await resolveFile("open-next.config");
681
+
682
+ if (nextConfig.exists) {
683
+ const fileContent = await fs.readFile(nextConfig.path, "utf-8");
684
+ let updated = fileContent;
685
+ if (
686
+ !fileContent.includes(
687
+ "import { defineCloudflareConfig } from '@opennextjs/cloudflare'",
688
+ )
689
+ ) {
690
+ updated = `import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";\n${fileContent}`;
691
+ }
692
+ if (!fileContent.includes("initOpenNextCloudflareForDev()")) {
693
+ updated += "\ninitOpenNextCloudflareForDev();\n";
694
+ }
695
+ await fs.writeFile(nextConfig.path, updated);
696
+ } else {
697
+ await fs.writeFile(
698
+ nextConfig.path,
699
+ `import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
700
+ import type { NextConfig } from "next";
701
+
702
+ const nextConfig: NextConfig = {
703
+ /* config options here */
704
+ };
705
+
706
+ export default nextConfig;
707
+
708
+ initOpenNextCloudflareForDev();
709
+ `,
710
+ );
711
+ }
712
+
713
+ if (!openNextConfig.exists) {
714
+ await fs.writeFile(
715
+ openNextConfig.path,
716
+ `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
717
+
718
+ export default defineCloudflareConfig({
719
+ // Uncomment to enable R2 cache,
720
+ // It should be imported as:
721
+ // import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
722
+ // See https://opennext.js.org/cloudflare/caching for more details
723
+ // incrementalCache: r2IncrementalCache,
724
+ });
725
+ `,
726
+ );
727
+ }
728
+ await fs.writeFile(
729
+ resolve(context.cwd, "env.d.ts"),
730
+ `// Auto-generated Cloudflare binding types.
731
+ // @see https://alchemy.run/concepts/bindings/#type-safe-bindings
732
+
733
+ import type { website } from "./alchemy.run.ts";
734
+
735
+ declare global {
736
+ type CloudflareEnv = typeof website.Env;
737
+ }
738
+
739
+ declare module "cloudflare:workers" {
740
+ namespace Cloudflare {
741
+ export interface Env extends CloudflareEnv {}
742
+ }
743
+ }`,
744
+ );
745
+
746
+ const tsConfigPath = resolve(context.cwd, "tsconfig.json");
747
+ await updateTsConfig(tsConfigPath, {
748
+ compilerOptions: {
749
+ types: ["@cloudflare/workers-types", "env.d.ts"],
750
+ },
751
+ });
752
+ }
753
+
633
754
  async function updateReactRouterProject(context: InitContext): Promise<{
634
755
  main: string | undefined;
635
756
  }> {
package/bin/constants.ts CHANGED
@@ -24,6 +24,10 @@ export const dependencyVersionMap = {
24
24
  // nuxt
25
25
  "nitro-cloudflare-dev": "^0.2.2",
26
26
 
27
+ // nextjs
28
+ "@opennextjs/cloudflare": "^1.6.5",
29
+ sharp: "^0.34.3",
30
+
27
31
  // react-router
28
32
  "@cloudflare/vite-plugin": "^1.0.12",
29
33
 
@@ -0,0 +1,69 @@
1
+ import type { WebSocket as WsWebSocket } from "ws";
2
+ import { CDPServer } from "./server.ts";
3
+
4
+ export class CDPProxy extends CDPServer {
5
+ private inspectorUrl: string;
6
+ private inspectorWs?: WebSocket;
7
+ private onStartMessageQueue: Array<string>;
8
+
9
+ constructor(
10
+ inspectorUrl: string,
11
+ options: ConstructorParameters<typeof CDPServer>[0] & {
12
+ connect?: boolean;
13
+ },
14
+ ) {
15
+ super(options);
16
+ this.inspectorUrl = inspectorUrl;
17
+ this.onStartMessageQueue = [];
18
+ if (options.connect ?? true) {
19
+ this.start();
20
+ }
21
+ }
22
+
23
+ public async start(): Promise<void> {
24
+ this.inspectorWs = new WebSocket(this.inspectorUrl);
25
+
26
+ this.inspectorWs.addEventListener("open", async () => {
27
+ await new Promise((resolve) => setTimeout(resolve, 1000));
28
+ for (const message of this.onStartMessageQueue) {
29
+ this.internalHandleClientMessage(message);
30
+ }
31
+ });
32
+
33
+ this.inspectorWs.onmessage = async (event) => {
34
+ await this.handleInspectorMessage(event.data.toString());
35
+ };
36
+
37
+ this.inspectorWs.onclose = () => {
38
+ console.warn(`[${this.name}:Debug] Inspector closed`);
39
+ };
40
+
41
+ this.inspectorWs.onerror = (error) => {
42
+ console.error(`[${this.name}:Debug] Inspector errored:`, error);
43
+ };
44
+ }
45
+
46
+ async handleClientMessage(_ws: WsWebSocket, data: string): Promise<void> {
47
+ if (
48
+ this.inspectorWs == null ||
49
+ this.inspectorWs.readyState !== WebSocket.OPEN
50
+ ) {
51
+ if (this.onStartMessageQueue.length === 0) {
52
+ await this.start();
53
+ }
54
+ this.onStartMessageQueue.push(data);
55
+ return;
56
+ } else {
57
+ this.internalHandleClientMessage(data);
58
+ }
59
+ }
60
+
61
+ private async internalHandleClientMessage(data: string): Promise<void> {
62
+ const message = JSON.parse(data);
63
+ const messageDomain = message.method?.split(".")?.[0];
64
+ if (messageDomain != null && !this.domains.has(messageDomain)) {
65
+ return;
66
+ }
67
+ this.inspectorWs!.send(data);
68
+ }
69
+ }
@@ -0,0 +1,173 @@
1
+ import fs from "node:fs";
2
+ import { createServer, type Server } from "node:http";
3
+ import path from "pathe";
4
+ import { WebSocketServer, type WebSocket as WsWebSocket } from "ws";
5
+ import { findOpenPort } from "../../../src/util/find-open-port.ts";
6
+
7
+ export const LOGS_DIRECTORY = path.join(process.cwd(), ".alchemy", "logs");
8
+ const DEBUGGER_URLS_FILE = path.join(
9
+ process.cwd(),
10
+ ".alchemy",
11
+ ".debugger-urls",
12
+ );
13
+
14
+ export class CDPManager {
15
+ public readonly server: Server;
16
+ private url?: string;
17
+ private cdpServers: Map<string, CDPServer> = new Map();
18
+ private port?: number;
19
+
20
+ constructor() {
21
+ if (fs.existsSync(LOGS_DIRECTORY)) {
22
+ fs.rmSync(LOGS_DIRECTORY, { recursive: true, force: true });
23
+ }
24
+ fs.mkdirSync(LOGS_DIRECTORY, { recursive: true });
25
+
26
+ fs.writeFileSync(DEBUGGER_URLS_FILE, "");
27
+
28
+ this.server = createServer((req, res) => {
29
+ this.handleRequest(req, res);
30
+ });
31
+
32
+ this.server.on("upgrade", (request, socket, head) => {
33
+ this.handleUpgrade(request, socket, head);
34
+ });
35
+ }
36
+
37
+ public async startServer(): Promise<void> {
38
+ this.port = await findOpenPort(1336, 65535);
39
+ this.url = `http://localhost:${this.port}`;
40
+ await new Promise<void>((resolve) => {
41
+ this.server.listen(this.port, () => {
42
+ console.log(`[CDP-Manager] Debug server started at ${this.url}`);
43
+ resolve();
44
+ });
45
+ });
46
+ }
47
+
48
+ public getUrl(): string {
49
+ if (this.url == null) {
50
+ throw new Error("CDP manager not started");
51
+ }
52
+ return this.url;
53
+ }
54
+
55
+ private handleRequest(_req: any, res: any): void {
56
+ res.writeHead(404, { "Content-Type": "application/json" });
57
+ res.end(JSON.stringify({ error: "Not found" }));
58
+ }
59
+
60
+ private handleUpgrade(request: any, socket: any, head: any): void {
61
+ try {
62
+ const url = new URL(request.url, this.url);
63
+
64
+ const match = url.pathname.match(/^\/servers\/(.+)$/);
65
+ if (match) {
66
+ const serverName = match[1];
67
+
68
+ const cdpServer = this.cdpServers.get(serverName);
69
+
70
+ if (cdpServer) {
71
+ cdpServer.handleUpgrade(request, socket, head);
72
+ } else {
73
+ console.log(
74
+ `[CDP-Manager] CDP server ${serverName} not found, destroying socket. Available servers: ${Array.from(this.cdpServers.keys()).join(", ")}`,
75
+ );
76
+ socket.destroy();
77
+ }
78
+ } else {
79
+ socket.destroy();
80
+ }
81
+ } catch (error) {
82
+ console.log(`[CDP-Manager] Error handling upgrade: ${error}`);
83
+ socket.destroy();
84
+ }
85
+ }
86
+
87
+ public async registerCDPServer(server: CDPServer) {
88
+ this.cdpServers.set(server.name, server);
89
+ console.log(
90
+ `[CDP-Manager] CDP server ${server.name} registered. Available servers: ${Array.from(this.cdpServers.keys()).join(", ")}`,
91
+ );
92
+ await fs.promises.appendFile(
93
+ DEBUGGER_URLS_FILE,
94
+ `${server.name}=ws://localhost:${this.port}/servers/${server.name}\n`,
95
+ );
96
+ }
97
+
98
+ public close(): void {
99
+ this.server.close();
100
+ }
101
+ }
102
+
103
+ export abstract class CDPServer {
104
+ protected logFile: string;
105
+ protected domains: Set<string>;
106
+ public readonly name: string;
107
+ private wss: WebSocketServer;
108
+ //todo(michael): support multiple clients
109
+ private lastClient: WsWebSocket | null = null;
110
+
111
+ constructor(options: {
112
+ name: string;
113
+ server: Server;
114
+ logFile?: string;
115
+ domains?: Set<string>;
116
+ }) {
117
+ this.domains = options.domains ?? new Set(["Log", "Debugger"]);
118
+ this.logFile =
119
+ options.logFile ?? path.join(LOGS_DIRECTORY, `${options.name}.log`);
120
+ this.name = options.name;
121
+ fs.writeFileSync(this.logFile, "");
122
+ this.wss = new WebSocketServer({
123
+ noServer: true,
124
+ });
125
+
126
+ this.wss.on("connection", async (clientWs) => {
127
+ this.lastClient = clientWs;
128
+ clientWs.on("message", async (data) => {
129
+ await this.handleClientMessage(clientWs, data.toString());
130
+ });
131
+
132
+ clientWs.on("close", () => {});
133
+ });
134
+ }
135
+
136
+ protected async handleInspectorMessage(data: string) {
137
+ try {
138
+ const message = JSON.parse(data);
139
+ const messageDomain = message.method?.split(".")?.[0];
140
+ if (messageDomain != null && !this.domains.has(messageDomain)) {
141
+ return;
142
+ }
143
+
144
+ if (message.id == null) {
145
+ await fs.promises.appendFile(this.logFile, `${data}\n`);
146
+ }
147
+ if (this.lastClient != null) {
148
+ this.lastClient.send(data);
149
+ }
150
+ } catch (error) {
151
+ console.error(
152
+ `[${this.name}:Debug] Error handling inspector message:`,
153
+ error,
154
+ );
155
+ }
156
+ }
157
+
158
+ abstract handleClientMessage(ws: WsWebSocket, data: string): Promise<void>;
159
+
160
+ public handleUpgrade(request: any, socket: any, head: any): void {
161
+ try {
162
+ this.wss.handleUpgrade(request, socket, head, (ws) =>
163
+ this.wss.emit("connection", ws, request),
164
+ );
165
+ } catch (error) {
166
+ console.error(
167
+ `[${this.name}:Debug] Error during WebSocket upgrade:`,
168
+ error,
169
+ );
170
+ socket.destroy();
171
+ }
172
+ }
173
+ }
@@ -7,7 +7,10 @@ import z from "zod";
7
7
  import { detectRuntime } from "../../src/util/detect-node-runtime.ts";
8
8
  import { detectPackageManager } from "../../src/util/detect-package-manager.ts";
9
9
  import { exists } from "../../src/util/exists.ts";
10
+ import { promiseWithResolvers } from "../../src/util/promise-with-resolvers.ts";
10
11
  import { ExitSignal } from "../trpc.ts";
12
+ import { CDPProxy } from "./cdp-manager/cdp-proxy.ts";
13
+ import { CDPManager } from "./cdp-manager/server.ts";
11
14
 
12
15
  export const entrypoint = z
13
16
  .string()
@@ -50,6 +53,15 @@ export const execArgs = {
50
53
  .describe(
51
54
  "Specify which stage/environment to target. Defaults to your username ($USER, or $USERNAME on windows)",
52
55
  ),
56
+ inspect: z.boolean().optional().describe("Enable inspector"),
57
+ inspectBrk: z
58
+ .boolean()
59
+ .optional()
60
+ .describe("Enable inspector and break on start"),
61
+ inspectWait: z
62
+ .boolean()
63
+ .optional()
64
+ .describe("Enable inspector and wait for connection"),
53
65
  envFile: z
54
66
  .string()
55
67
  .optional()
@@ -69,6 +81,9 @@ export async function execAlchemy(
69
81
  envFile,
70
82
  read,
71
83
  dev,
84
+ inspect,
85
+ inspectBrk,
86
+ inspectWait,
72
87
  adopt,
73
88
  }: {
74
89
  cwd?: string;
@@ -81,11 +96,16 @@ export async function execAlchemy(
81
96
  read?: boolean;
82
97
  dev?: boolean;
83
98
  adopt?: boolean;
99
+ inspect?: boolean;
100
+ inspectBrk?: boolean;
101
+ inspectWait?: boolean;
84
102
  },
85
103
  ) {
86
104
  const args: string[] = [];
87
105
  const execArgs: string[] = [];
88
106
 
107
+ const shouldInspect = (inspect || inspectBrk || inspectWait) ?? false;
108
+
89
109
  if (quiet) args.push("--quiet");
90
110
  if (read) args.push("--read");
91
111
  if (force) args.push("--force");
@@ -99,11 +119,19 @@ export async function execAlchemy(
99
119
  execArgs.push(`--env-file ${envFile}`);
100
120
  }
101
121
  if (dev) args.push("--dev");
122
+ if (inspect) execArgs.push("--inspect");
123
+ if (inspectWait) execArgs.push("--inspect-wait");
124
+ if (inspectBrk) execArgs.push("--inspect-brk");
102
125
  if (adopt) args.push("--adopt");
103
126
 
104
127
  // Check for alchemy.run.ts or alchemy.run.js (if not provided)
105
128
  if (!main) {
106
- const candidates = ["alchemy.run.ts", "alchemy.run.js"];
129
+ const candidates = [
130
+ "alchemy.run.ts",
131
+ "alchemy.run.js",
132
+ "alchemy.run.mts",
133
+ "alchemy.run.mjs",
134
+ ];
107
135
  for (const file of candidates) {
108
136
  const resolved = resolve(cwd, file);
109
137
  if (await exists(resolved)) {
@@ -131,7 +159,7 @@ export async function execAlchemy(
131
159
  const execArgsString = execArgs.join(" ");
132
160
  // Determine the command to run based on package manager and file extension
133
161
  let command: string;
134
- const isTypeScript = main.endsWith(".ts");
162
+ const isTypeScript = main.endsWith("ts");
135
163
 
136
164
  switch (packageManager) {
137
165
  case "bun":
@@ -165,21 +193,72 @@ export async function execAlchemy(
165
193
  break;
166
194
  }
167
195
  }
196
+
197
+ const childRuntime = command.split(" ")[0];
198
+
199
+ const { promise: inspectorUrlPromise, resolve: resolveInspectorUrl } =
200
+ promiseWithResolvers<string>();
201
+
168
202
  process.on("SIGINT", async () => {
169
203
  await exitPromise;
170
204
  process.exit(sanitizeExitCode(child.exitCode));
171
205
  });
172
206
 
173
- console.log(command);
174
207
  const child = spawn(command, {
175
208
  cwd,
176
209
  shell: true,
177
- stdio: "inherit",
210
+ stdio: ["inherit", "inherit", "pipe"],
178
211
  env: {
179
212
  ...process.env,
180
213
  FORCE_COLOR: "1",
181
214
  },
182
215
  });
216
+
217
+ if (child.stderr) {
218
+ child.stderr.on("data", (data) => {
219
+ const string = data.toString();
220
+ //* bun inspector url seems to always be on 6499
221
+ //todo(michael): support node and deno
222
+ const bunInspectorMatch = string.match(
223
+ /ws:\/\/localhost:6499\/[a-zA-z0-9]*/,
224
+ );
225
+ const nodeInspectorMatch = string.match(
226
+ /ws:\/\/127.0.0.1:9229\/[a-zA-z0-9-]*/,
227
+ );
228
+ if (bunInspectorMatch) {
229
+ const inspectorUrl = bunInspectorMatch[0];
230
+ resolveInspectorUrl(inspectorUrl);
231
+ } else if (nodeInspectorMatch) {
232
+ const inspectorUrl = nodeInspectorMatch[0];
233
+ resolveInspectorUrl(inspectorUrl);
234
+ }
235
+ process.stderr.write(data);
236
+ });
237
+ }
238
+
239
+ if (shouldInspect) {
240
+ const inspectorUrl = await inspectorUrlPromise;
241
+ //* we await to make sure bun has finished printing so we don't cut if off
242
+ if (childRuntime === "bun") {
243
+ await new Promise((resolve) => setTimeout(resolve, 100));
244
+ }
245
+ const cdpManager = new CDPManager();
246
+ await cdpManager.startServer();
247
+ const rootCDPProxy = new CDPProxy(inspectorUrl, {
248
+ name: "alchemy.run.ts",
249
+ server: cdpManager.server,
250
+ connect: inspectWait || inspectBrk,
251
+ domains:
252
+ childRuntime === "bun"
253
+ ? new Set(["Inspector", "Console", "Runtime", "Debugger", "Heap"])
254
+ : new Set(["Runtime", "Debugger", "Profiler", "Log"]),
255
+ });
256
+ await cdpManager.registerCDPServer(rootCDPProxy);
257
+ if (inspectWait || inspectBrk) {
258
+ console.log("Waiting for inspector to connect....");
259
+ }
260
+ }
261
+
183
262
  const exitPromise = once(child, "exit");
184
263
  await exitPromise.catch(() => {});
185
264
  process.exit(sanitizeExitCode(child.exitCode));
package/bin/types.ts CHANGED
@@ -8,6 +8,7 @@ export const TEMPLATE_DEFINITIONS = [
8
8
  { name: "sveltekit", description: "SvelteKit" },
9
9
  { name: "tanstack-start", description: "TanStack Start" },
10
10
  { name: "rwsdk", description: "Redwood SDK" },
11
+ { name: "nextjs", description: "Next.js" },
11
12
  { name: "nuxt", description: "Nuxt.js" },
12
13
  { name: "typescript", description: "TypeScript Worker" },
13
14
  ] as const;
@@ -1,5 +1,5 @@
1
1
  import { relative } from "node:path";
2
- import chalk from "picocolors";
2
+ import pc from "picocolors";
3
3
  import { dedent } from "../../util/dedent.js";
4
4
  import { logger } from "../../util/logger.js";
5
5
  import { NODEJS_MODULES_RE } from "./nodejs-builtin-modules.js";
@@ -87,7 +87,7 @@ export const esbuildPluginNodeCompatImports = (mode) => ({
87
87
  });
88
88
  function toList(items, absWorkingDir) {
89
89
  return items
90
- .map((i) => ` - ${chalk.blue(relative(absWorkingDir ?? "/", i))}`)
90
+ .map((i) => ` - ${pc.blue(relative(absWorkingDir ?? "/", i))}`)
91
91
  .join("\n");
92
92
  }
93
93
  //# sourceMappingURL=plugin-node-compat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-node-compat.js","sourceRoot":"","sources":["../../../src/cloudflare/bundle/plugin-node-compat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-node-compat.js","sourceRoot":"","sources":["../../../src/cloudflare/bundle/plugin-node-compat.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-wasm.d.ts","sourceRoot":"","sources":["../../../src/cloudflare/bundle/plugin-wasm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,gBAAgB;;;EA2B/B"}
1
+ {"version":3,"file":"plugin-wasm.d.ts","sourceRoot":"","sources":["../../../src/cloudflare/bundle/plugin-wasm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,gBAAgB;;;EA+C/B"}