wrangler 2.0.8 → 2.0.9
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 +1 -1
- package/src/__tests__/dev.test.tsx +61 -58
- package/src/__tests__/init.test.ts +3 -0
- package/src/__tests__/pages.test.ts +98 -1
- package/src/__tests__/publish.test.ts +123 -3
- package/src/__tests__/whoami.test.tsx +34 -0
- package/src/cfetch/internal.ts +6 -9
- package/src/config/config.ts +1 -1
- package/src/create-worker-preview.ts +15 -15
- package/src/dev/dev.tsx +4 -12
- package/src/dev/remote.tsx +26 -16
- package/src/index.tsx +22 -83
- package/src/pages.tsx +14 -3
- package/src/publish.ts +148 -15
- package/src/user.tsx +12 -1
- package/src/whoami.tsx +3 -2
- package/src/worker.ts +2 -1
- package/src/zones.ts +73 -0
- package/wrangler-dist/cli.js +188 -80
package/package.json
CHANGED
|
@@ -3,7 +3,6 @@ import getPort from "get-port";
|
|
|
3
3
|
import patchConsole from "patch-console";
|
|
4
4
|
import dedent from "ts-dedent";
|
|
5
5
|
import Dev from "../dev/dev";
|
|
6
|
-
import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
|
|
7
6
|
import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
|
|
8
7
|
import { mockConsoleMethods } from "./helpers/mock-console";
|
|
9
8
|
import { runInTempDir } from "./helpers/run-in-tmp";
|
|
@@ -11,8 +10,6 @@ import { runWrangler } from "./helpers/run-wrangler";
|
|
|
11
10
|
import writeWranglerToml from "./helpers/write-wrangler-toml";
|
|
12
11
|
|
|
13
12
|
describe("wrangler dev", () => {
|
|
14
|
-
mockAccountId();
|
|
15
|
-
mockApiToken();
|
|
16
13
|
runInTempDir();
|
|
17
14
|
const std = mockConsoleMethods();
|
|
18
15
|
afterEach(() => {
|
|
@@ -152,10 +149,12 @@ describe("wrangler dev", () => {
|
|
|
152
149
|
fs.writeFileSync("index.js", `export default {};`);
|
|
153
150
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
154
151
|
await runWrangler("dev --host some-host.com");
|
|
155
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
host: "some-host.com",
|
|
155
|
+
zone: "some-zone-id",
|
|
156
|
+
})
|
|
157
|
+
);
|
|
159
158
|
});
|
|
160
159
|
|
|
161
160
|
it("should read wrangler.toml's dev.host", async () => {
|
|
@@ -168,9 +167,7 @@ describe("wrangler dev", () => {
|
|
|
168
167
|
fs.writeFileSync("index.js", `export default {};`);
|
|
169
168
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
170
169
|
await runWrangler("dev");
|
|
171
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
172
|
-
"some-host.com"
|
|
173
|
-
);
|
|
170
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
174
171
|
});
|
|
175
172
|
|
|
176
173
|
it("should read --route", async () => {
|
|
@@ -180,9 +177,7 @@ describe("wrangler dev", () => {
|
|
|
180
177
|
fs.writeFileSync("index.js", `export default {};`);
|
|
181
178
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
182
179
|
await runWrangler("dev --route http://some-host.com/some/path/*");
|
|
183
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
184
|
-
"some-host.com"
|
|
185
|
-
);
|
|
180
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
186
181
|
});
|
|
187
182
|
|
|
188
183
|
it("should read wrangler.toml's routes", async () => {
|
|
@@ -196,9 +191,7 @@ describe("wrangler dev", () => {
|
|
|
196
191
|
fs.writeFileSync("index.js", `export default {};`);
|
|
197
192
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
198
193
|
await runWrangler("dev");
|
|
199
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
200
|
-
"some-host.com"
|
|
201
|
-
);
|
|
194
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
202
195
|
});
|
|
203
196
|
|
|
204
197
|
it("should read wrangler.toml's environment specific routes", async () => {
|
|
@@ -220,9 +213,7 @@ describe("wrangler dev", () => {
|
|
|
220
213
|
fs.writeFileSync("index.js", `export default {};`);
|
|
221
214
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
222
215
|
await runWrangler("dev --env staging");
|
|
223
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
224
|
-
"some-host.com"
|
|
225
|
-
);
|
|
216
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
226
217
|
});
|
|
227
218
|
|
|
228
219
|
it("should strip leading `*` from given host when deducing a zone id", async () => {
|
|
@@ -233,9 +224,7 @@ describe("wrangler dev", () => {
|
|
|
233
224
|
fs.writeFileSync("index.js", `export default {};`);
|
|
234
225
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
235
226
|
await runWrangler("dev");
|
|
236
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
237
|
-
"some-host.com"
|
|
238
|
-
);
|
|
227
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
239
228
|
});
|
|
240
229
|
|
|
241
230
|
it("should strip leading `*.` from given host when deducing a zone id", async () => {
|
|
@@ -246,9 +235,7 @@ describe("wrangler dev", () => {
|
|
|
246
235
|
fs.writeFileSync("index.js", `export default {};`);
|
|
247
236
|
mockGetZones("some-host.com", [{ id: "some-zone-id" }]);
|
|
248
237
|
await runWrangler("dev");
|
|
249
|
-
expect((Dev as jest.Mock).mock.calls[0][0].
|
|
250
|
-
"some-host.com"
|
|
251
|
-
);
|
|
238
|
+
expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual("some-host.com");
|
|
252
239
|
});
|
|
253
240
|
|
|
254
241
|
it("should, when provided, use a configured zone_id", async () => {
|
|
@@ -260,10 +247,12 @@ describe("wrangler dev", () => {
|
|
|
260
247
|
});
|
|
261
248
|
fs.writeFileSync("index.js", `export default {};`);
|
|
262
249
|
await runWrangler("dev");
|
|
263
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
250
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
251
|
+
expect.objectContaining({
|
|
252
|
+
host: "some-domain.com",
|
|
253
|
+
zone: "some-zone-id",
|
|
254
|
+
})
|
|
255
|
+
);
|
|
267
256
|
});
|
|
268
257
|
|
|
269
258
|
it("should, when provided, use a zone_name to get a zone_id", async () => {
|
|
@@ -276,11 +265,13 @@ describe("wrangler dev", () => {
|
|
|
276
265
|
fs.writeFileSync("index.js", `export default {};`);
|
|
277
266
|
mockGetZones("some-zone.com", [{ id: "a-zone-id" }]);
|
|
278
267
|
await runWrangler("dev");
|
|
279
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
268
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
269
|
+
expect.objectContaining({
|
|
270
|
+
// note that it uses the provided zone_name as a host too
|
|
271
|
+
host: "some-zone.com",
|
|
272
|
+
zone: "a-zone-id",
|
|
273
|
+
})
|
|
274
|
+
);
|
|
284
275
|
});
|
|
285
276
|
|
|
286
277
|
it("given a long host, it should use the longest subdomain that resolves to a zone", async () => {
|
|
@@ -292,10 +283,12 @@ describe("wrangler dev", () => {
|
|
|
292
283
|
mockGetZones("222.333.some-host.com", []);
|
|
293
284
|
mockGetZones("333.some-host.com", [{ id: "some-zone-id" }]);
|
|
294
285
|
await runWrangler("dev --host 111.222.333.some-host.com");
|
|
295
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
286
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
287
|
+
expect.objectContaining({
|
|
288
|
+
host: "111.222.333.some-host.com",
|
|
289
|
+
zone: "some-zone-id",
|
|
290
|
+
})
|
|
291
|
+
);
|
|
299
292
|
});
|
|
300
293
|
|
|
301
294
|
it("should, in order, use args.host/config.dev.host/args.routes/(config.route|config.routes)", async () => {
|
|
@@ -310,10 +303,12 @@ describe("wrangler dev", () => {
|
|
|
310
303
|
routes: ["http://5.some-host.com/some/path/*"],
|
|
311
304
|
});
|
|
312
305
|
await runWrangler("dev");
|
|
313
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
306
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
307
|
+
expect.objectContaining({
|
|
308
|
+
host: "5.some-host.com",
|
|
309
|
+
zone: "some-zone-id-5",
|
|
310
|
+
})
|
|
311
|
+
);
|
|
317
312
|
(Dev as jest.Mock).mockClear();
|
|
318
313
|
|
|
319
314
|
// config.route
|
|
@@ -323,10 +318,12 @@ describe("wrangler dev", () => {
|
|
|
323
318
|
route: "https://4.some-host.com/some/path/*",
|
|
324
319
|
});
|
|
325
320
|
await runWrangler("dev");
|
|
326
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
321
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
322
|
+
expect.objectContaining({
|
|
323
|
+
host: "4.some-host.com",
|
|
324
|
+
zone: "some-zone-id-4",
|
|
325
|
+
})
|
|
326
|
+
);
|
|
330
327
|
(Dev as jest.Mock).mockClear();
|
|
331
328
|
|
|
332
329
|
// --routes
|
|
@@ -336,10 +333,12 @@ describe("wrangler dev", () => {
|
|
|
336
333
|
route: "https://4.some-host.com/some/path/*",
|
|
337
334
|
});
|
|
338
335
|
await runWrangler("dev --routes http://3.some-host.com/some/path/*");
|
|
339
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
336
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
337
|
+
expect.objectContaining({
|
|
338
|
+
host: "3.some-host.com",
|
|
339
|
+
zone: "some-zone-id-3",
|
|
340
|
+
})
|
|
341
|
+
);
|
|
343
342
|
(Dev as jest.Mock).mockClear();
|
|
344
343
|
|
|
345
344
|
// config.dev.host
|
|
@@ -352,10 +351,12 @@ describe("wrangler dev", () => {
|
|
|
352
351
|
route: "4.some-host.com/some/path/*",
|
|
353
352
|
});
|
|
354
353
|
await runWrangler("dev --routes http://3.some-host.com/some/path/*");
|
|
355
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
354
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
host: "2.some-host.com",
|
|
357
|
+
zone: "some-zone-id-2",
|
|
358
|
+
})
|
|
359
|
+
);
|
|
359
360
|
(Dev as jest.Mock).mockClear();
|
|
360
361
|
|
|
361
362
|
// --host
|
|
@@ -370,10 +371,12 @@ describe("wrangler dev", () => {
|
|
|
370
371
|
await runWrangler(
|
|
371
372
|
"dev --routes http://3.some-host.com/some/path/* --host 1.some-host.com"
|
|
372
373
|
);
|
|
373
|
-
expect((Dev as jest.Mock).mock.calls[0][0]
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
expect((Dev as jest.Mock).mock.calls[0][0]).toEqual(
|
|
375
|
+
expect.objectContaining({
|
|
376
|
+
host: "1.some-host.com",
|
|
377
|
+
zone: "some-zone-id-1",
|
|
378
|
+
})
|
|
379
|
+
);
|
|
377
380
|
(Dev as jest.Mock).mockClear();
|
|
378
381
|
});
|
|
379
382
|
|
|
@@ -435,6 +435,9 @@ describe("init", () => {
|
|
|
435
435
|
`);
|
|
436
436
|
expect(fs.lstatSync(".git").isDirectory()).toBe(true);
|
|
437
437
|
expect(fs.lstatSync(".gitignore").isFile()).toBe(true);
|
|
438
|
+
expect((await execa("git", ["branch", "--show-current"])).stdout).toEqual(
|
|
439
|
+
"main"
|
|
440
|
+
);
|
|
438
441
|
});
|
|
439
442
|
|
|
440
443
|
it("should not offer to initialize a git repo if it's already inside one", async () => {
|
|
@@ -19,7 +19,7 @@ function assertLater(fn: () => void) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function mockGetToken(jwt: string) {
|
|
22
|
-
setMockResponse(
|
|
22
|
+
return setMockResponse(
|
|
23
23
|
"/accounts/:accountId/pages/projects/foo/upload-token",
|
|
24
24
|
async ([_url, accountId]) => {
|
|
25
25
|
assertLater(() => {
|
|
@@ -488,6 +488,103 @@ describe("pages", () => {
|
|
|
488
488
|
`);
|
|
489
489
|
});
|
|
490
490
|
|
|
491
|
+
it("should refetch a JWT if it expires while uploading", async () => {
|
|
492
|
+
writeFileSync("logo.txt", "foobar");
|
|
493
|
+
|
|
494
|
+
const cancelMockGetToken = mockGetToken("<<funfetti-auth-jwt>>");
|
|
495
|
+
|
|
496
|
+
setMockResponse(
|
|
497
|
+
"/pages/assets/check-missing",
|
|
498
|
+
"POST",
|
|
499
|
+
async (_, init) => {
|
|
500
|
+
const body = JSON.parse(init.body as string) as { hashes: string[] };
|
|
501
|
+
assertLater(() => {
|
|
502
|
+
expect(init.headers).toMatchObject({
|
|
503
|
+
Authorization: "Bearer <<funfetti-auth-jwt>>",
|
|
504
|
+
});
|
|
505
|
+
expect(body).toMatchObject({
|
|
506
|
+
hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
return body.hashes;
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Accumulate multiple requests then assert afterwards
|
|
514
|
+
const requests: RequestInit[] = [];
|
|
515
|
+
setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
|
|
516
|
+
requests.push(init);
|
|
517
|
+
|
|
518
|
+
// Fail just the first request
|
|
519
|
+
if (requests.length < 2) {
|
|
520
|
+
cancelMockGetToken();
|
|
521
|
+
mockGetToken("<<funfetti-auth-jwt2>>");
|
|
522
|
+
return createFetchResult(null, false, [
|
|
523
|
+
{
|
|
524
|
+
code: 8000013,
|
|
525
|
+
message: "Authorization failed",
|
|
526
|
+
},
|
|
527
|
+
]);
|
|
528
|
+
} else {
|
|
529
|
+
return createFetchResult(null, true);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
setMockResponse(
|
|
534
|
+
"/accounts/:accountId/pages/projects/foo/deployments",
|
|
535
|
+
async ([_url, accountId], init) => {
|
|
536
|
+
assertLater(() => {
|
|
537
|
+
expect(accountId).toEqual("some-account-id");
|
|
538
|
+
expect(init.method).toEqual("POST");
|
|
539
|
+
const body = init.body as FormData;
|
|
540
|
+
const manifest = JSON.parse(body.get("manifest") as string);
|
|
541
|
+
expect(manifest).toMatchInlineSnapshot(`
|
|
542
|
+
Object {
|
|
543
|
+
"/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
|
|
544
|
+
}
|
|
545
|
+
`);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
url: "https://abcxyz.foo.pages.dev/",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
await runWrangler("pages publish . --project-name=foo");
|
|
555
|
+
|
|
556
|
+
// Assert two requests
|
|
557
|
+
expect(requests.length).toBe(2);
|
|
558
|
+
|
|
559
|
+
expect(requests[0].headers).toMatchObject({
|
|
560
|
+
Authorization: "Bearer <<funfetti-auth-jwt>>",
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
expect(requests[1].headers).toMatchObject({
|
|
564
|
+
Authorization: "Bearer <<funfetti-auth-jwt2>>",
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
for (const init of requests) {
|
|
568
|
+
const body = JSON.parse(init.body as string) as UploadPayloadFile[];
|
|
569
|
+
expect(body).toMatchObject([
|
|
570
|
+
{
|
|
571
|
+
key: "1a98fb08af91aca4a7df1764a2c4ddb0",
|
|
572
|
+
value: Buffer.from("foobar").toString("base64"),
|
|
573
|
+
metadata: {
|
|
574
|
+
contentType: "text/plain",
|
|
575
|
+
},
|
|
576
|
+
base64: true,
|
|
577
|
+
},
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
expect(std.out).toMatchInlineSnapshot(`
|
|
582
|
+
"✨ Success! Uploaded 1 files (TIMINGS)
|
|
583
|
+
|
|
584
|
+
✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
|
|
585
|
+
`);
|
|
586
|
+
});
|
|
587
|
+
|
|
491
588
|
it("should try to use multiple buckets (up to the max concurrency)", async () => {
|
|
492
589
|
writeFileSync("logo.txt", "foobar");
|
|
493
590
|
writeFileSync("logo.png", "foobar");
|
|
@@ -649,8 +649,85 @@ describe("publish", () => {
|
|
|
649
649
|
await runWrangler("publish ./index --env dev --legacy-env false");
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
+
it("should fallback to the Wrangler 1 zone-based API if the bulk-routes API fails", async () => {
|
|
653
|
+
writeWranglerToml({
|
|
654
|
+
routes: ["example.com/some-route/*"],
|
|
655
|
+
});
|
|
656
|
+
writeWorkerSource();
|
|
657
|
+
mockUpdateWorkerRequest({ enabled: false });
|
|
658
|
+
mockUploadWorkerRequest({ expectedType: "esm" });
|
|
659
|
+
// Simulate the bulk-routes API failing with a not authorized error.
|
|
660
|
+
mockUnauthorizedPublishRoutesRequest();
|
|
661
|
+
// Simulate that the worker has already been deployed to another route in this zone.
|
|
662
|
+
mockCollectKnownRoutesRequest([
|
|
663
|
+
{
|
|
664
|
+
pattern: "foo.example.com/other-route",
|
|
665
|
+
script: "test-name",
|
|
666
|
+
},
|
|
667
|
+
]);
|
|
668
|
+
mockGetZoneFromHostRequest("example.com", "some-zone-id");
|
|
669
|
+
mockPublishRoutesFallbackRequest({
|
|
670
|
+
pattern: "example.com/some-route/*",
|
|
671
|
+
script: "test-name",
|
|
672
|
+
});
|
|
673
|
+
await runWrangler("publish ./index");
|
|
674
|
+
|
|
675
|
+
expect(std.err).toMatchInlineSnapshot(`""`);
|
|
676
|
+
expect(std.warn).toMatchInlineSnapshot(`
|
|
677
|
+
"[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mThe current authentication token does not have 'All Zones' permissions.[0m
|
|
678
|
+
|
|
679
|
+
Falling back to using the zone-based API endpoint to update each route individually.
|
|
680
|
+
Note that there is no access to routes associated with zones that the API token does not have
|
|
681
|
+
permission for.
|
|
682
|
+
Existing routes for this Worker in such zones will not be deleted.
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mPreviously deployed routes:[0m
|
|
686
|
+
|
|
687
|
+
The following routes were already associated with this worker, and have not been deleted:
|
|
688
|
+
- \\"foo.example.com/other-route\\"
|
|
689
|
+
If these routes are not wanted then you can remove them in the dashboard.
|
|
690
|
+
|
|
691
|
+
"
|
|
692
|
+
`);
|
|
693
|
+
expect(std.out).toMatchInlineSnapshot(`
|
|
694
|
+
"Uploaded test-name (TIMINGS)
|
|
695
|
+
Published test-name (TIMINGS)
|
|
696
|
+
example.com/some-route/*"
|
|
697
|
+
`);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("should error if the bulk-routes API fails and trying to push to a non-production environment", async () => {
|
|
701
|
+
writeWranglerToml({
|
|
702
|
+
routes: ["example.com/some-route/*"],
|
|
703
|
+
legacy_env: false,
|
|
704
|
+
});
|
|
705
|
+
writeWorkerSource();
|
|
706
|
+
mockUpdateWorkerRequest({ env: "staging", enabled: false });
|
|
707
|
+
mockUploadWorkerRequest({ env: "staging", expectedType: "esm" });
|
|
708
|
+
// Simulate the bulk-routes API failing with a not authorized error.
|
|
709
|
+
mockUnauthorizedPublishRoutesRequest({ env: "staging" });
|
|
710
|
+
// Simulate that the worker has already been deployed to another route in this zone.
|
|
711
|
+
mockCollectKnownRoutesRequest([
|
|
712
|
+
{
|
|
713
|
+
pattern: "foo.example.com/other-route",
|
|
714
|
+
script: "test-name",
|
|
715
|
+
},
|
|
716
|
+
]);
|
|
717
|
+
mockGetZoneFromHostRequest("example.com", "some-zone-id");
|
|
718
|
+
mockPublishRoutesFallbackRequest({
|
|
719
|
+
pattern: "example.com/some-route/*",
|
|
720
|
+
script: "test-name",
|
|
721
|
+
});
|
|
722
|
+
await expect(runWrangler("publish ./index --env=staging")).rejects
|
|
723
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
724
|
+
"Service environments combined with an API token that doesn't have 'All Zones' permissions is not supported.
|
|
725
|
+
Either turn off service environments by setting \`legacy_env = true\`, creating an API token with 'All Zones' permissions, or logging in via OAuth"
|
|
726
|
+
`);
|
|
727
|
+
});
|
|
728
|
+
|
|
652
729
|
describe("custom domains", () => {
|
|
653
|
-
it("should publish routes marked with 'custom_domain' as
|
|
730
|
+
it("should publish routes marked with 'custom_domain' as separate custom domains", async () => {
|
|
654
731
|
writeWranglerToml({
|
|
655
732
|
routes: [{ pattern: "api.example.com", custom_domain: true }],
|
|
656
733
|
});
|
|
@@ -5004,7 +5081,7 @@ addEventListener('fetch', event => {});`
|
|
|
5004
5081
|
"debug": "",
|
|
5005
5082
|
"err": "",
|
|
5006
5083
|
"out": "--dry-run: exiting now.",
|
|
5007
|
-
"warn": "[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mEnabling node.js compatibility mode for
|
|
5084
|
+
"warn": "[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mEnabling node.js compatibility mode for built-ins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details.[0m
|
|
5008
5085
|
|
|
5009
5086
|
",
|
|
5010
5087
|
}
|
|
@@ -5048,7 +5125,7 @@ addEventListener('fetch', event => {});`
|
|
|
5048
5125
|
"debug": "",
|
|
5049
5126
|
"err": "",
|
|
5050
5127
|
"out": "--dry-run: exiting now.",
|
|
5051
|
-
"warn": "[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mEnabling node.js compatibility mode for
|
|
5128
|
+
"warn": "[33m▲ [43;33m[[43;30mWARNING[43;33m][0m [1mEnabling node.js compatibility mode for built-ins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details.[0m
|
|
5052
5129
|
|
|
5053
5130
|
",
|
|
5054
5131
|
}
|
|
@@ -5226,6 +5303,49 @@ function mockPublishRoutesRequest({
|
|
|
5226
5303
|
);
|
|
5227
5304
|
}
|
|
5228
5305
|
|
|
5306
|
+
function mockUnauthorizedPublishRoutesRequest({
|
|
5307
|
+
env = undefined,
|
|
5308
|
+
legacyEnv = false,
|
|
5309
|
+
}: {
|
|
5310
|
+
env?: string | undefined;
|
|
5311
|
+
legacyEnv?: boolean | undefined;
|
|
5312
|
+
} = {}) {
|
|
5313
|
+
const servicesOrScripts = env && !legacyEnv ? "services" : "scripts";
|
|
5314
|
+
const environment = env && !legacyEnv ? "/environments/:envName" : "";
|
|
5315
|
+
|
|
5316
|
+
setMockRawResponse(
|
|
5317
|
+
`/accounts/:accountId/workers/${servicesOrScripts}/:scriptName${environment}/routes`,
|
|
5318
|
+
"PUT",
|
|
5319
|
+
() =>
|
|
5320
|
+
createFetchResult(null, false, [
|
|
5321
|
+
{ message: "Authentication error", code: 10000 },
|
|
5322
|
+
])
|
|
5323
|
+
);
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
function mockCollectKnownRoutesRequest(
|
|
5327
|
+
routes: { pattern: string; script: string }[]
|
|
5328
|
+
) {
|
|
5329
|
+
setMockResponse(`/zones/:zoneId/workers/routes`, "GET", () => routes);
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
function mockGetZoneFromHostRequest(host: string, zone: string) {
|
|
5333
|
+
setMockResponse("/zones", (_uri, _init, queryParams) => {
|
|
5334
|
+
expect(queryParams.get("name")).toEqual(host);
|
|
5335
|
+
return [{ id: zone }];
|
|
5336
|
+
});
|
|
5337
|
+
}
|
|
5338
|
+
|
|
5339
|
+
function mockPublishRoutesFallbackRequest(route: {
|
|
5340
|
+
pattern: string;
|
|
5341
|
+
script: string;
|
|
5342
|
+
}) {
|
|
5343
|
+
setMockResponse(`/zones/:zoneId/workers/routes`, "POST", (_url, { body }) => {
|
|
5344
|
+
expect(JSON.parse(body as string)).toEqual(route);
|
|
5345
|
+
return route.pattern;
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5229
5349
|
function mockPublishCustomDomainsRequest({
|
|
5230
5350
|
publishFlags,
|
|
5231
5351
|
domains = [],
|
|
@@ -9,6 +9,8 @@ import { runInTempDir } from "./helpers/run-in-tmp";
|
|
|
9
9
|
import type { UserInfo } from "../whoami";
|
|
10
10
|
|
|
11
11
|
describe("getUserInfo()", () => {
|
|
12
|
+
const ENV_COPY = process.env;
|
|
13
|
+
|
|
12
14
|
runInTempDir({ homedir: "./home" });
|
|
13
15
|
const std = mockConsoleMethods();
|
|
14
16
|
const { setIsTTY } = useMockIsTTY();
|
|
@@ -17,6 +19,10 @@ describe("getUserInfo()", () => {
|
|
|
17
19
|
setIsTTY(true);
|
|
18
20
|
});
|
|
19
21
|
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.env = ENV_COPY;
|
|
24
|
+
});
|
|
25
|
+
|
|
20
26
|
it("should return undefined if there is no config file", async () => {
|
|
21
27
|
const userInfo = await getUserInfo();
|
|
22
28
|
expect(userInfo).toBeUndefined();
|
|
@@ -28,6 +34,34 @@ describe("getUserInfo()", () => {
|
|
|
28
34
|
expect(userInfo).toBeUndefined();
|
|
29
35
|
});
|
|
30
36
|
|
|
37
|
+
it("should say it's using an API token when one is set", async () => {
|
|
38
|
+
process.env = {
|
|
39
|
+
CLOUDFLARE_API_TOKEN: "123456789",
|
|
40
|
+
};
|
|
41
|
+
setMockResponse("/user", () => {
|
|
42
|
+
return { email: "user@example.com" };
|
|
43
|
+
});
|
|
44
|
+
setMockResponse("/accounts", () => {
|
|
45
|
+
return [
|
|
46
|
+
{ name: "Account One", id: "account-1" },
|
|
47
|
+
{ name: "Account Two", id: "account-2" },
|
|
48
|
+
{ name: "Account Three", id: "account-3" },
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const userInfo = await getUserInfo();
|
|
53
|
+
expect(userInfo).toEqual({
|
|
54
|
+
authType: "API",
|
|
55
|
+
apiToken: "123456789",
|
|
56
|
+
email: "user@example.com",
|
|
57
|
+
accounts: [
|
|
58
|
+
{ name: "Account One", id: "account-1" },
|
|
59
|
+
{ name: "Account Two", id: "account-2" },
|
|
60
|
+
{ name: "Account Three", id: "account-3" },
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
31
65
|
it("should return the user's email and accounts if authenticated via config token", async () => {
|
|
32
66
|
writeAuthConfigFile({ oauth_token: "some-oauth-token" });
|
|
33
67
|
|
package/src/cfetch/internal.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
1
2
|
import { fetch, Headers } from "undici";
|
|
2
3
|
import { version as wranglerVersion } from "../../package.json";
|
|
3
4
|
import { getEnvironmentVariableFactory } from "../environment-variables";
|
|
4
5
|
import { ParseError, parseJSON } from "../parse";
|
|
5
|
-
import {
|
|
6
|
+
import { loginOrRefreshIfRequired, requireApiToken } from "../user";
|
|
6
7
|
import type { URLSearchParams } from "node:url";
|
|
7
8
|
import type { RequestInit, HeadersInit } from "undici";
|
|
8
9
|
|
|
@@ -30,6 +31,10 @@ export async function fetchInternal<ResponseType>(
|
|
|
30
31
|
queryParams?: URLSearchParams,
|
|
31
32
|
abortSignal?: AbortSignal
|
|
32
33
|
): Promise<ResponseType> {
|
|
34
|
+
assert(
|
|
35
|
+
resource.startsWith("/"),
|
|
36
|
+
`CF API fetch - resource path must start with a "/" but got "${resource}"`
|
|
37
|
+
);
|
|
33
38
|
await requireLoggedIn();
|
|
34
39
|
const apiToken = requireApiToken();
|
|
35
40
|
const headers = cloneHeaders(init.headers);
|
|
@@ -90,14 +95,6 @@ async function requireLoggedIn(): Promise<void> {
|
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
function requireApiToken(): string {
|
|
94
|
-
const authToken = getAPIToken();
|
|
95
|
-
if (!authToken) {
|
|
96
|
-
throw new Error("No API token found.");
|
|
97
|
-
}
|
|
98
|
-
return authToken;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
98
|
function addAuthorizationHeaderIfUnspecified(
|
|
102
99
|
headers: Record<string, string>,
|
|
103
100
|
apiToken: string
|
package/src/config/config.ts
CHANGED
|
@@ -63,7 +63,7 @@ async function sessionToken(
|
|
|
63
63
|
): Promise<CfPreviewToken> {
|
|
64
64
|
const { accountId } = account;
|
|
65
65
|
const initUrl = ctx.zone
|
|
66
|
-
? `/zones/${ctx.zone
|
|
66
|
+
? `/zones/${ctx.zone}/workers/edge-preview`
|
|
67
67
|
: `/accounts/${accountId}/workers/subdomain/edge-preview`;
|
|
68
68
|
|
|
69
69
|
const { exchange_url } = await fetchResult<{ exchange_url: string }>(
|
|
@@ -111,7 +111,7 @@ async function createPreviewToken(
|
|
|
111
111
|
);
|
|
112
112
|
|
|
113
113
|
const { accountId } = account;
|
|
114
|
-
const scriptId = ctx.zone ? randomId() :
|
|
114
|
+
const scriptId = worker.name || (ctx.zone ? randomId() : host.split(".")[0]);
|
|
115
115
|
const url =
|
|
116
116
|
ctx.env && !ctx.legacyEnv
|
|
117
117
|
? `/accounts/${accountId}/workers/services/${scriptId}/environments/${ctx.env}/edge-preview`
|
|
@@ -139,19 +139,19 @@ async function createPreviewToken(
|
|
|
139
139
|
|
|
140
140
|
return {
|
|
141
141
|
value: preview_token,
|
|
142
|
-
host:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
host:
|
|
143
|
+
ctx.host ??
|
|
144
|
+
(worker.name
|
|
145
|
+
? `${
|
|
146
|
+
worker.name
|
|
147
|
+
// TODO: this should also probably have the env prefix
|
|
148
|
+
// but it doesn't appear to work yet, instead giving us the
|
|
149
|
+
// "There is nothing here yet" screen
|
|
150
|
+
// ctx.env && !ctx.legacyEnv
|
|
151
|
+
// ? `${ctx.env}.${worker.name}`
|
|
152
|
+
// : worker.name
|
|
153
|
+
}.${host.split(".").slice(1).join(".")}`
|
|
154
|
+
: host),
|
|
155
155
|
|
|
156
156
|
inspectorUrl,
|
|
157
157
|
prewarmUrl,
|