wrangler 2.1.2 → 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.2",
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
+ });
@@ -1025,7 +1025,8 @@ describe("wrangler dev", () => {
1025
1025
  -l, --local Run on my machine [boolean] [default: false]
1026
1026
  --minify Minify the script [boolean]
1027
1027
  --node-compat Enable node.js compatibility [boolean]
1028
- --experimental-enable-local-persistence Enable persistence for this session (only for local mode) [boolean]
1028
+ --persist Enable persistence for local mode, using default path: .wrangler/state [boolean]
1029
+ --persist-to Specify directory to use for local persistence (implies --persist) [string]
1029
1030
  --inspect Enable dev tools [deprecated] [boolean]",
1030
1031
  "warn": "",
1031
1032
  }
@@ -0,0 +1,40 @@
1
+ jest.unmock("../dialogs");
2
+ import { fromDashMessagePrompt } from "../dialogs";
3
+ import { CI } from "../is-ci";
4
+
5
+ describe("fromDashMessagePrompt", () => {
6
+ it("should return undefined in CI when last deployed from api", async () => {
7
+ //in CI
8
+ jest.spyOn(CI, "isCI").mockReturnValue(true);
9
+ const result = await fromDashMessagePrompt("api");
10
+ expect(result).toBe(undefined);
11
+ });
12
+
13
+ it("should return undefined in CI when last deployed from wrangler", async () => {
14
+ //in CI
15
+ jest.spyOn(CI, "isCI").mockReturnValue(true);
16
+ const result = await fromDashMessagePrompt("wrangler");
17
+ expect(result).toBe(undefined);
18
+ });
19
+
20
+ it("should return true in CI when last deployed from dash", async () => {
21
+ //in CI
22
+ jest.spyOn(CI, "isCI").mockReturnValue(true);
23
+ const result = await fromDashMessagePrompt("dash");
24
+ expect(result).toBe(true);
25
+ });
26
+
27
+ it("should return undefined when last deployed from api", async () => {
28
+ //not in CI
29
+ jest.spyOn(CI, "isCI").mockReturnValue(false);
30
+ const result = await fromDashMessagePrompt("api");
31
+ expect(result).toBe(undefined);
32
+ });
33
+
34
+ it("should return undefined when last deployed from wrangler", async () => {
35
+ //not in CI
36
+ jest.spyOn(CI, "isCI").mockReturnValue(false);
37
+ const result = await fromDashMessagePrompt("wrangler");
38
+ expect(result).toBe(undefined);
39
+ });
40
+ });
@@ -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;
@@ -19,7 +19,8 @@ interface DevOptions {
19
19
  nodeCompat?: boolean;
20
20
  compatibilityDate?: string;
21
21
  compatibilityFlags?: string[];
22
- experimentalEnableLocalPersistence?: boolean;
22
+ persist?: boolean;
23
+ persistTo?: string;
23
24
  liveReload?: boolean;
24
25
  watch?: boolean;
25
26
  vars?: {
@@ -56,9 +57,12 @@ interface DevApiOptions {
56
57
  disableExperimentalWarning?: boolean;
57
58
  }
58
59
 
59
- interface UnstableDev {
60
+ export interface UnstableDevWorker {
60
61
  stop: () => Promise<void>;
61
- fetch: (init?: RequestInit) => Promise<Response | undefined>;
62
+ fetch: (
63
+ input?: RequestInfo,
64
+ init?: RequestInit
65
+ ) => Promise<Response | undefined>;
62
66
  waitUntilExit: () => Promise<void>;
63
67
  }
64
68
  /**
@@ -85,7 +89,7 @@ export async function unstable_dev(
85
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.
86
90
  if (testMode) {
87
91
  //in testMode, we can run multiple wranglers in parallel, but rebuilds might not work out of the box
88
- return new Promise<UnstableDev>((resolve) => {
92
+ return new Promise<UnstableDevWorker>((resolve) => {
89
93
  //lmao
90
94
  return new Promise<Awaited<ReturnType<typeof startApiDev>>>((ready) => {
91
95
  // once the devServer is ready for requests, we resolve the inner promise
@@ -111,9 +115,16 @@ export async function unstable_dev(
111
115
  // with an object that lets you fetch and stop the dev server
112
116
  resolve({
113
117
  stop: devServer.stop,
114
- fetch: async (init?: RequestInit) => {
115
- const urlToFetch = `http://${readyAddress}:${readyPort}/`;
116
- 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
+ );
117
128
  },
118
129
  //no-op, does nothing in tests
119
130
  waitUntilExit: async () => {
@@ -125,7 +136,7 @@ export async function unstable_dev(
125
136
  } else {
126
137
  //outside of test mode, rebuilds work fine, but only one instance of wrangler will work at a time
127
138
 
128
- return new Promise<UnstableDev>((resolve) => {
139
+ return new Promise<UnstableDevWorker>((resolve) => {
129
140
  //lmao
130
141
  return new Promise<Awaited<ReturnType<typeof startDev>>>((ready) => {
131
142
  const devServer = startDev({
@@ -146,9 +157,16 @@ export async function unstable_dev(
146
157
  }).then((devServer) => {
147
158
  resolve({
148
159
  stop: devServer.stop,
149
- fetch: async (init?: RequestInit) => {
150
- const urlToFetch = `http://${readyAddress}:${readyPort}/`;
151
- 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
+ );
152
170
  },
153
171
  waitUntilExit: devServer.devReactElement.waitUntilExit,
154
172
  });
@@ -156,3 +174,31 @@ export async function unstable_dev(
156
174
  });
157
175
  }
158
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
+ }