wrangler 0.0.0-eebdb6c → 0.0.0-ef13735
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 +2 -2
- package/package.json +19 -12
- package/pages/functions/buildWorker.ts +6 -1
- package/pages/functions/filepath-routing.test.ts +43 -9
- package/pages/functions/filepath-routing.ts +57 -63
- package/pages/functions/identifiers.ts +1 -1
- package/pages/functions/routes.ts +18 -25
- package/pages/functions/template-worker.ts +27 -32
- package/src/__tests__/dev.test.tsx +73 -0
- package/src/__tests__/guess-worker-format.test.ts +51 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/mock-account-id.ts +30 -0
- package/src/__tests__/helpers/mock-bin.ts +33 -0
- package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +52 -16
- package/src/__tests__/helpers/mock-console.ts +56 -0
- package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/run-in-tmp.ts +29 -0
- package/src/__tests__/helpers/run-wrangler.ts +14 -0
- package/src/__tests__/index.test.ts +519 -57
- package/src/__tests__/jest.setup.ts +17 -2
- package/src/__tests__/kv.test.ts +361 -320
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1381 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/whoami.test.tsx +86 -0
- package/src/api/form_data.ts +27 -7
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +24 -10
- package/src/cfetch/index.ts +14 -15
- package/src/cfetch/internal.ts +43 -11
- package/src/config.ts +640 -118
- package/src/dev.tsx +291 -313
- package/src/dialogs.tsx +2 -2
- package/src/guess-worker-format.ts +66 -0
- package/src/index.tsx +598 -325
- package/src/inspect.ts +596 -0
- package/src/kv.tsx +27 -10
- package/src/module-collection.ts +7 -8
- package/src/open-in-browser.ts +13 -0
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +340 -217
- package/src/paths.ts +26 -0
- package/src/proxy.ts +344 -77
- package/src/publish.ts +344 -281
- package/src/sites.tsx +184 -54
- package/src/user.tsx +73 -76
- package/src/whoami.tsx +69 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/wrangler-dist/cli.js +110741 -109055
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/run-in-tmp.ts +0 -19
- package/src/__tests__/run-wrangler.ts +0 -32
- package/src/api/inspect.ts +0 -430
package/bin/wrangler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { spawn } = require("child_process");
|
|
3
|
-
const { join } = require("path");
|
|
2
|
+
const { spawn } = require("node:child_process");
|
|
3
|
+
const { join } = require("node:path");
|
|
4
4
|
const semiver = require("semiver");
|
|
5
5
|
|
|
6
6
|
const MIN_NODE_VERSION = "16.7.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wrangler",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-ef13735",
|
|
4
4
|
"author": "wrangler@cloudflare.com",
|
|
5
5
|
"description": "Command-line interface for all things Cloudflare Workers",
|
|
6
6
|
"bin": {
|
|
@@ -36,10 +36,11 @@
|
|
|
36
36
|
"cli"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"esbuild": "0.14.
|
|
40
|
-
"miniflare": "2.
|
|
39
|
+
"esbuild": "0.14.14",
|
|
40
|
+
"miniflare": "2.3.0",
|
|
41
41
|
"path-to-regexp": "^6.2.0",
|
|
42
|
-
"semiver": "^1.1.0"
|
|
42
|
+
"semiver": "^1.1.0",
|
|
43
|
+
"xxhash-wasm": "^1.0.1"
|
|
43
44
|
},
|
|
44
45
|
"optionalDependencies": {
|
|
45
46
|
"fsevents": "~2.3.2"
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@babel/types": "^7.16.0",
|
|
49
50
|
"@iarna/toml": "^2.2.5",
|
|
50
|
-
"@types/mime": "^2.0.3",
|
|
51
51
|
"@types/estree": "^0.0.50",
|
|
52
|
+
"@types/mime": "^2.0.3",
|
|
52
53
|
"@types/react": "^17.0.37",
|
|
53
54
|
"@types/serve-static": "^1.13.10",
|
|
54
55
|
"@types/signal-exit": "^3.0.1",
|
|
@@ -58,25 +59,28 @@
|
|
|
58
59
|
"acorn-walk": "^8.2.0",
|
|
59
60
|
"chokidar": "^3.5.2",
|
|
60
61
|
"clipboardy": "^3.0.0",
|
|
62
|
+
"cmd-shim": "^4.1.0",
|
|
61
63
|
"command-exists": "^1.2.9",
|
|
64
|
+
"devtools-protocol": "^0.0.955664",
|
|
62
65
|
"execa": "^6.0.0",
|
|
63
66
|
"faye-websocket": "^0.11.4",
|
|
64
67
|
"finalhandler": "^1.1.2",
|
|
65
68
|
"find-up": "^6.2.0",
|
|
66
|
-
"
|
|
69
|
+
"ignore": "^5.2.0",
|
|
67
70
|
"ink": "^3.2.0",
|
|
68
71
|
"ink-select-input": "^4.2.1",
|
|
69
72
|
"ink-table": "^3.0.0",
|
|
73
|
+
"ink-testing-library": "^2.1.0",
|
|
70
74
|
"ink-text-input": "^4.0.2",
|
|
75
|
+
"jest-fetch-mock": "^3.0.3",
|
|
71
76
|
"mime": "^3.0.0",
|
|
72
|
-
"node-fetch": "^3.1.0",
|
|
73
77
|
"open": "^8.4.0",
|
|
74
78
|
"react": "^17.0.2",
|
|
75
79
|
"react-error-boundary": "^3.1.4",
|
|
76
80
|
"serve-static": "^1.14.1",
|
|
77
81
|
"signal-exit": "^3.0.6",
|
|
78
82
|
"tmp-promise": "^3.0.3",
|
|
79
|
-
"undici": "
|
|
83
|
+
"undici": "4.13.0",
|
|
80
84
|
"ws": "^8.3.0",
|
|
81
85
|
"yargs": "^17.3.0"
|
|
82
86
|
},
|
|
@@ -86,28 +90,31 @@
|
|
|
86
90
|
"pages",
|
|
87
91
|
"miniflare-config-stubs",
|
|
88
92
|
"wrangler-dist",
|
|
89
|
-
"
|
|
93
|
+
"templates",
|
|
90
94
|
"vendor",
|
|
91
95
|
"import_meta_url.js"
|
|
92
96
|
],
|
|
93
97
|
"scripts": {
|
|
94
98
|
"clean": "rm -rf wrangler-dist",
|
|
99
|
+
"check:type": "tsc",
|
|
95
100
|
"bundle": "node -r esbuild-register scripts/bundle.ts",
|
|
96
101
|
"build": "npm run clean && npm run bundle",
|
|
97
102
|
"start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
|
|
98
|
-
"test": "
|
|
103
|
+
"test": "jest --silent=false --verbose=true",
|
|
104
|
+
"test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch"
|
|
99
105
|
},
|
|
100
106
|
"engines": {
|
|
101
107
|
"node": ">=16.7.0"
|
|
102
108
|
},
|
|
103
109
|
"jest": {
|
|
104
110
|
"restoreMocks": true,
|
|
111
|
+
"testTimeout": 30000,
|
|
105
112
|
"testRegex": ".*.(test|spec)\\.[jt]sx?$",
|
|
106
113
|
"transformIgnorePatterns": [
|
|
107
|
-
"node_modules/(?!
|
|
114
|
+
"node_modules/(?!find-up|locate-path|p-locate|p-limit|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream)"
|
|
108
115
|
],
|
|
109
116
|
"moduleNameMapper": {
|
|
110
|
-
"clipboardy": "<rootDir>/src/__tests__/clipboardy-mock.js"
|
|
117
|
+
"clipboardy": "<rootDir>/src/__tests__/helpers/clipboardy-mock.js"
|
|
111
118
|
},
|
|
112
119
|
"transform": {
|
|
113
120
|
"^.+\\.c?(t|j)sx?$": [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import path from "path";
|
|
1
|
+
import path from "node:path";
|
|
2
2
|
import { build } from "esbuild";
|
|
3
3
|
|
|
4
4
|
type Options = {
|
|
@@ -6,6 +6,7 @@ type Options = {
|
|
|
6
6
|
outfile: string;
|
|
7
7
|
minify?: boolean;
|
|
8
8
|
sourcemap?: boolean;
|
|
9
|
+
fallbackService?: string;
|
|
9
10
|
watch?: boolean;
|
|
10
11
|
onEnd?: () => void;
|
|
11
12
|
};
|
|
@@ -15,6 +16,7 @@ export function buildWorker({
|
|
|
15
16
|
outfile = "bundle.js",
|
|
16
17
|
minify = false,
|
|
17
18
|
sourcemap = false,
|
|
19
|
+
fallbackService = "ASSETS",
|
|
18
20
|
watch = false,
|
|
19
21
|
onEnd = () => {},
|
|
20
22
|
}: Options) {
|
|
@@ -31,6 +33,9 @@ export function buildWorker({
|
|
|
31
33
|
sourcemap,
|
|
32
34
|
watch,
|
|
33
35
|
allowOverwrite: true,
|
|
36
|
+
define: {
|
|
37
|
+
__FALLBACK_SERVICE__: JSON.stringify(fallbackService),
|
|
38
|
+
},
|
|
34
39
|
plugins: [
|
|
35
40
|
{
|
|
36
41
|
name: "wrangler notifier and monitor",
|
|
@@ -1,32 +1,66 @@
|
|
|
1
|
+
import { toUrlPath } from "../../src/paths";
|
|
1
2
|
import { compareRoutes } from "./filepath-routing";
|
|
3
|
+
import type { HTTPMethod, RouteConfig } from "./routes";
|
|
2
4
|
|
|
3
5
|
describe("compareRoutes()", () => {
|
|
6
|
+
test("routes / last", () => {
|
|
7
|
+
expect(
|
|
8
|
+
compareRoutes(routeConfig("/"), routeConfig("/foo"))
|
|
9
|
+
).toBeGreaterThanOrEqual(1);
|
|
10
|
+
expect(
|
|
11
|
+
compareRoutes(routeConfig("/"), routeConfig("/:foo"))
|
|
12
|
+
).toBeGreaterThanOrEqual(1);
|
|
13
|
+
expect(
|
|
14
|
+
compareRoutes(routeConfig("/"), routeConfig("/:foo*"))
|
|
15
|
+
).toBeGreaterThanOrEqual(1);
|
|
16
|
+
});
|
|
17
|
+
|
|
4
18
|
test("routes with fewer segments come after those with more segments", () => {
|
|
5
|
-
expect(
|
|
19
|
+
expect(
|
|
20
|
+
compareRoutes(routeConfig("/foo"), routeConfig("/foo/bar"))
|
|
21
|
+
).toBeGreaterThanOrEqual(1);
|
|
22
|
+
expect(
|
|
23
|
+
compareRoutes(routeConfig("/foo"), routeConfig("/foo/bar/cat"))
|
|
24
|
+
).toBeGreaterThanOrEqual(1);
|
|
6
25
|
});
|
|
7
26
|
|
|
8
27
|
test("routes with wildcard segments come after those without", () => {
|
|
9
|
-
expect(compareRoutes("/:foo*", "/foo")).toBe(1);
|
|
10
|
-
expect(compareRoutes("/:foo*", "/:foo")).toBe(1);
|
|
28
|
+
expect(compareRoutes(routeConfig("/:foo*"), routeConfig("/foo"))).toBe(1);
|
|
29
|
+
expect(compareRoutes(routeConfig("/:foo*"), routeConfig("/:foo"))).toBe(1);
|
|
11
30
|
});
|
|
12
31
|
|
|
13
32
|
test("routes with dynamic segments come after those without", () => {
|
|
14
|
-
expect(compareRoutes("/:foo", "/foo")).toBe(1);
|
|
33
|
+
expect(compareRoutes(routeConfig("/:foo"), routeConfig("/foo"))).toBe(1);
|
|
15
34
|
});
|
|
16
35
|
|
|
17
|
-
test("routes with dynamic segments
|
|
18
|
-
expect(
|
|
36
|
+
test("routes with dynamic segments occurring earlier come after those with dynamic segments in later positions", () => {
|
|
37
|
+
expect(
|
|
38
|
+
compareRoutes(routeConfig("/foo/:id/bar"), routeConfig("/foo/bar/:id"))
|
|
39
|
+
).toBe(1);
|
|
19
40
|
});
|
|
20
41
|
|
|
21
42
|
test("routes with no HTTP method come after those specifying a method", () => {
|
|
22
|
-
expect(compareRoutes("/foo", "
|
|
43
|
+
expect(compareRoutes(routeConfig("/foo"), routeConfig("/foo", "GET"))).toBe(
|
|
44
|
+
1
|
|
45
|
+
);
|
|
23
46
|
});
|
|
24
47
|
|
|
25
48
|
test("two equal routes are sorted according to their original position in the list", () => {
|
|
26
|
-
expect(
|
|
49
|
+
expect(
|
|
50
|
+
compareRoutes(routeConfig("/foo", "GET"), routeConfig("/foo", "GET"))
|
|
51
|
+
).toBe(0);
|
|
27
52
|
});
|
|
28
53
|
|
|
29
54
|
test("it returns -1 if the first argument should appear first in the list", () => {
|
|
30
|
-
expect(compareRoutes("
|
|
55
|
+
expect(compareRoutes(routeConfig("/foo", "GET"), routeConfig("/foo"))).toBe(
|
|
56
|
+
-1
|
|
57
|
+
);
|
|
31
58
|
});
|
|
32
59
|
});
|
|
60
|
+
|
|
61
|
+
function routeConfig(routePath: string, method?: string): RouteConfig {
|
|
62
|
+
return {
|
|
63
|
+
routePath: toUrlPath(routePath),
|
|
64
|
+
method: method as HTTPMethod,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -1,39 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import path from "path";
|
|
4
|
-
import fs from "fs/promises";
|
|
5
|
-
import { transform } from "esbuild";
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
6
3
|
import * as acorn from "acorn";
|
|
7
4
|
import * as acornWalk from "acorn-walk";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
type
|
|
13
|
-
baseDir: string;
|
|
14
|
-
baseURL: string;
|
|
15
|
-
};
|
|
5
|
+
import { transform } from "esbuild";
|
|
6
|
+
import { toUrlPath } from "../../src/paths";
|
|
7
|
+
import type { UrlPath } from "../../src/paths";
|
|
8
|
+
import type { HTTPMethod, RouteConfig } from "./routes";
|
|
9
|
+
import type { ExportNamedDeclaration, Identifier } from "estree";
|
|
16
10
|
|
|
17
11
|
export async function generateConfigFromFileTree({
|
|
18
12
|
baseDir,
|
|
19
13
|
baseURL,
|
|
20
|
-
}:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
}: {
|
|
15
|
+
baseDir: string;
|
|
16
|
+
baseURL: UrlPath;
|
|
17
|
+
}) {
|
|
18
|
+
let routeEntries: RouteConfig[] = [];
|
|
25
19
|
|
|
26
20
|
if (!baseURL.startsWith("/")) {
|
|
27
|
-
baseURL = `/${baseURL}
|
|
21
|
+
baseURL = `/${baseURL}` as UrlPath;
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
if (baseURL.endsWith("/")) {
|
|
31
|
-
baseURL = baseURL.slice(0, -1);
|
|
25
|
+
baseURL = baseURL.slice(0, -1) as UrlPath;
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
await forEachFile(baseDir, async (filepath) => {
|
|
35
29
|
const ext = path.extname(filepath);
|
|
36
|
-
if (
|
|
30
|
+
if (/^\.(mjs|js|ts|tsx|jsx)$/.test(ext)) {
|
|
37
31
|
// transform the code to ensure we're working with vanilla JS + ESM
|
|
38
32
|
const { code } = await transform(await fs.readFile(filepath, "utf-8"), {
|
|
39
33
|
loader: ext === ".ts" ? "ts" : "js",
|
|
@@ -45,8 +39,10 @@ export async function generateConfigFromFileTree({
|
|
|
45
39
|
sourceType: "module",
|
|
46
40
|
});
|
|
47
41
|
acornWalk.simple(ast, {
|
|
48
|
-
ExportNamedDeclaration(_node) {
|
|
49
|
-
|
|
42
|
+
ExportNamedDeclaration(_node: unknown) {
|
|
43
|
+
// This dynamic cast assumes that the AST generated by acornWalk will generate nodes that
|
|
44
|
+
// are compatible with the eslint AST nodes.
|
|
45
|
+
const node = _node as ExportNamedDeclaration;
|
|
50
46
|
|
|
51
47
|
// this is an array because multiple things can be exported from a single statement
|
|
52
48
|
// i.e. `export {foo, bar}` or `export const foo = "f", bar = "b"`
|
|
@@ -56,7 +52,7 @@ export async function generateConfigFromFileTree({
|
|
|
56
52
|
const declaration = node.declaration;
|
|
57
53
|
|
|
58
54
|
// `export async function onRequest() {...}`
|
|
59
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
55
|
+
if (declaration.type === "FunctionDeclaration" && declaration.id) {
|
|
60
56
|
exportNames.push(declaration.id.name);
|
|
61
57
|
}
|
|
62
58
|
|
|
@@ -82,10 +78,9 @@ export async function generateConfigFromFileTree({
|
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
for (const exportName of exportNames) {
|
|
85
|
-
const [match, method] =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
) ?? [];
|
|
81
|
+
const [match, method = ""] = (exportName.match(
|
|
82
|
+
/^onRequest(Get|Post|Put|Patch|Delete|Options|Head)?$/
|
|
83
|
+
) ?? []) as (string | undefined)[];
|
|
89
84
|
|
|
90
85
|
if (match) {
|
|
91
86
|
const basename = path.basename(filepath).slice(0, -ext.length);
|
|
@@ -112,18 +107,15 @@ export async function generateConfigFromFileTree({
|
|
|
112
107
|
routePath = routePath.replace(/\[\[(.+)]]/g, ":$1*"); // transform [[id]] => :id*
|
|
113
108
|
routePath = routePath.replace(/\[(.+)]/g, ":$1"); // transform [id] => :id
|
|
114
109
|
|
|
115
|
-
|
|
116
|
-
routePath
|
|
117
|
-
|
|
110
|
+
const routeEntry: RouteConfig = {
|
|
111
|
+
routePath: toUrlPath(routePath),
|
|
112
|
+
method: method.toUpperCase() as HTTPMethod,
|
|
113
|
+
[isMiddlewareFile ? "middleware" : "module"]: [
|
|
114
|
+
`${path.relative(baseDir, filepath)}:${exportName}`,
|
|
115
|
+
],
|
|
116
|
+
};
|
|
118
117
|
|
|
119
|
-
routeEntries.push(
|
|
120
|
-
routePath,
|
|
121
|
-
{
|
|
122
|
-
[isMiddlewareFile ? "middleware" : "module"]: [
|
|
123
|
-
`${path.relative(baseDir, filepath)}:${exportName}`,
|
|
124
|
-
],
|
|
125
|
-
},
|
|
126
|
-
]);
|
|
118
|
+
routeEntries.push(routeEntry);
|
|
127
119
|
}
|
|
128
120
|
}
|
|
129
121
|
},
|
|
@@ -133,45 +125,40 @@ export async function generateConfigFromFileTree({
|
|
|
133
125
|
|
|
134
126
|
// Combine together any routes (index routes) which contain both a module and a middleware
|
|
135
127
|
routeEntries = routeEntries.reduce(
|
|
136
|
-
(acc: typeof routeEntries,
|
|
128
|
+
(acc: typeof routeEntries, { routePath, ...rest }) => {
|
|
137
129
|
const existingRouteEntry = acc.find(
|
|
138
|
-
(routeEntry) => routeEntry
|
|
130
|
+
(routeEntry) => routeEntry.routePath === routePath
|
|
139
131
|
);
|
|
140
132
|
if (existingRouteEntry !== undefined) {
|
|
141
|
-
existingRouteEntry
|
|
142
|
-
...existingRouteEntry[1],
|
|
143
|
-
...routeHandler,
|
|
144
|
-
};
|
|
133
|
+
Object.assign(existingRouteEntry, rest);
|
|
145
134
|
} else {
|
|
146
|
-
acc.push(
|
|
135
|
+
acc.push({ routePath, ...rest });
|
|
147
136
|
}
|
|
148
137
|
return acc;
|
|
149
138
|
},
|
|
150
139
|
[]
|
|
151
140
|
);
|
|
152
141
|
|
|
153
|
-
routeEntries.sort((
|
|
142
|
+
routeEntries.sort((a, b) => compareRoutes(a, b));
|
|
154
143
|
|
|
155
|
-
return {
|
|
144
|
+
return {
|
|
145
|
+
routes: routeEntries,
|
|
146
|
+
};
|
|
156
147
|
}
|
|
157
148
|
|
|
158
149
|
// Ensure routes are produced in order of precedence so that
|
|
159
150
|
// more specific routes aren't occluded from matching due to
|
|
160
151
|
// less specific routes appearing first in the route list.
|
|
161
|
-
export function compareRoutes(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const segments = segmentedPath.slice(1).split("/");
|
|
170
|
-
return [method, segments];
|
|
152
|
+
export function compareRoutes(
|
|
153
|
+
{ routePath: routePathA, method: methodA }: RouteConfig,
|
|
154
|
+
{ routePath: routePathB, method: methodB }: RouteConfig
|
|
155
|
+
) {
|
|
156
|
+
function parseRoutePath(routePath: UrlPath): string[] {
|
|
157
|
+
return routePath.slice(1).split("/").filter(Boolean);
|
|
171
158
|
}
|
|
172
159
|
|
|
173
|
-
const
|
|
174
|
-
const
|
|
160
|
+
const segmentsA = parseRoutePath(routePathA);
|
|
161
|
+
const segmentsB = parseRoutePath(routePathB);
|
|
175
162
|
|
|
176
163
|
// sort routes with fewer segments after those with more segments
|
|
177
164
|
if (segmentsA.length !== segmentsB.length) {
|
|
@@ -197,8 +184,8 @@ export function compareRoutes(a: string, b: string) {
|
|
|
197
184
|
if (methodA && !methodB) return -1;
|
|
198
185
|
if (!methodA && methodB) return 1;
|
|
199
186
|
|
|
200
|
-
// all else equal, just sort
|
|
201
|
-
return
|
|
187
|
+
// all else equal, just sort the paths lexicographically
|
|
188
|
+
return routePathA.localeCompare(routePathB);
|
|
202
189
|
}
|
|
203
190
|
|
|
204
191
|
async function forEachFile<T>(
|
|
@@ -208,7 +195,7 @@ async function forEachFile<T>(
|
|
|
208
195
|
const searchPaths = [baseDir];
|
|
209
196
|
const returnValues: T[] = [];
|
|
210
197
|
|
|
211
|
-
while (searchPaths
|
|
198
|
+
while (isNotEmpty(searchPaths)) {
|
|
212
199
|
const cwd = searchPaths.shift();
|
|
213
200
|
const dir = await fs.readdir(cwd, { withFileTypes: true });
|
|
214
201
|
for (const entry of dir) {
|
|
@@ -223,3 +210,10 @@ async function forEachFile<T>(
|
|
|
223
210
|
|
|
224
211
|
return returnValues;
|
|
225
212
|
}
|
|
213
|
+
|
|
214
|
+
interface NonEmptyArray<T> extends Array<T> {
|
|
215
|
+
shift(): T;
|
|
216
|
+
}
|
|
217
|
+
function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
|
|
218
|
+
return array.length > 0;
|
|
219
|
+
}
|
|
@@ -68,7 +68,7 @@ export const validIdentifierRegex = new RegExp(
|
|
|
68
68
|
"u"
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
-
export const
|
|
71
|
+
export const isValidIdentifier = (identifier: string) =>
|
|
72
72
|
validIdentifierRegex.test(identifier);
|
|
73
73
|
|
|
74
74
|
export const normalizeIdentifier = (identifier: string) =>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { isValidIdentifer, normalizeIdentifier } from "./identifiers";
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isValidIdentifier, normalizeIdentifier } from "./identifiers";
|
|
4
|
+
import type { UrlPath } from "../../src/paths";
|
|
6
5
|
|
|
7
6
|
export const HTTP_METHODS = [
|
|
8
7
|
"HEAD",
|
|
@@ -17,26 +16,26 @@ export type HTTPMethod = typeof HTTP_METHODS[number];
|
|
|
17
16
|
export function isHTTPMethod(
|
|
18
17
|
maybeHTTPMethod: string
|
|
19
18
|
): maybeHTTPMethod is HTTPMethod {
|
|
20
|
-
return HTTP_METHODS.includes(maybeHTTPMethod
|
|
19
|
+
return (HTTP_METHODS as readonly string[]).includes(maybeHTTPMethod);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
export type RoutesCollection = Array<{
|
|
24
|
-
routePath:
|
|
25
|
-
|
|
23
|
+
routePath: UrlPath;
|
|
24
|
+
method?: HTTPMethod;
|
|
26
25
|
modules: string[];
|
|
27
26
|
middlewares: string[];
|
|
28
27
|
}>;
|
|
29
28
|
|
|
30
29
|
export type Config = {
|
|
31
|
-
routes?:
|
|
32
|
-
schedules?:
|
|
30
|
+
routes?: RouteConfig[];
|
|
31
|
+
schedules?: unknown;
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
export type
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
export type RouteConfig = {
|
|
35
|
+
routePath: UrlPath;
|
|
36
|
+
method?: HTTPMethod;
|
|
37
|
+
middleware?: string | string[];
|
|
38
|
+
module?: string | string[];
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
type ImportMap = Map<
|
|
@@ -93,7 +92,7 @@ export function parseConfig(config: Config, baseDir: string) {
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
// ensure the module name (if provided) is a valid identifier to guard against injection attacks
|
|
96
|
-
if (name !== "default" && !
|
|
95
|
+
if (name !== "default" && !isValidIdentifier(name)) {
|
|
97
96
|
throw new Error(`Invalid module identifier "${name}"`);
|
|
98
97
|
}
|
|
99
98
|
|
|
@@ -114,16 +113,10 @@ export function parseConfig(config: Config, baseDir: string) {
|
|
|
114
113
|
});
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
for (const
|
|
118
|
-
let [_methods, routePath] = route.split(" ");
|
|
119
|
-
if (!routePath) {
|
|
120
|
-
routePath = _methods;
|
|
121
|
-
_methods = "";
|
|
122
|
-
}
|
|
123
|
-
|
|
116
|
+
for (const { routePath, method, ...props } of config.routes ?? []) {
|
|
124
117
|
routes.push({
|
|
125
118
|
routePath,
|
|
126
|
-
|
|
119
|
+
method,
|
|
127
120
|
middlewares: parseModuleIdentifiers(props.middleware),
|
|
128
121
|
modules: parseModuleIdentifiers(props.module),
|
|
129
122
|
});
|
|
@@ -148,7 +141,7 @@ export const routes = [
|
|
|
148
141
|
.map(
|
|
149
142
|
(route) => ` {
|
|
150
143
|
routePath: "${route.routePath}",
|
|
151
|
-
|
|
144
|
+
method: "${route.method}",
|
|
152
145
|
middlewares: [${route.middlewares.join(", ")}],
|
|
153
146
|
modules: [${route.modules.join(", ")}],
|
|
154
147
|
},`
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
|
|
3
1
|
import { match } from "path-to-regexp";
|
|
4
2
|
import type { HTTPMethod } from "./routes";
|
|
5
3
|
|
|
6
4
|
/* TODO: Grab these from @cloudflare/workers-types instead */
|
|
7
|
-
type Params<P extends string =
|
|
5
|
+
type Params<P extends string = string> = Record<P, string | string[]>;
|
|
8
6
|
|
|
9
7
|
type EventContext<Env, P extends string, Data> = {
|
|
10
8
|
request: Request;
|
|
11
|
-
waitUntil: (promise: Promise<
|
|
9
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
12
10
|
next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
|
|
13
11
|
env: Env & { ASSETS: { fetch: typeof fetch } };
|
|
14
12
|
params: Params<P>;
|
|
@@ -17,40 +15,39 @@ type EventContext<Env, P extends string, Data> = {
|
|
|
17
15
|
|
|
18
16
|
declare type PagesFunction<
|
|
19
17
|
Env = unknown,
|
|
20
|
-
P extends string =
|
|
18
|
+
P extends string = string,
|
|
21
19
|
Data extends Record<string, unknown> = Record<string, unknown>
|
|
22
20
|
> = (context: EventContext<Env, P, Data>) => Response | Promise<Response>;
|
|
23
21
|
/* end @cloudflare/workers-types */
|
|
24
22
|
|
|
25
23
|
type RouteHandler = {
|
|
26
24
|
routePath: string;
|
|
27
|
-
|
|
25
|
+
method?: HTTPMethod;
|
|
28
26
|
modules: PagesFunction[];
|
|
29
27
|
middlewares: PagesFunction[];
|
|
30
28
|
};
|
|
31
29
|
|
|
32
30
|
// inject `routes` via ESBuild
|
|
33
31
|
declare const routes: RouteHandler[];
|
|
32
|
+
// define `__FALLBACK_SERVICE__` via ESBuild
|
|
33
|
+
declare const __FALLBACK_SERVICE__: string;
|
|
34
34
|
|
|
35
35
|
// expect an ASSETS fetcher binding pointing to the asset-server stage
|
|
36
|
-
type
|
|
37
|
-
[name: string]:
|
|
38
|
-
ASSETS: { fetch
|
|
36
|
+
type FetchEnv = {
|
|
37
|
+
[name: string]: { fetch: typeof fetch };
|
|
38
|
+
ASSETS: { fetch: typeof fetch };
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
type WorkerContext = {
|
|
42
|
-
waitUntil: (promise: Promise<
|
|
42
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
function* executeRequest(request: Request,
|
|
45
|
+
function* executeRequest(request: Request, _env: FetchEnv) {
|
|
46
46
|
const requestPath = new URL(request.url).pathname;
|
|
47
47
|
|
|
48
|
-
// First, iterate through the routes and execute "middlewares" on partial route matches
|
|
49
|
-
for (const route of routes) {
|
|
50
|
-
if (
|
|
51
|
-
route.methods.length &&
|
|
52
|
-
!route.methods.includes(request.method as HTTPMethod)
|
|
53
|
-
) {
|
|
48
|
+
// First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
|
|
49
|
+
for (const route of [...routes].reverse()) {
|
|
50
|
+
if (route.method && route.method !== request.method) {
|
|
54
51
|
continue;
|
|
55
52
|
}
|
|
56
53
|
|
|
@@ -68,10 +65,7 @@ function* executeRequest(request: Request, env: Env) {
|
|
|
68
65
|
|
|
69
66
|
// Then look for the first exact route match and execute its "modules"
|
|
70
67
|
for (const route of routes) {
|
|
71
|
-
if (
|
|
72
|
-
route.methods.length &&
|
|
73
|
-
!route.methods.includes(request.method as HTTPMethod)
|
|
74
|
-
) {
|
|
68
|
+
if (route.method && route.method !== request.method) {
|
|
75
69
|
continue;
|
|
76
70
|
}
|
|
77
71
|
|
|
@@ -87,16 +81,10 @@ function* executeRequest(request: Request, env: Env) {
|
|
|
87
81
|
break;
|
|
88
82
|
}
|
|
89
83
|
}
|
|
90
|
-
|
|
91
|
-
// Finally, yield to the asset-server
|
|
92
|
-
return {
|
|
93
|
-
handler: () => env.ASSETS.fetch(request.url, request),
|
|
94
|
-
params: {} as Params,
|
|
95
|
-
};
|
|
96
84
|
}
|
|
97
85
|
|
|
98
86
|
export default {
|
|
99
|
-
async fetch(request: Request, env:
|
|
87
|
+
async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
|
|
100
88
|
const handlerIterator = executeRequest(request, env);
|
|
101
89
|
const data = {}; // arbitrary data the user can set between functions
|
|
102
90
|
const next = async (input?: RequestInfo, init?: RequestInit) => {
|
|
@@ -104,10 +92,11 @@ export default {
|
|
|
104
92
|
request = new Request(input, init);
|
|
105
93
|
}
|
|
106
94
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
95
|
+
const result = handlerIterator.next();
|
|
96
|
+
// Note we can't use `!result.done` because this doesn't narrow to the correct type
|
|
97
|
+
if (result.done == false) {
|
|
98
|
+
const { handler, params } = result.value;
|
|
99
|
+
const context = {
|
|
111
100
|
request: new Request(request.clone()),
|
|
112
101
|
next,
|
|
113
102
|
params,
|
|
@@ -123,6 +112,12 @@ export default {
|
|
|
123
112
|
[101, 204, 205, 304].includes(response.status) ? null : response.body,
|
|
124
113
|
response
|
|
125
114
|
);
|
|
115
|
+
} else if (__FALLBACK_SERVICE__) {
|
|
116
|
+
// There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case)
|
|
117
|
+
return env[__FALLBACK_SERVICE__].fetch(request);
|
|
118
|
+
} else {
|
|
119
|
+
// There was not fallback service so actually make the request to the origin.
|
|
120
|
+
return fetch(request);
|
|
126
121
|
}
|
|
127
122
|
};
|
|
128
123
|
|