Skip to content

Commit 39e1408

Browse files
[wrangler] make sure that the ready-on message is printed after the local runtime is ready
1 parent 670b364 commit 39e1408

File tree

9 files changed

+131
-28
lines changed

9 files changed

+131
-28
lines changed

.changeset/fluffy-seals-arrive.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
make sure that the ready-on message is printed after the local runtime is ready
6+
7+
fix the fact that when starting a local dev session the log saying `Ready on http://localhost:xxxx` could be displayed before the local runtime is actually ready to handle requests (this is quite noticeable when running dev sessions with containers, where the ready message currently gets displayed before the container images building/pulling process)

fixtures/interactive-dev-tests/tests/index.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,10 @@ baseDescribe.skipIf(process.platform !== "linux" && process.env.CI === "true")(
493493
});
494494

495495
it("should allow quitting while the image is building", async () => {
496-
const wrangler = await startWranglerDev([
497-
"dev",
498-
"-c",
499-
path.join(tmpDir, "wrangler.jsonc"),
500-
]);
496+
const wrangler = await startWranglerDev(
497+
["dev", "-c", path.join(tmpDir, "wrangler.jsonc")],
498+
true
499+
);
501500

502501
const waitForOptions = {
503502
timeout: 10_000,
@@ -517,11 +516,10 @@ baseDescribe.skipIf(process.platform !== "linux" && process.env.CI === "true")(
517516
});
518517

519518
it("should rebuilding while the image is building", async () => {
520-
const wrangler = await startWranglerDev([
521-
"dev",
522-
"-c",
523-
path.join(tmpDir, "wrangler.jsonc"),
524-
]);
519+
const wrangler = await startWranglerDev(
520+
["dev", "-c", path.join(tmpDir, "wrangler.jsonc")],
521+
true
522+
);
525523

526524
const waitForOptions = {
527525
timeout: 15_000,

fixtures/pages-workerjs-app/tests/index.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { rename } from "node:fs/promises";
33
import path, { resolve } from "node:path";
44
import { setTimeout } from "node:timers/promises";
55
import { fetch } from "undici";
6-
import { describe, it } from "vitest";
6+
import { describe, it, vi } from "vitest";
77
import { runWranglerPagesDev } from "../../shared/src/run-wrangler-long-lived";
88

99
describe("Pages _worker.js", () => {
@@ -72,6 +72,15 @@ describe("Pages _worker.js", () => {
7272
"--port=0",
7373
"--inspector-port=0",
7474
]);
75+
vi.waitFor(
76+
() => {
77+
expect(getOutput()).toContain("Ready on");
78+
},
79+
{
80+
timeout: 5_000,
81+
}
82+
);
83+
await setTimeout(200);
7584
try {
7685
clearOutput();
7786
await tryRename(
@@ -109,6 +118,15 @@ describe("Pages _worker.js", () => {
109118
"--port=0",
110119
"--inspector-port=0",
111120
]);
121+
vi.waitFor(
122+
() => {
123+
expect(getOutput()).toContain("Ready on");
124+
},
125+
{
126+
timeout: 5_000,
127+
}
128+
);
129+
await setTimeout(200);
112130
try {
113131
clearOutput();
114132
await tryRename(

packages/wrangler/e2e/containers.dev.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,31 @@ describe
189189
}
190190
// from miniflare output:
191191
await worker.readUntil(/Container image\(s\) ready/);
192+
await worker.stop();
193+
});
194+
195+
it("will display the ready-on message after the container(s) have been built/pulled", async () => {
196+
const worker = helper.runLongLived("wrangler dev");
197+
const readyRegexp = /Ready on (http:\/\/[a-z0-9.]+:[0-9]+)/;
198+
await worker.readUntil(readyRegexp);
199+
200+
await worker.stop();
201+
202+
const fullOutput = await worker.output;
203+
const indexOfContainersReadyMessage = fullOutput.indexOf(
204+
"Container image(s) ready"
205+
);
206+
207+
const indexOfReadyOnMessage = fullOutput.indexOf("Ready on");
208+
expect(indexOfReadyOnMessage).toBeGreaterThan(
209+
indexOfContainersReadyMessage
210+
);
192211
});
193212

194213
it(`will be able to interact with the container`, async () => {
195214
const worker = helper.runLongLived("wrangler dev");
196-
const ready = await worker.waitForReady();
197215
await worker.readUntil(/Container image\(s\) ready/);
216+
const ready = await worker.waitForReady();
198217

199218
let response = await fetch(`${ready.url}/status`);
200219
expect(response.status).toBe(200);

packages/wrangler/e2e/dev-remote-bindings.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,14 @@ describe.skipIf(!CLOUDFLARE_ACCOUNT_ID)(
189189

190190
// This should only include logs from the user Wrangler session (i.e. a single list of attached bindings, and only one ready message)
191191
expect(normalizeOutput(worker.currentOutput)).toMatchInlineSnapshot(`
192-
"Your Worker has access to the following bindings:
193-
Binding Resource Mode
194-
env.AI AI remote
195-
[wrangler:info] Ready on http://<HOST>:<PORT>
196-
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`experimental_remote: true\` for the binding definition in your configuration file.
197-
⎔ Starting local server...
198-
[wrangler:info] GET / 200 OK (TIMINGS)"
199-
`);
192+
"Your Worker has access to the following bindings:
193+
Binding Resource Mode
194+
env.AI AI remote
195+
▲ [WARNING] AI bindings always access remote resources, and so may incur usage charges even in local dev. To suppress this warning, set \`experimental_remote: true\` for the binding definition in your configuration file.
196+
⎔ Starting local server...
197+
[wrangler:info] Ready on http://<HOST>:<PORT>
198+
[wrangler:info] GET / 200 OK (TIMINGS)"
199+
`);
200200
});
201201

202202
describe("shows helpful error logs", () => {

packages/wrangler/e2e/helpers/wrangler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class WranglerLongLivedCommand extends LongLivedCommand {
3737
super(getWranglerCommand(wranglerCommand), getOptions(options));
3838
}
3939

40-
async waitForReady(readTimeout = 5_000): Promise<{ url: string }> {
40+
async waitForReady(readTimeout = 15_000): Promise<{ url: string }> {
4141
const match = await this.readUntil(
4242
/Ready on (?<url>https?:\/\/.*)/,
4343
readTimeout

packages/wrangler/src/api/startDevWorker/DevEnv.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export class DevEnv extends EventEmitter {
7777
});
7878
});
7979

80+
this.resolveProxyLocalServerReady().catch(() => {});
81+
8082
runtimes.forEach((runtime) => {
8183
runtime.on("reloadStart", (event) => {
8284
proxy.onReloadStart(event);
@@ -93,6 +95,29 @@ export class DevEnv extends EventEmitter {
9395
});
9496
}
9597

98+
/**
99+
* Awaits for all the local runtime controllers to be ready, and when they are
100+
* it resolves a promise on the proxy controller to let it know that the
101+
* local server is now ready to handle requests
102+
*/
103+
private async resolveProxyLocalServerReady(): Promise<void> {
104+
await Promise.all(
105+
this.runtimes
106+
.filter(
107+
(runtime) =>
108+
runtime instanceof RemoteRuntimeController ||
109+
runtime instanceof LocalRuntimeController
110+
)
111+
.map((runtime) => {
112+
return new Promise<void>((resolve) => {
113+
runtime.once("reloadComplete", () => resolve());
114+
});
115+
})
116+
);
117+
118+
this.proxy.localServerReady.resolve();
119+
}
120+
96121
// *********************
97122
// Event Dispatchers
98123
// *********************

packages/wrangler/src/api/startDevWorker/ProxyController.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import type {
4141
} from "./events";
4242
import type { StartDevWorkerOptions } from "./types";
4343
import type { DeferredPromise } from "./utils";
44-
import type { MiniflareOptions } from "miniflare";
44+
import type { LogOptions, MiniflareOptions } from "miniflare";
4545

4646
type ProxyControllerEventMap = ControllerEventMap & {
4747
ready: [ReadyEvent];
@@ -50,6 +50,8 @@ type ProxyControllerEventMap = ControllerEventMap & {
5050
export class ProxyController extends Controller<ProxyControllerEventMap> {
5151
public ready = createDeferred<ReadyEvent>();
5252

53+
public localServerReady = createDeferred<void>();
54+
5355
public proxyWorker?: Miniflare;
5456
proxyWorkerOptions?: MiniflareOptions;
5557
private inspectorProxyWorkerWebSocket?: DeferredPromise<WebSocket>;
@@ -126,11 +128,17 @@ export class ProxyController extends Controller<ProxyControllerEventMap> {
126128
verbose: logger.loggerLevel === "debug",
127129

128130
// log requests into the ProxyWorker (for local + remote mode)
129-
log: new ProxyControllerLogger(castLogLevel(logger.loggerLevel), {
130-
prefix:
131-
// if debugging, log requests with specic ProxyWorker prefix
132-
logger.loggerLevel === "debug" ? "wrangler-ProxyWorker" : "wrangler",
133-
}),
131+
log: new ProxyControllerLogger(
132+
castLogLevel(logger.loggerLevel),
133+
{
134+
prefix:
135+
// if debugging, log requests with specic ProxyWorker prefix
136+
logger.loggerLevel === "debug"
137+
? "wrangler-ProxyWorker"
138+
: "wrangler",
139+
},
140+
this.localServerReady.promise
141+
),
134142
handleRuntimeStdio,
135143
liveReload: false,
136144
};
@@ -606,6 +614,18 @@ export class ProxyController extends Controller<ProxyControllerEventMap> {
606614
}
607615

608616
class ProxyControllerLogger extends WranglerLog {
617+
constructor(
618+
level: LogLevel,
619+
opts: LogOptions,
620+
private localServerReady: Promise<void>
621+
) {
622+
super(level, opts);
623+
}
624+
625+
logReady(message: string): void {
626+
this.localServerReady.then(() => super.logReady(message)).catch(() => {});
627+
}
628+
609629
log(message: string) {
610630
// filter out request logs being handled by the ProxyWorker
611631
// the requests log remaining are handled by the UserWorker

packages/wrangler/src/dev.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ import path from "node:path";
55
import util from "node:util";
66
import { isWebContainer } from "@webcontainer/env";
77
import { DevEnv } from "./api";
8+
import { BundlerController } from "./api/startDevWorker/BundlerController";
9+
import { ConfigController } from "./api/startDevWorker/ConfigController";
10+
import { LocalRuntimeController } from "./api/startDevWorker/LocalRuntimeController";
811
import { MultiworkerRuntimeController } from "./api/startDevWorker/MultiworkerRuntimeController";
912
import { NoOpProxyController } from "./api/startDevWorker/NoOpProxyController";
13+
import { ProxyController } from "./api/startDevWorker/ProxyController";
14+
import { RemoteRuntimeController } from "./api/startDevWorker/RemoteRuntimeController";
1015
import {
1116
convertCfWorkerInitBindingsToBindings,
1217
extractBindingsOfType,
@@ -38,6 +43,7 @@ import type {
3843
StartDevWorkerInput,
3944
Trigger,
4045
} from "./api";
46+
import type { RuntimeController } from "./api/startDevWorker/BaseController";
4147
import type { Config, Environment } from "./config";
4248
import type {
4349
EnvironmentNonInheritable,
@@ -670,7 +676,17 @@ export async function startDev(args: StartDevOptions) {
670676
unregisterHotKeys = registerDevHotKeys(primaryDevEnv, args);
671677
}
672678
} else {
673-
devEnv = new DevEnv();
679+
devEnv = new DevEnv({
680+
config: new ConfigController(),
681+
bundler: new BundlerController(),
682+
runtimes: [
683+
...(args.remote
684+
? [new RemoteRuntimeController()]
685+
: [new LocalRuntimeController()]),
686+
// new RemoteRuntimeController(),
687+
] as RuntimeController[],
688+
proxy: new ProxyController(),
689+
});
674690

675691
// The ProxyWorker will have a stable host and port, so only listen for the first update
676692
void devEnv.proxy.ready.promise.then(({ url }) => {

0 commit comments

Comments
 (0)