Skip to content

Commit 20b7527

Browse files
authored
Merge 2ba7120 into c4d7a9e
2 parents c4d7a9e + 2ba7120 commit 20b7527

File tree

10 files changed

+491
-2
lines changed

10 files changed

+491
-2
lines changed

alchemy/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
"libsodium-wrappers": "^0.7.15",
147147
"turndown": "^7.0.0",
148148
"unenv": "2.0.0-rc.15",
149+
"ws": "^8.18.2",
149150
"yaml": "^2.0.0"
150151
},
151152
"peerDependencies": {
@@ -189,6 +190,7 @@
189190
"@types/libsodium-wrappers": "^0.7.14",
190191
"@types/node": "latest",
191192
"@types/turndown": "^5.0.5",
193+
"@types/ws": "^8.18.1",
192194
"ai": "^4.1.16",
193195
"alchemy": "^0.37.1",
194196
"arktype": "^2.1.16",

alchemy/src/alchemy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,12 @@ export interface AlchemyOptions {
394394
* If not provided, the default fallback logger will be used.
395395
*/
396396
logger?: LoggerApi;
397+
/**
398+
* Whether to persist runtime logs to a file.
399+
*
400+
* @default true
401+
*/
402+
persistLogs?: boolean;
397403
}
398404

399405
export interface ScopeOptions extends AlchemyOptions {

alchemy/src/cloudflare/worker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ export interface BaseWorkerProps<
334334
* Port to use for local development
335335
*/
336336
port?: number;
337+
/**
338+
* Whether to log to the console.
339+
*
340+
* @default true
341+
*/
342+
logToConsole?: boolean;
337343
/**
338344
* EXPERIMENTAL: Whether to run the worker remotely instead of locally.
339345
*
@@ -1035,6 +1041,10 @@ export const _Worker = Resource(
10351041
compatibilityFlags,
10361042
bindings: props.bindings ?? ({} as B),
10371043
port: typeof props.dev === "object" ? props.dev.port : undefined,
1044+
logToConsole:
1045+
typeof props.dev === "object"
1046+
? (props.dev?.logToConsole ?? true)
1047+
: true,
10381048
};
10391049

10401050
let url: string;

alchemy/src/cloudflare/worker/miniflare-worker-options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type MiniflareWorkerOptions = Pick<
2020
name: string;
2121
script: string;
2222
port?: number;
23+
logToConsole: boolean;
2324
};
2425

2526
type BindingType = Exclude<Binding, string | Self>["type"];
@@ -225,6 +226,7 @@ export function buildMiniflareWorkerOptions({
225226
compatibilityDate,
226227
compatibilityFlags,
227228
unsafeDirectSockets: [{ entrypoint: undefined, proxy: true }],
229+
unsafeInspectorProxy: true,
228230
containerEngine: {
229231
localDocker: {
230232
socketPath:

alchemy/src/cloudflare/worker/miniflare.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type WorkerOptions,
77
} from "miniflare";
88
import path from "node:path";
9+
import { InspectorProxy } from "../../util/chrome-devtools/inspector-proxy.ts";
910
import { findOpenPort } from "../../util/find-open-port.ts";
1011
import { logger } from "../../util/logger.ts";
1112
import {
@@ -25,6 +26,8 @@ class MiniflareServer {
2526
workers = new Map<string, WorkerOptions>();
2627
servers = new Map<string, HTTPServer>();
2728
mixedModeProxies = new Map<string, MixedModeProxy>();
29+
inspectorPort?: number;
30+
inspectorProxies: Map<string, InspectorProxy> = new Map();
2831

2932
stream = new WritableStream<{
3033
worker: MiniflareWorkerOptions;
@@ -70,6 +73,10 @@ class MiniflareServer {
7073
await withErrorRewrite(
7174
this.miniflare.setOptions(await this.miniflareOptions()),
7275
);
76+
const inspectorProxy = this.inspectorProxies.get(worker.name);
77+
if (inspectorProxy) {
78+
await inspectorProxy.reconnect();
79+
}
7380
} else {
7481
const { Miniflare } = await import("miniflare").catch(() => {
7582
throw new Error(
@@ -95,6 +102,15 @@ class MiniflareServer {
95102
port: worker.port ?? (await findOpenPort()),
96103
fetch: this.createRequestHandler(worker.name as string),
97104
});
105+
const inspectorProxy = new InspectorProxy(
106+
server.server,
107+
`ws://localhost:${this.inspectorPort}/${worker.name}`,
108+
{
109+
consoleIdentifier: worker.logToConsole ? worker.name : undefined,
110+
},
111+
);
112+
this.inspectorProxies.set(worker.name, inspectorProxy);
113+
98114
this.servers.set(worker.name, server);
99115
await server.ready;
100116
return server;
@@ -139,6 +155,23 @@ class MiniflareServer {
139155
private createRequestHandler(name: string) {
140156
return async (req: Request) => {
141157
try {
158+
const url = new URL(req.url);
159+
const subdomain = url.hostname.split(".")[0];
160+
if (subdomain === "inspect") {
161+
if (url.pathname === "/" && url.searchParams.get("ws") == null) {
162+
return Response.redirect(
163+
`http://inspect.localhost:${url.port}?ws=localhost:${url.port}`,
164+
302,
165+
);
166+
}
167+
const app = await fetch(
168+
`http://devtools.devprod.cloudflare.dev/${url.pathname === "/" ? "js_app" : url.pathname}`,
169+
);
170+
app.headers.delete("content-encoding");
171+
app.headers.delete("content-length");
172+
return app;
173+
}
174+
142175
if (!this.miniflare) {
143176
return new Response(
144177
"[Alchemy] Miniflare is not initialized. Please try again.",
@@ -177,7 +210,8 @@ class MiniflareServer {
177210

178211
private async miniflareOptions(): Promise<MiniflareOptions> {
179212
const { getDefaultDevRegistryPath } = await import("miniflare");
180-
return {
213+
this.inspectorPort = this.inspectorPort ?? (await findOpenPort());
214+
const options = {
181215
workers: Array.from(this.workers.values()),
182216
defaultPersistRoot: path.join(process.cwd(), ".alchemy/miniflare"),
183217
unsafeDevRegistryPath: getDefaultDevRegistryPath(),
@@ -189,7 +223,10 @@ class MiniflareServer {
189223
r2Persist: true,
190224
secretsStorePersist: true,
191225
workflowsPersist: true,
226+
inspectorPort: this.inspectorPort,
227+
handleRuntimeStdio: () => {},
192228
};
229+
return options;
193230
}
194231
}
195232

alchemy/src/scope.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface ScopeOptions {
4141
dev?: boolean;
4242
telemetryClient?: ITelemetryClient;
4343
logger?: LoggerApi;
44+
persistLogs?: boolean;
4445
}
4546

4647
export type PendingDeletions = Array<{
@@ -111,6 +112,7 @@ export class Scope {
111112
public readonly logger: LoggerApi;
112113
public readonly telemetryClient: ITelemetryClient;
113114
public readonly dataMutex: AsyncMutex;
115+
public readonly persistLogs: boolean;
114116

115117
private isErrored = false;
116118
private finalized = false;
@@ -184,6 +186,7 @@ export class Scope {
184186
this.telemetryClient =
185187
options.telemetryClient ?? this.parent?.telemetryClient!;
186188
this.dataMutex = new AsyncMutex();
189+
this.persistLogs = options.persistLogs ?? this.parent?.persistLogs ?? true;
187190
}
188191

189192
public get root(): Scope {

alchemy/src/test/vitest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export function test(
145145
phase: "up",
146146
telemetryClient: new NoopTelemetryClient(),
147147
quiet: defaultOptions.quiet,
148+
persistLogs: false,
148149
});
149150

150151
test.beforeAll = (fn: (scope: Scope) => Promise<void>) => {

0 commit comments

Comments
 (0)