wrangler 2.0.25 → 2.0.28
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/bin/wrangler.js +1 -1
- package/miniflare-dist/index.mjs +15 -3
- package/package.json +8 -6
- package/src/__tests__/configuration.test.ts +33 -29
- package/src/__tests__/dev.test.tsx +8 -6
- package/src/__tests__/generate.test.ts +2 -4
- package/src/__tests__/helpers/mock-cfetch.ts +33 -0
- package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
- package/src/__tests__/helpers/mock-known-routes.ts +7 -0
- package/src/__tests__/index.test.ts +30 -30
- package/src/__tests__/init.test.ts +537 -359
- package/src/__tests__/jest.setup.ts +7 -0
- package/src/__tests__/metrics.test.ts +1 -1
- package/src/__tests__/pages.test.ts +14 -0
- package/src/__tests__/publish.test.ts +59 -18
- package/src/__tests__/r2.test.ts +4 -3
- package/src/__tests__/tail.test.ts +53 -3
- package/src/__tests__/test-old-node-version.js +3 -3
- package/src/__tests__/user.test.ts +11 -0
- package/src/__tests__/worker-namespace.test.ts +37 -35
- package/src/api/dev.ts +1 -0
- package/src/bundle.ts +1 -1
- package/src/cfetch/internal.ts +118 -1
- package/src/config/environment.ts +1 -1
- package/src/config/index.ts +4 -4
- package/src/config/validation-helpers.ts +19 -6
- package/src/config/validation.ts +11 -5
- package/src/config-cache.ts +2 -1
- package/src/create-worker-upload-form.ts +29 -26
- package/src/dev/dev.tsx +4 -0
- package/src/dev/remote.tsx +10 -1
- package/src/dev.tsx +36 -8
- package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
- package/src/generate.ts +1 -1
- package/src/index.tsx +54 -8
- package/src/init.ts +111 -38
- package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
- package/src/metrics/metrics-config.ts +1 -1
- package/src/metrics/send-event.ts +5 -5
- package/src/miniflare-cli/assets.ts +8 -0
- package/src/miniflare-cli/index.ts +6 -3
- package/src/pages/build.tsx +41 -15
- package/src/pages/constants.ts +1 -0
- package/src/pages/dev.tsx +93 -37
- package/src/pages/errors.ts +22 -0
- package/src/pages/functions/routes-consolidation.test.ts +185 -1
- package/src/pages/functions/routes-consolidation.ts +46 -2
- package/src/pages/functions/routes-transformation.ts +0 -3
- package/src/pages/functions.tsx +96 -0
- package/src/pages/index.tsx +65 -55
- package/src/pages/publish.tsx +27 -16
- package/src/proxy.ts +10 -0
- package/src/publish.ts +19 -4
- package/src/r2.ts +4 -4
- package/src/tail/filters.ts +3 -1
- package/src/tail/printing.ts +2 -0
- package/src/user/user.tsx +6 -4
- package/src/whoami.tsx +5 -5
- package/src/worker.ts +3 -2
- package/src/zones.ts +91 -0
- package/templates/pages-template-plugin.ts +16 -4
- package/templates/pages-template-worker.ts +16 -5
- package/templates/service-bindings-module-facade.js +10 -7
- package/templates/service-bindings-sw-facade.js +10 -7
- package/wrangler-dist/cli.d.ts +8 -3
- package/wrangler-dist/cli.js +6757 -1639
package/src/pages/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Build from "./build";
|
|
4
4
|
import * as Deployments from "./deployments";
|
|
5
5
|
import * as Dev from "./dev";
|
|
6
|
+
import * as Functions from "./functions";
|
|
6
7
|
import * as Projects from "./projects";
|
|
7
8
|
import * as Publish from "./publish";
|
|
8
9
|
import * as Upload from "./upload";
|
|
@@ -19,66 +20,75 @@ process.on("SIGTERM", () => {
|
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"create [project-name]",
|
|
51
|
-
"Create a new Cloudflare Pages project",
|
|
52
|
-
Projects.CreateOptions,
|
|
53
|
-
Projects.CreateHandler
|
|
54
|
-
)
|
|
55
|
-
.command("upload [directory]", false, Upload.Options, Upload.Handler)
|
|
56
|
-
.epilogue(pagesBetaWarning)
|
|
57
|
-
)
|
|
58
|
-
.command(
|
|
59
|
-
"deployment",
|
|
60
|
-
"🚀 Interact with the deployments of a project",
|
|
61
|
-
(yargs) =>
|
|
23
|
+
return (
|
|
24
|
+
yargs
|
|
25
|
+
.command(
|
|
26
|
+
"dev [directory] [-- command..]",
|
|
27
|
+
"🧑💻 Develop your full-stack Pages application locally",
|
|
28
|
+
Dev.Options,
|
|
29
|
+
Dev.Handler
|
|
30
|
+
)
|
|
31
|
+
/**
|
|
32
|
+
* `wrangler pages functions` is meant for internal use only for now,
|
|
33
|
+
* so let's hide this command from the help output
|
|
34
|
+
*/
|
|
35
|
+
.command("functions", false, (yargs) =>
|
|
36
|
+
yargs
|
|
37
|
+
.command(
|
|
38
|
+
"build [directory]",
|
|
39
|
+
"Compile a folder of Cloudflare Pages Functions into a single Worker",
|
|
40
|
+
Build.Options,
|
|
41
|
+
Build.Handler
|
|
42
|
+
)
|
|
43
|
+
.command(
|
|
44
|
+
"optimize-routes [routesPath] [outputRoutesPath]",
|
|
45
|
+
"Consolidate and optimize the route paths declared in _routes.json",
|
|
46
|
+
Functions.OptimizeRoutesOptions,
|
|
47
|
+
Functions.OptimizeRoutesHandler
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
.command("project", "⚡️ Interact with your Pages projects", (yargs) =>
|
|
62
51
|
yargs
|
|
63
52
|
.command(
|
|
64
53
|
"list",
|
|
65
|
-
"List
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
"List your Cloudflare Pages projects",
|
|
55
|
+
Projects.ListOptions,
|
|
56
|
+
Projects.ListHandler
|
|
68
57
|
)
|
|
69
58
|
.command(
|
|
70
|
-
"create [
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
"create [project-name]",
|
|
60
|
+
"Create a new Cloudflare Pages project",
|
|
61
|
+
Projects.CreateOptions,
|
|
62
|
+
Projects.CreateHandler
|
|
74
63
|
)
|
|
64
|
+
.command("upload [directory]", false, Upload.Options, Upload.Handler)
|
|
75
65
|
.epilogue(pagesBetaWarning)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
66
|
+
)
|
|
67
|
+
.command(
|
|
68
|
+
"deployment",
|
|
69
|
+
"🚀 Interact with the deployments of a project",
|
|
70
|
+
(yargs) =>
|
|
71
|
+
yargs
|
|
72
|
+
.command(
|
|
73
|
+
"list",
|
|
74
|
+
"List deployments in your Cloudflare Pages project",
|
|
75
|
+
Deployments.ListOptions,
|
|
76
|
+
Deployments.ListHandler
|
|
77
|
+
)
|
|
78
|
+
.command(
|
|
79
|
+
"create [directory]",
|
|
80
|
+
"🆙 Publish a directory of static assets as a Pages deployment",
|
|
81
|
+
Publish.Options,
|
|
82
|
+
Publish.Handler
|
|
83
|
+
)
|
|
84
|
+
.epilogue(pagesBetaWarning)
|
|
85
|
+
)
|
|
86
|
+
.command(
|
|
87
|
+
"publish [directory]",
|
|
88
|
+
"🆙 Publish a directory of static assets as a Pages deployment",
|
|
89
|
+
Publish.Options,
|
|
90
|
+
Publish.Handler
|
|
91
|
+
)
|
|
92
|
+
.epilogue(pagesBetaWarning)
|
|
93
|
+
);
|
|
84
94
|
};
|
package/src/pages/publish.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import * as metrics from "../metrics";
|
|
|
16
16
|
import { requireAuth } from "../user";
|
|
17
17
|
import { buildFunctions } from "./build";
|
|
18
18
|
import { PAGES_CONFIG_CACHE_FILENAME } from "./constants";
|
|
19
|
+
import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
|
|
19
20
|
import {
|
|
20
21
|
isRoutesJSONSpec,
|
|
21
22
|
optimizeRoutesJSONSpec,
|
|
@@ -256,18 +257,24 @@ export const Handler = async ({
|
|
|
256
257
|
const routesOutputPath = join(tmpdir(), `_routes-${Math.random()}.json`);
|
|
257
258
|
if (existsSync(functionsDirectory)) {
|
|
258
259
|
const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
buildFunctions({
|
|
260
|
+
try {
|
|
261
|
+
await buildFunctions({
|
|
262
262
|
outfile,
|
|
263
263
|
functionsDirectory,
|
|
264
|
-
onEnd: () =>
|
|
264
|
+
onEnd: () => {},
|
|
265
265
|
buildOutputDirectory: dirname(outfile),
|
|
266
266
|
routesOutputPath,
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
});
|
|
268
|
+
builtFunctions = readFileSync(outfile, "utf-8");
|
|
269
|
+
} catch (e) {
|
|
270
|
+
if (e instanceof FunctionsNoRoutesError) {
|
|
271
|
+
logger.warn(
|
|
272
|
+
getFunctionsNoRoutesWarning(functionsDirectory, "skipping")
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
const manifest = await upload({ directory, accountId, projectName });
|
|
@@ -305,32 +312,34 @@ export const Handler = async ({
|
|
|
305
312
|
_redirects = readFileSync(join(directory, "_redirects"), "utf-8");
|
|
306
313
|
} catch {}
|
|
307
314
|
|
|
308
|
-
try {
|
|
309
|
-
_routes = readFileSync(routesOutputPath, "utf-8");
|
|
310
|
-
} catch {}
|
|
311
|
-
|
|
312
315
|
try {
|
|
313
316
|
_workerJS = readFileSync(join(directory, "_worker.js"), "utf-8");
|
|
314
317
|
} catch {}
|
|
315
318
|
|
|
316
319
|
if (_headers) {
|
|
317
320
|
formData.append("_headers", new File([_headers], "_headers"));
|
|
321
|
+
logger.log(`✨ Uploading _headers`);
|
|
318
322
|
}
|
|
319
323
|
|
|
320
324
|
if (_redirects) {
|
|
321
325
|
formData.append("_redirects", new File([_redirects], "_redirects"));
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (_routes) {
|
|
325
|
-
formData.append("_routes.json", new File([_routes], "_routes.json"));
|
|
326
|
+
logger.log(`✨ Uploading _redirects`);
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
if (builtFunctions) {
|
|
329
330
|
formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
|
|
331
|
+
logger.log(`✨ Uploading Functions`);
|
|
332
|
+
try {
|
|
333
|
+
_routes = readFileSync(routesOutputPath, "utf-8");
|
|
334
|
+
if (_routes) {
|
|
335
|
+
formData.append("_routes.json", new File([_routes], "_routes.json"));
|
|
336
|
+
}
|
|
337
|
+
} catch {}
|
|
330
338
|
} else if (_workerJS) {
|
|
331
339
|
// Advanced Mode
|
|
332
340
|
// https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
|
|
333
341
|
formData.append("_worker.js", new File([_workerJS], "_worker.js"));
|
|
342
|
+
logger.log(`✨ Uploading _worker.js`);
|
|
334
343
|
|
|
335
344
|
try {
|
|
336
345
|
// In advanced mode, developers can specify a custom _routes.json
|
|
@@ -348,6 +357,8 @@ export const Handler = async ({
|
|
|
348
357
|
}
|
|
349
358
|
|
|
350
359
|
_routes = JSON.stringify(optimizeRoutesJSONSpec(advancedModeRoutes));
|
|
360
|
+
formData.append("_routes.json", new File([_routes], "_routes.json"));
|
|
361
|
+
logger.log(`✨ Uploading _routes.json`);
|
|
351
362
|
|
|
352
363
|
logger.warn(
|
|
353
364
|
`🚨 _routes.json is an experimental feature and is subject to change. Don't use unless you really must!`
|
package/src/proxy.ts
CHANGED
|
@@ -177,6 +177,7 @@ export function usePreviewServer({
|
|
|
177
177
|
const cleanupListeners: (() => void)[] = [];
|
|
178
178
|
|
|
179
179
|
// create a ClientHttp2Session
|
|
180
|
+
logger.debug("PREVIEW URL:", `https://${previewToken.host}`);
|
|
180
181
|
const remote = connect(`https://${previewToken.host}`);
|
|
181
182
|
cleanupListeners.push(() => remote.destroy());
|
|
182
183
|
|
|
@@ -221,6 +222,15 @@ export function usePreviewServer({
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
const request = message.pipe(remote.request(headers));
|
|
225
|
+
logger.debug(
|
|
226
|
+
"WORKER REQUEST",
|
|
227
|
+
new Date().toLocaleTimeString(),
|
|
228
|
+
method,
|
|
229
|
+
url
|
|
230
|
+
);
|
|
231
|
+
logger.debug("HEADERS", JSON.stringify(headers, null, 2));
|
|
232
|
+
logger.debug("PREVIEW TOKEN", previewToken);
|
|
233
|
+
|
|
224
234
|
request.on("response", (responseHeaders) => {
|
|
225
235
|
const status = responseHeaders[":status"] ?? 500;
|
|
226
236
|
|
package/src/publish.ts
CHANGED
|
@@ -36,6 +36,8 @@ type Props = {
|
|
|
36
36
|
compatibilityDate: string | undefined;
|
|
37
37
|
compatibilityFlags: string[] | undefined;
|
|
38
38
|
assetPaths: AssetPaths | undefined;
|
|
39
|
+
vars: Record<string, string> | undefined;
|
|
40
|
+
defines: Record<string, string> | undefined;
|
|
39
41
|
triggers: string[] | undefined;
|
|
40
42
|
routes: string[] | undefined;
|
|
41
43
|
legacyEnv: boolean | undefined;
|
|
@@ -382,7 +384,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
382
384
|
tsconfig: props.tsconfig ?? config.tsconfig,
|
|
383
385
|
minify,
|
|
384
386
|
nodeCompat,
|
|
385
|
-
define: config.define,
|
|
387
|
+
define: { ...config.define, ...props.defines },
|
|
386
388
|
checkFetch: false,
|
|
387
389
|
assets: config.assets && {
|
|
388
390
|
...config.assets,
|
|
@@ -430,7 +432,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
430
432
|
? { binding: "__STATIC_CONTENT", id: assets.namespace }
|
|
431
433
|
: []
|
|
432
434
|
),
|
|
433
|
-
vars: config.vars,
|
|
435
|
+
vars: { ...config.vars, ...props.vars },
|
|
434
436
|
wasm_modules: config.wasm_modules,
|
|
435
437
|
text_blobs: {
|
|
436
438
|
...config.text_blobs,
|
|
@@ -443,7 +445,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
443
445
|
durable_objects: config.durable_objects,
|
|
444
446
|
r2_buckets: config.r2_buckets,
|
|
445
447
|
services: config.services,
|
|
446
|
-
|
|
448
|
+
dispatch_namespaces: config.dispatch_namespaces,
|
|
447
449
|
logfwdr: config.logfwdr,
|
|
448
450
|
unsafe: config.unsafe?.bindings,
|
|
449
451
|
};
|
|
@@ -470,6 +472,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
470
472
|
compatibility_flags:
|
|
471
473
|
props.compatibilityFlags ?? config.compatibility_flags,
|
|
472
474
|
usage_model: config.usage_model,
|
|
475
|
+
keep_bindings: true,
|
|
473
476
|
};
|
|
474
477
|
|
|
475
478
|
void printBundleSize(
|
|
@@ -482,7 +485,19 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
|
|
|
482
485
|
kv_namespaces: config.kv_namespaces,
|
|
483
486
|
text_blobs: config.text_blobs,
|
|
484
487
|
};
|
|
485
|
-
|
|
488
|
+
|
|
489
|
+
// mask anything that was overridden in cli args
|
|
490
|
+
// so that we don't log potential secrets into the terminal
|
|
491
|
+
const maskedVars = { ...withoutStaticAssets.vars };
|
|
492
|
+
for (const key of Object.keys(maskedVars)) {
|
|
493
|
+
if (maskedVars[key] !== config.vars[key]) {
|
|
494
|
+
// This means it was overridden in cli args
|
|
495
|
+
// so let's mask it
|
|
496
|
+
maskedVars[key] = "(hidden)";
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
printBindings({ ...withoutStaticAssets, vars: maskedVars });
|
|
486
501
|
|
|
487
502
|
if (!props.dryRun) {
|
|
488
503
|
// Upload the script so it has time to propagate.
|
package/src/r2.ts
CHANGED
|
@@ -33,10 +33,10 @@ export async function createR2Bucket(
|
|
|
33
33
|
accountId: string,
|
|
34
34
|
bucketName: string
|
|
35
35
|
): Promise<void> {
|
|
36
|
-
return await fetchResult<void>(
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
);
|
|
36
|
+
return await fetchResult<void>(`/accounts/${accountId}/r2/buckets`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
body: JSON.stringify({ name: bucketName }),
|
|
39
|
+
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
package/src/tail/filters.ts
CHANGED
|
@@ -58,13 +58,14 @@ type OutcomeFilter = {
|
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* There are five possible outcomes we can get, three of which
|
|
61
|
-
* (exception, exceededCpu, and unknown) are considered errors
|
|
61
|
+
* (exception, exceededCpu, exceededMemory, and unknown) are considered errors
|
|
62
62
|
*/
|
|
63
63
|
export type Outcome =
|
|
64
64
|
| "ok"
|
|
65
65
|
| "canceled"
|
|
66
66
|
| "exception"
|
|
67
67
|
| "exceededCpu"
|
|
68
|
+
| "exceededMemory"
|
|
68
69
|
| "unknown";
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -210,6 +211,7 @@ function parseOutcome(
|
|
|
210
211
|
case "error":
|
|
211
212
|
outcomes.add("exception");
|
|
212
213
|
outcomes.add("exceededCpu");
|
|
214
|
+
outcomes.add("exceededMemory");
|
|
213
215
|
outcomes.add("unknown");
|
|
214
216
|
break;
|
|
215
217
|
|
package/src/tail/printing.ts
CHANGED
|
@@ -96,6 +96,8 @@ function prettifyOutcome(outcome: Outcome): string {
|
|
|
96
96
|
return "Canceled";
|
|
97
97
|
case "exceededCpu":
|
|
98
98
|
return "Exceeded CPU Limit";
|
|
99
|
+
case "exceededMemory":
|
|
100
|
+
return "Exceeded Memory Limit";
|
|
99
101
|
case "exception":
|
|
100
102
|
return "Exception Thrown";
|
|
101
103
|
case "unknown":
|
package/src/user/user.tsx
CHANGED
|
@@ -224,6 +224,7 @@ import {
|
|
|
224
224
|
saveToConfigCache,
|
|
225
225
|
} from "../config-cache";
|
|
226
226
|
import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
|
|
227
|
+
import { CI } from "../is-ci";
|
|
227
228
|
import isInteractive from "../is-interactive";
|
|
228
229
|
import { logger } from "../logger";
|
|
229
230
|
import openInBrowser from "../open-in-browser";
|
|
@@ -878,10 +879,11 @@ type LoginProps = {
|
|
|
878
879
|
export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
879
880
|
// TODO: if there already is a token, then try refreshing
|
|
880
881
|
// TODO: ask permission before opening browser
|
|
882
|
+
const { isCI } = CI;
|
|
881
883
|
if (!getAPIToken()) {
|
|
882
884
|
// Not logged in.
|
|
883
885
|
// If we are not interactive, we cannot ask the user to login
|
|
884
|
-
return isInteractive() && (await login());
|
|
886
|
+
return isInteractive() && !isCI() && (await login());
|
|
885
887
|
} else if (isAccessTokenExpired()) {
|
|
886
888
|
// We're logged in, but the refresh token seems to have expired,
|
|
887
889
|
// so let's try to refresh it
|
|
@@ -891,7 +893,7 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
891
893
|
return true;
|
|
892
894
|
} else {
|
|
893
895
|
// If the refresh token isn't valid, then we ask the user to login again
|
|
894
|
-
return isInteractive() && (await login());
|
|
896
|
+
return isInteractive() && !isCI() && (await login());
|
|
895
897
|
}
|
|
896
898
|
} else {
|
|
897
899
|
return true;
|
|
@@ -1088,7 +1090,7 @@ export async function getAccountId(): Promise<string | undefined> {
|
|
|
1088
1090
|
return accounts[0].id;
|
|
1089
1091
|
}
|
|
1090
1092
|
|
|
1091
|
-
if (isInteractive()) {
|
|
1093
|
+
if (isInteractive() && !CI.isCI()) {
|
|
1092
1094
|
const account = await new Promise<{ id: string; name: string }>(
|
|
1093
1095
|
(resolve, reject) => {
|
|
1094
1096
|
const { unmount } = render(
|
|
@@ -1128,7 +1130,7 @@ export async function requireAuth(config: {
|
|
|
1128
1130
|
}): Promise<string> {
|
|
1129
1131
|
const loggedIn = await loginOrRefreshIfRequired();
|
|
1130
1132
|
if (!loggedIn) {
|
|
1131
|
-
if (!isInteractive()) {
|
|
1133
|
+
if (!isInteractive() || CI.isCI()) {
|
|
1132
1134
|
throw new Error(
|
|
1133
1135
|
"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."
|
|
1134
1136
|
);
|
package/src/whoami.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Text, render } from "ink";
|
|
2
2
|
import Table from "ink-table";
|
|
3
|
-
import React from "react";
|
|
3
|
+
import React, { Fragment } from "react";
|
|
4
4
|
import { fetchListResult, fetchResult } from "./cfetch";
|
|
5
5
|
import { logger } from "./logger";
|
|
6
6
|
import { getAPIToken, getAuthFromEnv, getScopes } from "./user";
|
|
@@ -64,12 +64,12 @@ function Permissions(props: {
|
|
|
64
64
|
and re-login.
|
|
65
65
|
</Text>
|
|
66
66
|
<Text>Scope (Access)</Text>
|
|
67
|
-
{permissions.map(([
|
|
68
|
-
|
|
67
|
+
{permissions.map(([scope, access], index) => (
|
|
68
|
+
<Fragment key={`${scope}${index}`}>
|
|
69
69
|
<Text>
|
|
70
|
-
- {
|
|
70
|
+
- {scope} {access && `(${access})`}
|
|
71
71
|
</Text>
|
|
72
|
-
|
|
72
|
+
</Fragment>
|
|
73
73
|
))}
|
|
74
74
|
</>
|
|
75
75
|
) : null
|
package/src/worker.ts
CHANGED
|
@@ -122,7 +122,7 @@ interface CfService {
|
|
|
122
122
|
environment?: string;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
interface
|
|
125
|
+
interface CfDispatchNamespace {
|
|
126
126
|
binding: string;
|
|
127
127
|
namespace: string;
|
|
128
128
|
}
|
|
@@ -183,7 +183,7 @@ export interface CfWorkerInit {
|
|
|
183
183
|
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
184
184
|
r2_buckets: CfR2Bucket[] | undefined;
|
|
185
185
|
services: CfService[] | undefined;
|
|
186
|
-
|
|
186
|
+
dispatch_namespaces: CfDispatchNamespace[] | undefined;
|
|
187
187
|
logfwdr: CfLogfwdr | undefined;
|
|
188
188
|
unsafe: CfUnsafeBinding[] | undefined;
|
|
189
189
|
};
|
|
@@ -191,6 +191,7 @@ export interface CfWorkerInit {
|
|
|
191
191
|
compatibility_date: string | undefined;
|
|
192
192
|
compatibility_flags: string[] | undefined;
|
|
193
193
|
usage_model: "bundled" | "unbound" | undefined;
|
|
194
|
+
keep_bindings: boolean | undefined;
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
export interface CfWorkerContext {
|
package/src/zones.ts
CHANGED
|
@@ -74,3 +74,94 @@ export async function getZoneIdFromHost(host: string): Promise<string> {
|
|
|
74
74
|
|
|
75
75
|
throw new Error(`Could not find zone for ${host}`);
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* An object holding information about an assigned worker route, returned from the API
|
|
80
|
+
*/
|
|
81
|
+
interface WorkerRoute {
|
|
82
|
+
id: string;
|
|
83
|
+
pattern: string;
|
|
84
|
+
script: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Given a zone within the user's account, return a list of all assigned worker routes
|
|
89
|
+
*/
|
|
90
|
+
export async function getRoutesForZone(zone: string): Promise<WorkerRoute[]> {
|
|
91
|
+
const routes = await fetchListResult<WorkerRoute>(
|
|
92
|
+
`/zones/${zone}/workers/routes`
|
|
93
|
+
);
|
|
94
|
+
return routes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Given two strings, return the levenshtein distance between them as a simple text match heuristic
|
|
99
|
+
*/
|
|
100
|
+
function distanceBetween(a: string, b: string, cache = new Map()): number {
|
|
101
|
+
if (cache.has(`${a}|${b}`)) {
|
|
102
|
+
return cache.get(`${a}|${b}`);
|
|
103
|
+
}
|
|
104
|
+
let result;
|
|
105
|
+
if (b == "") {
|
|
106
|
+
result = a.length;
|
|
107
|
+
} else if (a == "") {
|
|
108
|
+
result = b.length;
|
|
109
|
+
} else if (a[0] === b[0]) {
|
|
110
|
+
result = distanceBetween(a.slice(1), b.slice(1), cache);
|
|
111
|
+
} else {
|
|
112
|
+
result =
|
|
113
|
+
1 +
|
|
114
|
+
Math.min(
|
|
115
|
+
distanceBetween(a.slice(1), b, cache),
|
|
116
|
+
distanceBetween(a, b.slice(1), cache),
|
|
117
|
+
distanceBetween(a.slice(1), b.slice(1), cache)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
cache.set(`${a}|${b}`, result);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Given an invalid route, sort the valid routes by closeness to the invalid route (levenstein distance)
|
|
126
|
+
*/
|
|
127
|
+
export function findClosestRoute(
|
|
128
|
+
providedRoute: string,
|
|
129
|
+
assignedRoutes: WorkerRoute[]
|
|
130
|
+
): WorkerRoute[] {
|
|
131
|
+
return assignedRoutes.sort((a, b) => {
|
|
132
|
+
const distanceA = distanceBetween(providedRoute, a.pattern);
|
|
133
|
+
const distanceB = distanceBetween(providedRoute, b.pattern);
|
|
134
|
+
return distanceA - distanceB;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Given a route (must be assigned and within the correct zone), return the name of the worker assigned to it
|
|
140
|
+
*/
|
|
141
|
+
export async function getWorkerForZone(worker: string) {
|
|
142
|
+
const zone = await getZoneForRoute(worker);
|
|
143
|
+
if (!zone) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`The route '${worker}' is not part of one of your zones. Either add this zone from the Cloudflare dashboard, or try using a route within one of your existing zones.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const routes = await getRoutesForZone(zone.id);
|
|
149
|
+
|
|
150
|
+
const scriptName = routes.find((route) => route.pattern === worker)?.script;
|
|
151
|
+
|
|
152
|
+
if (!scriptName) {
|
|
153
|
+
const closestRoute = findClosestRoute(worker, routes)?.[0];
|
|
154
|
+
|
|
155
|
+
if (!closestRoute) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`The route '${worker}' has no workers assigned. You can assign a worker to it from wrangler.toml or the Cloudflare dashboard`
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`The route '${worker}' has no workers assigned. Did you mean to tail the route '${closestRoute.pattern}'?`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return scriptName;
|
|
167
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { match } from "path-to-regexp";
|
|
2
2
|
|
|
3
|
+
//note: this explicitly does not include the * character, as pages requires this
|
|
4
|
+
const escapeRegex = /[.+?^${}()|[\]\\]/g;
|
|
5
|
+
|
|
3
6
|
type HTTPMethod =
|
|
4
7
|
| "HEAD"
|
|
5
8
|
| "OPTIONS"
|
|
@@ -67,8 +70,13 @@ function* executeRequest(request: Request, relativePathname: string) {
|
|
|
67
70
|
continue;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
const
|
|
73
|
+
// replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
|
|
74
|
+
const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
|
|
75
|
+
end: false,
|
|
76
|
+
});
|
|
77
|
+
const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
|
|
78
|
+
end: false,
|
|
79
|
+
});
|
|
72
80
|
const matchResult = routeMatcher(relativePathname);
|
|
73
81
|
const mountMatchResult = mountMatcher(relativePathname);
|
|
74
82
|
if (matchResult && mountMatchResult) {
|
|
@@ -88,8 +96,12 @@ function* executeRequest(request: Request, relativePathname: string) {
|
|
|
88
96
|
continue;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
const routeMatcher = match(route.routePath, {
|
|
92
|
-
|
|
99
|
+
const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
|
|
100
|
+
end: true,
|
|
101
|
+
});
|
|
102
|
+
const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
|
|
103
|
+
end: false,
|
|
104
|
+
});
|
|
93
105
|
const matchResult = routeMatcher(relativePathname);
|
|
94
106
|
const mountMatchResult = mountMatcher(relativePathname);
|
|
95
107
|
if (matchResult && mountMatchResult && route.modules.length) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { match } from "path-to-regexp";
|
|
2
2
|
|
|
3
|
+
//note: this explicitly does not include the * character, as pages requires this
|
|
4
|
+
const escapeRegex = /[.+?^${}()|[\]\\]/g;
|
|
5
|
+
|
|
3
6
|
type HTTPMethod =
|
|
4
7
|
| "HEAD"
|
|
5
8
|
| "OPTIONS"
|
|
@@ -61,8 +64,13 @@ function* executeRequest(request: Request) {
|
|
|
61
64
|
continue;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
const
|
|
67
|
+
// replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
|
|
68
|
+
const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
|
|
69
|
+
end: false,
|
|
70
|
+
});
|
|
71
|
+
const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
|
|
72
|
+
end: false,
|
|
73
|
+
});
|
|
66
74
|
const matchResult = routeMatcher(requestPath);
|
|
67
75
|
const mountMatchResult = mountMatcher(requestPath);
|
|
68
76
|
if (matchResult && mountMatchResult) {
|
|
@@ -81,9 +89,12 @@ function* executeRequest(request: Request) {
|
|
|
81
89
|
if (route.method && route.method !== request.method) {
|
|
82
90
|
continue;
|
|
83
91
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
|
|
93
|
+
end: true,
|
|
94
|
+
});
|
|
95
|
+
const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
|
|
96
|
+
end: false,
|
|
97
|
+
});
|
|
87
98
|
const matchResult = routeMatcher(requestPath);
|
|
88
99
|
const mountMatchResult = mountMatcher(requestPath);
|
|
89
100
|
if (matchResult && mountMatchResult && route.modules.length) {
|
|
@@ -16,20 +16,23 @@ export default {
|
|
|
16
16
|
if (details) {
|
|
17
17
|
facadeEnv[name] = {
|
|
18
18
|
async fetch(...reqArgs) {
|
|
19
|
+
const reqFromArgs = new Request(...reqArgs);
|
|
19
20
|
if (details.headers) {
|
|
20
|
-
const req = new Request(...reqArgs);
|
|
21
21
|
for (const [key, value] of Object.entries(details.headers)) {
|
|
22
22
|
// In remote mode, you need to add a couple of headers
|
|
23
23
|
// to make sure it's talking to the 'dev' preview session
|
|
24
24
|
// (much like wrangler dev already does via proxy.ts)
|
|
25
|
-
|
|
25
|
+
reqFromArgs.headers.set(key, value);
|
|
26
26
|
}
|
|
27
|
-
return env[name].fetch(
|
|
27
|
+
return env[name].fetch(reqFromArgs);
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
|
|
30
|
+
const url = new URL(reqFromArgs.url);
|
|
31
|
+
url.protocol = details.protocol;
|
|
32
|
+
url.host = details.host;
|
|
33
|
+
if (details.port !== undefined) url.port = details.port;
|
|
34
|
+
|
|
35
|
+
const request = new Request(url.toString(), reqFromArgs);
|
|
33
36
|
return fetch(request);
|
|
34
37
|
},
|
|
35
38
|
};
|