wrangler 2.1.3 → 2.1.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -1,4 +1,7 @@
1
+ import * as fs from "node:fs";
2
+ import { Request } from "undici";
1
3
  import { unstable_dev } from "../api";
4
+ import { runInTempDir } from "./helpers/run-in-tmp";
2
5
 
3
6
  jest.unmock("undici");
4
7
 
@@ -17,3 +20,170 @@ describe("unstable_dev", () => {
17
20
  await worker.stop();
18
21
  });
19
22
  });
23
+
24
+ describe("unstable dev fetch input protocol", () => {
25
+ it("should use http localProtocol", async () => {
26
+ const worker = await unstable_dev(
27
+ "src/__tests__/helpers/hello-world-worker.js",
28
+ { localProtocol: "http" },
29
+ { disableExperimentalWarning: true }
30
+ );
31
+ const res = await worker.fetch();
32
+ if (res) {
33
+ const text = await res.text();
34
+ expect(text).toMatchInlineSnapshot(`"Hello World!"`);
35
+ }
36
+ await worker.stop();
37
+ });
38
+
39
+ it("should use undefined localProtocol", async () => {
40
+ const worker = await unstable_dev(
41
+ "src/__tests__/helpers/hello-world-worker.js",
42
+ { localProtocol: undefined },
43
+ { disableExperimentalWarning: true }
44
+ );
45
+ const res = await worker.fetch();
46
+ if (res) {
47
+ const text = await res.text();
48
+ expect(text).toMatchInlineSnapshot(`"Hello World!"`);
49
+ }
50
+ await worker.stop();
51
+ });
52
+ });
53
+
54
+ describe("unstable dev fetch input parsing", () => {
55
+ runInTempDir();
56
+
57
+ it("should pass in a request object unchanged", async () => {
58
+ const scriptContent = `
59
+ export default {
60
+ fetch(request, env, ctx) {
61
+ const url = new URL(request.url);
62
+ if (url.pathname === "/test") {
63
+ if (request.method === "POST") {
64
+ return new Response("requestPOST");
65
+ }
66
+ return new Response("requestGET");
67
+ }
68
+ return new Response('Hello world');
69
+ }
70
+ };
71
+ `;
72
+ fs.writeFileSync("index.js", scriptContent);
73
+ const port = 21213;
74
+ const worker = await unstable_dev(
75
+ "index.js",
76
+ { port },
77
+ { disableExperimentalWarning: true }
78
+ );
79
+ const req = new Request("http://0.0.0.0:21213/test", {
80
+ method: "POST",
81
+ });
82
+ const resp = await worker.fetch(req);
83
+ let text;
84
+ if (resp) text = await resp.text();
85
+ expect(text).toMatchInlineSnapshot(`"requestPOST"`);
86
+ await worker.stop();
87
+ });
88
+
89
+ it("should strip back to pathname for URL objects", async () => {
90
+ const scriptContent = `
91
+ export default {
92
+ fetch(request, env, ctx) {
93
+ const url = new URL(request.url);
94
+ if (url.pathname === "/test") {
95
+ return new Response("request");
96
+ }
97
+ return new Response('Hello world');
98
+ }
99
+ };
100
+ `;
101
+ fs.writeFileSync("index.js", scriptContent);
102
+ const worker = await unstable_dev(
103
+ "index.js",
104
+ {},
105
+ { disableExperimentalWarning: true }
106
+ );
107
+ const url = new URL("http://localhost:80/test");
108
+ const resp = await worker.fetch(url);
109
+ let text;
110
+ if (resp) text = await resp.text();
111
+ expect(text).toMatchInlineSnapshot(`"request"`);
112
+ await worker.stop();
113
+ });
114
+
115
+ it("should allow full url passed in string, and stripped back to pathname", async () => {
116
+ const scriptContent = `
117
+ export default {
118
+ fetch(request, env, ctx) {
119
+ const url = new URL(request.url);
120
+ if (url.pathname === "/test") {
121
+ return new Response("request");
122
+ }
123
+ return new Response('Hello world');
124
+ }
125
+ };
126
+ `;
127
+ fs.writeFileSync("index.js", scriptContent);
128
+ const worker = await unstable_dev(
129
+ "index.js",
130
+ {},
131
+ { disableExperimentalWarning: true }
132
+ );
133
+ const resp = await worker.fetch("http://example.com/test");
134
+ let text;
135
+ if (resp) text = await resp.text();
136
+ expect(text).toMatchInlineSnapshot(`"request"`);
137
+ await worker.stop();
138
+ });
139
+
140
+ it("should allow pathname to be passed in", async () => {
141
+ const scriptContent = `
142
+ export default {
143
+ fetch(request, env, ctx) {
144
+ const url = new URL(request.url);
145
+ if (url.pathname === "/test") {
146
+ return new Response("request");
147
+ }
148
+ return new Response('Hello world');
149
+ }
150
+ };
151
+ `;
152
+ fs.writeFileSync("index.js", scriptContent);
153
+ const worker = await unstable_dev(
154
+ "index.js",
155
+ {},
156
+ { disableExperimentalWarning: true }
157
+ );
158
+ const resp = await worker.fetch("/test");
159
+ let text;
160
+ if (resp) text = await resp.text();
161
+ expect(text).toMatchInlineSnapshot(`"request"`);
162
+ await worker.stop();
163
+ });
164
+
165
+ it("should allow no input be passed in", async () => {
166
+ const scriptContent = `
167
+ export default {
168
+ fetch(request, env, ctx) {
169
+ const url = new URL(request.url);
170
+ if (url.pathname === "/test") {
171
+ return new Response("request");
172
+ }
173
+ return new Response('Hello world');
174
+ }
175
+ };
176
+ `;
177
+ fs.writeFileSync("index.js", scriptContent);
178
+ const worker = await unstable_dev(
179
+ "index.js",
180
+ {},
181
+ { disableExperimentalWarning: true }
182
+ );
183
+ const resp = await worker.fetch("");
184
+ let text;
185
+ if (resp) text = await resp.text();
186
+ expect(text).toMatchInlineSnapshot(`"Hello world"`);
187
+ await worker.stop();
188
+ });
189
+ });
@@ -0,0 +1,80 @@
1
+ import { Request } from "undici";
2
+ import { parseRequestInput } from "../api/dev";
3
+
4
+ describe("parseRequestInput for fetch on unstable dev", () => {
5
+ it("should allow no input to be passed in", () => {
6
+ const [input, _] = parseRequestInput("0.0.0.0", 8080);
7
+
8
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080"`);
9
+ });
10
+
11
+ it("should allow string of pathname to be passed in", () => {
12
+ const [input, _] = parseRequestInput("0.0.0.0", 8080, "/test");
13
+
14
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/test"`);
15
+ });
16
+
17
+ it("should allow full url to be passed in as string and stripped", () => {
18
+ const [input, _] = parseRequestInput(
19
+ "0.0.0.0",
20
+ 8080,
21
+ "http://cloudflare.com/test"
22
+ );
23
+
24
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/test"`);
25
+ });
26
+
27
+ it("should allow URL object without pathname to be passed in and stripped", () => {
28
+ const [input, _] = parseRequestInput(
29
+ "0.0.0.0",
30
+ 8080,
31
+ new URL("http://cloudflare.com")
32
+ );
33
+
34
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/"`);
35
+ });
36
+
37
+ it("should allow URL object with pathname to be passed in and stripped", () => {
38
+ const [input, _] = parseRequestInput(
39
+ "0.0.0.0",
40
+ 8080,
41
+ new URL("http://cloudflare.com/test")
42
+ );
43
+
44
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/test"`);
45
+ });
46
+
47
+ it("should allow request object to be passed in", () => {
48
+ const [input, init] = parseRequestInput(
49
+ "0.0.0.0",
50
+ 8080,
51
+ new Request("http://cloudflare.com/test", { method: "POST" })
52
+ );
53
+
54
+ expect(init).toBeUndefined();
55
+ expect(input).toBeInstanceOf(Request);
56
+ // We don't expect the request to be modified
57
+ expect((input as Request).url).toMatchInlineSnapshot(
58
+ `"http://cloudflare.com/test"`
59
+ );
60
+ expect((input as Request).method).toMatchInlineSnapshot(`"POST"`);
61
+ });
62
+
63
+ it("should parse to give https url with localProtocol = https", () => {
64
+ const [input, _] = parseRequestInput("0.0.0.0", 8080, "/test", {}, "https");
65
+
66
+ expect(input).toMatchInlineSnapshot(`"https://0.0.0.0:8080/test"`);
67
+ });
68
+
69
+ it("should parse to give http url with localProtocol = http", () => {
70
+ const [input, _] = parseRequestInput("0.0.0.0", 8080, "/test", {}, "http");
71
+
72
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/test"`);
73
+ });
74
+
75
+ it("should parse to give http url with localProtocol not set", () => {
76
+ const [input, _] = parseRequestInput("0.0.0.0", 8080, "/test", {});
77
+
78
+ expect(input).toMatchInlineSnapshot(`"http://0.0.0.0:8080/test"`);
79
+ });
80
+ });
@@ -982,7 +982,8 @@ describe("init", () => {
982
982
  {
983
983
  text: "Would you like to use TypeScript?",
984
984
  result: false,
985
- }
985
+ },
986
+ { text: "Would you like us to write your first test?", result: false }
986
987
  );
987
988
  mockSelect({
988
989
  text: "Would you like to create a Worker at src/index.js?",
@@ -1562,7 +1563,8 @@ describe("init", () => {
1562
1563
  {
1563
1564
  text: "Would you like to use TypeScript?",
1564
1565
  result: false,
1565
- }
1566
+ },
1567
+ { text: "Would you like us to write your first test?", result: false }
1566
1568
  );
1567
1569
  mockSelect({
1568
1570
  text: "Would you like to create a Worker at src/index.js?",
@@ -1596,6 +1598,127 @@ describe("init", () => {
1596
1598
  To publish your Worker to the Internet, run \`npm run deploy\`"
1597
1599
  `);
1598
1600
  });
1601
+ it("should add a jest test for a non-ts project with .js extension", async () => {
1602
+ mockConfirm(
1603
+ {
1604
+ text: "Would you like to use git to manage this Worker?",
1605
+ result: false,
1606
+ },
1607
+ {
1608
+ text: "No package.json found. Would you like to create one?",
1609
+ result: true,
1610
+ },
1611
+ {
1612
+ text: "Would you like to install wrangler into package.json?",
1613
+ result: false,
1614
+ },
1615
+ {
1616
+ text: "Would you like to use TypeScript?",
1617
+ result: false,
1618
+ },
1619
+ { text: "Would you like us to write your first test?", result: true }
1620
+ );
1621
+ mockSelect(
1622
+ {
1623
+ text: "Would you like to create a Worker at src/index.js?",
1624
+ result: "fetch",
1625
+ },
1626
+ { text: "Which test runner would you like to use?", result: "jest" }
1627
+ );
1628
+
1629
+ await runWrangler("init");
1630
+
1631
+ checkFiles({
1632
+ items: {
1633
+ "src/index.js": true,
1634
+ "src/index.test.js": true,
1635
+ "src/index.ts": false,
1636
+ "package.json": {
1637
+ contents: expect.objectContaining({
1638
+ name: expect.stringContaining("wrangler-tests"),
1639
+ version: "0.0.0",
1640
+ scripts: {
1641
+ start: "wrangler dev",
1642
+ deploy: "wrangler publish",
1643
+ test: "jest",
1644
+ },
1645
+ }),
1646
+ },
1647
+ },
1648
+ });
1649
+ expect(std.out).toMatchInlineSnapshot(`
1650
+ "✨ Created wrangler.toml
1651
+ ✨ Created package.json
1652
+ ✨ Created src/index.js
1653
+ ✨ Created src/index.test.js
1654
+ ✨ Installed jest into devDependencies
1655
+
1656
+ To start developing your Worker, run \`npm start\`
1657
+ To start testing your Worker, run \`npm test\`
1658
+ To publish your Worker to the Internet, run \`npm run deploy\`"
1659
+ `);
1660
+ });
1661
+
1662
+ it("should add a vitest test for a non-ts project with .js extension", async () => {
1663
+ mockConfirm(
1664
+ {
1665
+ text: "Would you like to use git to manage this Worker?",
1666
+ result: false,
1667
+ },
1668
+ {
1669
+ text: "No package.json found. Would you like to create one?",
1670
+ result: true,
1671
+ },
1672
+ {
1673
+ text: "Would you like to install wrangler into package.json?",
1674
+ result: false,
1675
+ },
1676
+ {
1677
+ text: "Would you like to use TypeScript?",
1678
+ result: false,
1679
+ },
1680
+ { text: "Would you like us to write your first test?", result: true }
1681
+ );
1682
+ mockSelect(
1683
+ {
1684
+ text: "Would you like to create a Worker at src/index.js?",
1685
+ result: "fetch",
1686
+ },
1687
+ { text: "Which test runner would you like to use?", result: "vitest" }
1688
+ );
1689
+
1690
+ await runWrangler("init");
1691
+
1692
+ checkFiles({
1693
+ items: {
1694
+ "src/index.js": true,
1695
+ "src/index.test.js": true,
1696
+ "src/index.ts": false,
1697
+ "package.json": {
1698
+ contents: expect.objectContaining({
1699
+ name: expect.stringContaining("wrangler-tests"),
1700
+ version: "0.0.0",
1701
+ scripts: {
1702
+ start: "wrangler dev",
1703
+ deploy: "wrangler publish",
1704
+ test: "vitest",
1705
+ },
1706
+ }),
1707
+ },
1708
+ },
1709
+ });
1710
+ expect(std.out).toMatchInlineSnapshot(`
1711
+ "✨ Created wrangler.toml
1712
+ ✨ Created package.json
1713
+ ✨ Created src/index.js
1714
+ ✨ Created src/index.test.js
1715
+ ✨ Installed vitest into devDependencies
1716
+
1717
+ To start developing your Worker, run \`npm start\`
1718
+ To start testing your Worker, run \`npm test\`
1719
+ To publish your Worker to the Internet, run \`npm run deploy\`"
1720
+ `);
1721
+ });
1599
1722
 
1600
1723
  it("should not overwrite package.json scripts for a non-ts project with .js extension", async () => {
1601
1724
  mockConfirm(
@@ -1610,7 +1733,8 @@ describe("init", () => {
1610
1733
  {
1611
1734
  text: "Would you like to use TypeScript?",
1612
1735
  result: false,
1613
- }
1736
+ },
1737
+ { text: "Would you like us to write your first test?", result: false }
1614
1738
  );
1615
1739
  mockSelect({
1616
1740
  text: "Would you like to create a Worker at src/index.js?",
@@ -124,7 +124,9 @@ describe("wrangler secret", () => {
124
124
  "🌀 Creating the secrets for the Worker \\"script-name\\"
125
125
  ✨ Successfully created secret for key: secret-name-1
126
126
  ✨ Successfully created secret for key: secret-name-2
127
- ✨ Finished processing secrets JSON file"
127
+
128
+ Finished processing secrets JSON file:
129
+ ✨ 2 secrets successfully uploaded"
128
130
  `);
129
131
  expect(std.err).toMatchInlineSnapshot(`""`);
130
132
  });
@@ -157,7 +159,10 @@ describe("wrangler secret", () => {
157
159
 
158
160
  expect(std.out).toMatchInlineSnapshot(`
159
161
  "🌀 Creating the secrets for the Worker \\"script-name\\"
160
- ✨ Finished processing secrets JSON file"
162
+
163
+ Finished processing secrets JSON file:
164
+ ✨ 0 secrets successfully uploaded
165
+ 🚨 2 secrets failed to upload"
161
166
  `);
162
167
  expect(std.err).toMatchInlineSnapshot(`
163
168
  "X [ERROR] 🚨 Error uploading secret for key: secret-name-1:
@@ -173,6 +178,75 @@ describe("wrangler secret", () => {
173
178
  `);
174
179
  });
175
180
 
181
+ it("should count success and failed secret:bulk", async () => {
182
+ writeFileSync(
183
+ "secret.json",
184
+ JSON.stringify({
185
+ "secret-name-1": "secret_text",
186
+ "secret-name-2": "secret_text",
187
+ "secret-name-3": "secret_text",
188
+ "secret-name-4": "secret_text",
189
+ "secret-name-5": "secret_text",
190
+ "secret-name-6": "secret_text",
191
+ "secret-name-7": "secret_text",
192
+ })
193
+ );
194
+
195
+ // User counter to pass different secrets to the request mock
196
+ let counter = 0;
197
+ setMockResponse(
198
+ `/accounts/:accountId/workers/scripts/:scriptName/secrets`,
199
+ "PUT",
200
+ ([_url, accountId]) => {
201
+ expect(accountId).toEqual("some-account-id");
202
+ counter++;
203
+
204
+ if (counter % 2 === 0) {
205
+ return { name: `secret-name-${counter}`, type: "secret_text" };
206
+ } else {
207
+ return Promise.reject(
208
+ new Error(`Failed to create secret ${counter}`)
209
+ );
210
+ }
211
+ }
212
+ );
213
+
214
+ await runWrangler("secret:bulk ./secret.json --name script-name");
215
+
216
+ expect(std.out).toMatchInlineSnapshot(`
217
+ "🌀 Creating the secrets for the Worker \\"script-name\\"
218
+ ✨ Successfully created secret for key: secret-name-2
219
+ ✨ Successfully created secret for key: secret-name-4
220
+ ✨ Successfully created secret for key: secret-name-6
221
+
222
+ Finished processing secrets JSON file:
223
+ ✨ 3 secrets successfully uploaded
224
+ 🚨 4 secrets failed to upload"
225
+ `);
226
+ expect(std.err).toMatchInlineSnapshot(`
227
+ "X [ERROR] 🚨 Error uploading secret for key: secret-name-1:
228
+
229
+ Failed to create secret 1
230
+
231
+
232
+ X [ERROR] 🚨 Error uploading secret for key: secret-name-3:
233
+
234
+ Failed to create secret 3
235
+
236
+
237
+ X [ERROR] 🚨 Error uploading secret for key: secret-name-5:
238
+
239
+ Failed to create secret 5
240
+
241
+
242
+ X [ERROR] 🚨 Error uploading secret for key: secret-name-7:
243
+
244
+ Failed to create secret 7
245
+
246
+ "
247
+ `);
248
+ });
249
+
176
250
  it("should create a secret: legacy envs", async () => {
177
251
  mockPrompt({
178
252
  text: "Enter a secret value:",
package/src/api/dev.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { fetch } from "undici";
1
+ import { fetch, Request } from "undici";
2
2
  import { startApiDev, startDev } from "../dev";
3
3
  import { logger } from "../logger";
4
4
 
5
5
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
6
- import type { RequestInit, Response } from "undici";
6
+ import type { RequestInit, Response, RequestInfo } from "undici";
7
7
 
8
8
  interface DevOptions {
9
9
  config?: string;
@@ -57,9 +57,12 @@ interface DevApiOptions {
57
57
  disableExperimentalWarning?: boolean;
58
58
  }
59
59
 
60
- interface UnstableDev {
60
+ export interface UnstableDevWorker {
61
61
  stop: () => Promise<void>;
62
- fetch: (init?: RequestInit) => Promise<Response | undefined>;
62
+ fetch: (
63
+ input?: RequestInfo,
64
+ init?: RequestInit
65
+ ) => Promise<Response | undefined>;
63
66
  waitUntilExit: () => Promise<void>;
64
67
  }
65
68
  /**
@@ -86,7 +89,7 @@ export async function unstable_dev(
86
89
  //due to Pages adoption of unstable_dev, we can't *just* disable rebuilds and watching. instead, we'll have two versions of startDev, which will converge.
87
90
  if (testMode) {
88
91
  //in testMode, we can run multiple wranglers in parallel, but rebuilds might not work out of the box
89
- return new Promise<UnstableDev>((resolve) => {
92
+ return new Promise<UnstableDevWorker>((resolve) => {
90
93
  //lmao
91
94
  return new Promise<Awaited<ReturnType<typeof startApiDev>>>((ready) => {
92
95
  // once the devServer is ready for requests, we resolve the inner promise
@@ -112,9 +115,16 @@ export async function unstable_dev(
112
115
  // with an object that lets you fetch and stop the dev server
113
116
  resolve({
114
117
  stop: devServer.stop,
115
- fetch: async (init?: RequestInit) => {
116
- const urlToFetch = `http://${readyAddress}:${readyPort}/`;
117
- return await fetch(urlToFetch, init);
118
+ fetch: async (input?: RequestInfo, init?: RequestInit) => {
119
+ return await fetch(
120
+ ...parseRequestInput(
121
+ readyAddress,
122
+ readyPort,
123
+ input,
124
+ init,
125
+ options?.localProtocol
126
+ )
127
+ );
118
128
  },
119
129
  //no-op, does nothing in tests
120
130
  waitUntilExit: async () => {
@@ -126,7 +136,7 @@ export async function unstable_dev(
126
136
  } else {
127
137
  //outside of test mode, rebuilds work fine, but only one instance of wrangler will work at a time
128
138
 
129
- return new Promise<UnstableDev>((resolve) => {
139
+ return new Promise<UnstableDevWorker>((resolve) => {
130
140
  //lmao
131
141
  return new Promise<Awaited<ReturnType<typeof startDev>>>((ready) => {
132
142
  const devServer = startDev({
@@ -147,9 +157,16 @@ export async function unstable_dev(
147
157
  }).then((devServer) => {
148
158
  resolve({
149
159
  stop: devServer.stop,
150
- fetch: async (init?: RequestInit) => {
151
- const urlToFetch = `http://${readyAddress}:${readyPort}/`;
152
- return await fetch(urlToFetch, init);
160
+ fetch: async (input?: RequestInfo, init?: RequestInit) => {
161
+ return await fetch(
162
+ ...parseRequestInput(
163
+ readyAddress,
164
+ readyPort,
165
+ input,
166
+ init,
167
+ options?.localProtocol
168
+ )
169
+ );
153
170
  },
154
171
  waitUntilExit: devServer.devReactElement.waitUntilExit,
155
172
  });
@@ -157,3 +174,31 @@ export async function unstable_dev(
157
174
  });
158
175
  }
159
176
  }
177
+
178
+ export function parseRequestInput(
179
+ readyAddress: string,
180
+ readyPort: number,
181
+ input?: RequestInfo,
182
+ init?: RequestInit,
183
+ protocol: "http" | "https" = "http"
184
+ ): [RequestInfo, RequestInit | undefined] {
185
+ if (input instanceof Request) {
186
+ return [input, undefined];
187
+ } else if (input instanceof URL) {
188
+ input = `${protocol}://${readyAddress}:${readyPort}${input.pathname}`;
189
+ } else if (typeof input === "string") {
190
+ try {
191
+ // Want to strip the URL to only get the pathname, but the user could pass in only the pathname
192
+ // Will error if we try and pass "/something" into new URL("/something")
193
+ input = `${protocol}://${readyAddress}:${readyPort}${
194
+ new URL(input).pathname
195
+ }`;
196
+ } catch {
197
+ input = `${protocol}://${readyAddress}:${readyPort}${input}`;
198
+ }
199
+ } else {
200
+ input = `${protocol}://${readyAddress}:${readyPort}`;
201
+ }
202
+
203
+ return [input, init];
204
+ }
package/src/api/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { unstable_dev } from "./dev";
2
+ export type { UnstableDevWorker } from "./dev";
package/src/cli.ts CHANGED
@@ -5,6 +5,7 @@ import { unstable_dev } from "./api";
5
5
  import { FatalError } from "./errors";
6
6
  import { main } from ".";
7
7
 
8
+ import type { UnstableDevWorker } from "./api";
8
9
  /**
9
10
  * The main entrypoint for the CLI.
10
11
  * main only gets called when the script is run directly, not when it's imported as a module.
@@ -25,3 +26,4 @@ if (typeof jest === "undefined" && require.main) {
25
26
  * and call wrangler.unstable_dev().
26
27
  */
27
28
  export { unstable_dev };
29
+ export type { UnstableDevWorker };
@@ -3,6 +3,7 @@ import { fetch } from "undici";
3
3
  import { fetchResult } from "./cfetch";
4
4
  import { createWorkerUploadForm } from "./create-worker-upload-form";
5
5
  import { logger } from "./logger";
6
+ import { parseJSON } from "./parse";
6
7
  import type { CfAccount, CfWorkerContext, CfWorkerInit } from "./worker";
7
8
 
8
9
  /**
@@ -125,9 +126,26 @@ export async function createPreviewSession(
125
126
  undefined,
126
127
  abortSignal
127
128
  );
128
- const { inspector_websocket, prewarm, token } = (await (
129
- await fetch(exchange_url, { signal: abortSignal })
130
- ).json()) as { inspector_websocket: string; token: string; prewarm: string };
129
+
130
+ logger.debug(`-- START EXCHANGE API REQUEST: GET ${exchange_url}`);
131
+ logger.debug("-- END EXCHANGE API REQUEST");
132
+ const exchangeResponse = await fetch(exchange_url, { signal: abortSignal });
133
+ const bodyText = await exchangeResponse.text();
134
+ logger.debug(
135
+ "-- START EXCHANGE API RESPONSE:",
136
+ exchangeResponse.statusText,
137
+ exchangeResponse.status
138
+ );
139
+ logger.debug("HEADERS:", JSON.stringify(exchangeResponse.headers, null, 2));
140
+ logger.debug("RESPONSE:", bodyText);
141
+ logger.debug("-- END EXCHANGE API RESPONSE");
142
+
143
+ const { inspector_websocket, prewarm, token } = parseJSON<{
144
+ inspector_websocket: string;
145
+ token: string;
146
+ prewarm: string;
147
+ }>(bodyText);
148
+
131
149
  const { host } = new URL(inspector_websocket);
132
150
  const query = `cf_workers_preview_token=${token}`;
133
151