wrangler 0.0.0-e22f9d7 → 0.0.0-e2a26a0
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 +15 -10
- package/pages/functions/buildWorker.ts +1 -1
- package/pages/functions/filepath-routing.test.ts +21 -13
- package/pages/functions/filepath-routing.ts +38 -28
- package/pages/functions/routes.ts +8 -6
- package/pages/functions/template-worker.ts +16 -24
- package/src/__tests__/dev.test.tsx +7 -3
- 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} +49 -13
- 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 +312 -56
- package/src/__tests__/jest.setup.ts +17 -2
- package/src/__tests__/kv.test.ts +344 -316
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/publish.test.ts +1222 -0
- package/src/__tests__/secret.test.ts +210 -0
- package/src/__tests__/whoami.test.tsx +86 -0
- package/src/api/form_data.ts +10 -7
- package/src/api/preview.ts +2 -2
- package/src/api/worker.ts +3 -3
- package/src/cfetch/index.ts +14 -14
- package/src/cfetch/internal.ts +40 -8
- package/src/config.ts +162 -8
- package/src/dev.tsx +103 -53
- package/src/dialogs.tsx +2 -2
- package/src/index.tsx +438 -311
- package/src/inspect.ts +80 -23
- package/src/kv.tsx +9 -1
- package/src/module-collection.ts +5 -6
- package/src/package-manager.ts +120 -0
- package/src/pages.tsx +41 -26
- package/src/paths.ts +26 -0
- package/src/proxy.ts +78 -10
- package/src/publish.ts +98 -52
- package/src/sites.tsx +175 -53
- package/src/user.tsx +51 -59
- 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 +111479 -110187
- 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/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-e2a26a0",
|
|
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.
|
|
39
|
+
"esbuild": "0.14.14",
|
|
40
40
|
"miniflare": "2.2.0",
|
|
41
41
|
"path-to-regexp": "^6.2.0",
|
|
42
|
-
"semiver": "^1.1.0"
|
|
42
|
+
"semiver": "^1.1.0",
|
|
43
|
+
"xxhash-addon": "^1.4.0"
|
|
43
44
|
},
|
|
44
45
|
"optionalDependencies": {
|
|
45
46
|
"fsevents": "~2.3.2"
|
|
@@ -58,27 +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",
|
|
62
64
|
"devtools-protocol": "^0.0.955664",
|
|
63
65
|
"execa": "^6.0.0",
|
|
64
66
|
"faye-websocket": "^0.11.4",
|
|
65
67
|
"finalhandler": "^1.1.2",
|
|
66
68
|
"find-up": "^6.2.0",
|
|
67
|
-
"
|
|
69
|
+
"ignore": "^5.2.0",
|
|
68
70
|
"ink": "^3.2.0",
|
|
69
71
|
"ink-select-input": "^4.2.1",
|
|
70
72
|
"ink-table": "^3.0.0",
|
|
71
73
|
"ink-testing-library": "^2.1.0",
|
|
72
74
|
"ink-text-input": "^4.0.2",
|
|
75
|
+
"jest-fetch-mock": "^3.0.3",
|
|
73
76
|
"mime": "^3.0.0",
|
|
74
|
-
"node-fetch": "^3.1.0",
|
|
75
77
|
"open": "^8.4.0",
|
|
76
78
|
"react": "^17.0.2",
|
|
77
79
|
"react-error-boundary": "^3.1.4",
|
|
78
80
|
"serve-static": "^1.14.1",
|
|
79
81
|
"signal-exit": "^3.0.6",
|
|
80
82
|
"tmp-promise": "^3.0.3",
|
|
81
|
-
"undici": "
|
|
83
|
+
"undici": "4.13.0",
|
|
82
84
|
"ws": "^8.3.0",
|
|
83
85
|
"yargs": "^17.3.0"
|
|
84
86
|
},
|
|
@@ -88,28 +90,31 @@
|
|
|
88
90
|
"pages",
|
|
89
91
|
"miniflare-config-stubs",
|
|
90
92
|
"wrangler-dist",
|
|
91
|
-
"
|
|
93
|
+
"templates",
|
|
92
94
|
"vendor",
|
|
93
95
|
"import_meta_url.js"
|
|
94
96
|
],
|
|
95
97
|
"scripts": {
|
|
96
98
|
"clean": "rm -rf wrangler-dist",
|
|
99
|
+
"check:type": "tsc",
|
|
97
100
|
"bundle": "node -r esbuild-register scripts/bundle.ts",
|
|
98
101
|
"build": "npm run clean && npm run bundle",
|
|
99
102
|
"start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
|
|
100
|
-
"test": "
|
|
103
|
+
"test": "jest --silent=false --verbose=true",
|
|
104
|
+
"test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch"
|
|
101
105
|
},
|
|
102
106
|
"engines": {
|
|
103
107
|
"node": ">=16.7.0"
|
|
104
108
|
},
|
|
105
109
|
"jest": {
|
|
106
110
|
"restoreMocks": true,
|
|
111
|
+
"testTimeout": 30000,
|
|
107
112
|
"testRegex": ".*.(test|spec)\\.[jt]sx?$",
|
|
108
113
|
"transformIgnorePatterns": [
|
|
109
|
-
"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)"
|
|
110
115
|
],
|
|
111
116
|
"moduleNameMapper": {
|
|
112
|
-
"clipboardy": "<rootDir>/src/__tests__/clipboardy-mock.js"
|
|
117
|
+
"clipboardy": "<rootDir>/src/__tests__/helpers/clipboardy-mock.js"
|
|
113
118
|
},
|
|
114
119
|
"transform": {
|
|
115
120
|
"^.+\\.c?(t|j)sx?$": [
|
|
@@ -1,39 +1,47 @@
|
|
|
1
|
+
import { toUrlPath } from "../../src/paths";
|
|
1
2
|
import { compareRoutes } from "./filepath-routing";
|
|
2
3
|
|
|
3
4
|
describe("compareRoutes()", () => {
|
|
5
|
+
const url = toUrlPath;
|
|
4
6
|
test("routes / last", () => {
|
|
5
|
-
expect(compareRoutes("/", "/foo")).toBeGreaterThanOrEqual(1);
|
|
6
|
-
expect(compareRoutes("/", "/:foo")).toBeGreaterThanOrEqual(1);
|
|
7
|
-
expect(compareRoutes("/", "/:foo*")).toBeGreaterThanOrEqual(
|
|
7
|
+
expect(compareRoutes([url("/")], [url("/foo")])).toBeGreaterThanOrEqual(1);
|
|
8
|
+
expect(compareRoutes([url("/")], [url("/:foo")])).toBeGreaterThanOrEqual(1);
|
|
9
|
+
expect(compareRoutes([url("/")], [url("/:foo*")])).toBeGreaterThanOrEqual(
|
|
10
|
+
1
|
|
11
|
+
);
|
|
8
12
|
});
|
|
9
13
|
|
|
10
14
|
test("routes with fewer segments come after those with more segments", () => {
|
|
11
|
-
expect(
|
|
12
|
-
|
|
15
|
+
expect(
|
|
16
|
+
compareRoutes([url("/foo")], [url("/foo/bar")])
|
|
17
|
+
).toBeGreaterThanOrEqual(1);
|
|
18
|
+
expect(
|
|
19
|
+
compareRoutes([url("/foo")], [url("/foo/bar/cat")])
|
|
20
|
+
).toBeGreaterThanOrEqual(1);
|
|
13
21
|
});
|
|
14
22
|
|
|
15
23
|
test("routes with wildcard segments come after those without", () => {
|
|
16
|
-
expect(compareRoutes("/:foo*", "/foo")).toBe(1);
|
|
17
|
-
expect(compareRoutes("/:foo*", "/:foo")).toBe(1);
|
|
24
|
+
expect(compareRoutes([url("/:foo*")], [url("/foo")])).toBe(1);
|
|
25
|
+
expect(compareRoutes([url("/:foo*")], [url("/:foo")])).toBe(1);
|
|
18
26
|
});
|
|
19
27
|
|
|
20
28
|
test("routes with dynamic segments come after those without", () => {
|
|
21
|
-
expect(compareRoutes("/:foo", "/foo")).toBe(1);
|
|
29
|
+
expect(compareRoutes([url("/:foo")], [url("/foo")])).toBe(1);
|
|
22
30
|
});
|
|
23
31
|
|
|
24
|
-
test("routes with dynamic segments
|
|
25
|
-
expect(compareRoutes("/foo/:id/bar", "/foo/bar/:id")).toBe(1);
|
|
32
|
+
test("routes with dynamic segments occurring earlier come after those with dynamic segments in later positions", () => {
|
|
33
|
+
expect(compareRoutes([url("/foo/:id/bar")], [url("/foo/bar/:id")])).toBe(1);
|
|
26
34
|
});
|
|
27
35
|
|
|
28
36
|
test("routes with no HTTP method come after those specifying a method", () => {
|
|
29
|
-
expect(compareRoutes("/foo", "
|
|
37
|
+
expect(compareRoutes([url("/foo")], [url("/foo"), "GET"])).toBe(1);
|
|
30
38
|
});
|
|
31
39
|
|
|
32
40
|
test("two equal routes are sorted according to their original position in the list", () => {
|
|
33
|
-
expect(compareRoutes("
|
|
41
|
+
expect(compareRoutes([url("/foo"), "GET"], [url("/foo"), "GET"])).toBe(0);
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
test("it returns -1 if the first argument should appear first in the list", () => {
|
|
37
|
-
expect(compareRoutes("
|
|
45
|
+
expect(compareRoutes([url("/foo"), "GET"], [url("/foo")])).toBe(-1);
|
|
38
46
|
});
|
|
39
47
|
});
|
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { transform } from "esbuild";
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
4
3
|
import * as acorn from "acorn";
|
|
5
4
|
import * as acornWalk from "acorn-walk";
|
|
5
|
+
import { transform } from "esbuild";
|
|
6
|
+
import { toUrlPath } from "../../src/paths";
|
|
7
|
+
import type { UrlPath } from "../../src/paths";
|
|
6
8
|
import type { Config, RouteConfig } from "./routes";
|
|
7
9
|
import type { ExportNamedDeclaration, Identifier } from "estree";
|
|
8
10
|
|
|
9
11
|
type Arguments = {
|
|
10
12
|
baseDir: string;
|
|
11
|
-
baseURL:
|
|
13
|
+
baseURL: UrlPath;
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
|
|
17
|
+
type RouteKey = [UrlPath, Method] | [UrlPath, undefined] | [UrlPath];
|
|
18
|
+
|
|
14
19
|
export async function generateConfigFromFileTree({
|
|
15
20
|
baseDir,
|
|
16
21
|
baseURL,
|
|
17
22
|
}: Arguments) {
|
|
18
|
-
let routeEntries: [
|
|
23
|
+
let routeEntries: [RouteKey, RouteConfig][] = [];
|
|
19
24
|
|
|
20
25
|
if (!baseURL.startsWith("/")) {
|
|
21
|
-
baseURL = `/${baseURL}
|
|
26
|
+
baseURL = `/${baseURL}` as UrlPath;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
if (baseURL.endsWith("/")) {
|
|
25
|
-
baseURL = baseURL.slice(0, -1);
|
|
30
|
+
baseURL = baseURL.slice(0, -1) as UrlPath;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
await forEachFile(baseDir, async (filepath) => {
|
|
@@ -52,7 +57,7 @@ export async function generateConfigFromFileTree({
|
|
|
52
57
|
const declaration = node.declaration;
|
|
53
58
|
|
|
54
59
|
// `export async function onRequest() {...}`
|
|
55
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
60
|
+
if (declaration.type === "FunctionDeclaration" && declaration.id) {
|
|
56
61
|
exportNames.push(declaration.id.name);
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -108,12 +113,14 @@ export async function generateConfigFromFileTree({
|
|
|
108
113
|
routePath = routePath.replace(/\[\[(.+)]]/g, ":$1*"); // transform [[id]] => :id*
|
|
109
114
|
routePath = routePath.replace(/\[(.+)]/g, ":$1"); // transform [id] => :id
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
const routeUrlPath = toUrlPath(routePath);
|
|
117
|
+
const routeKey: RouteKey = [
|
|
118
|
+
routeUrlPath,
|
|
119
|
+
method ? (method.toUpperCase() as Method) : undefined,
|
|
120
|
+
];
|
|
114
121
|
|
|
115
122
|
routeEntries.push([
|
|
116
|
-
|
|
123
|
+
routeKey,
|
|
117
124
|
{
|
|
118
125
|
[isMiddlewareFile ? "middleware" : "module"]: [
|
|
119
126
|
`${path.relative(baseDir, filepath)}:${exportName}`,
|
|
@@ -146,7 +153,7 @@ export async function generateConfigFromFileTree({
|
|
|
146
153
|
[]
|
|
147
154
|
);
|
|
148
155
|
|
|
149
|
-
routeEntries.sort(([
|
|
156
|
+
routeEntries.sort(([a], [b]) => compareRoutes(a, b));
|
|
150
157
|
|
|
151
158
|
return { routes: Object.fromEntries(routeEntries) } as Config;
|
|
152
159
|
}
|
|
@@ -154,20 +161,16 @@ export async function generateConfigFromFileTree({
|
|
|
154
161
|
// Ensure routes are produced in order of precedence so that
|
|
155
162
|
// more specific routes aren't occluded from matching due to
|
|
156
163
|
// less specific routes appearing first in the route list.
|
|
157
|
-
export function compareRoutes(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const segments = segmentedPath.slice(1).split("/").filter(Boolean);
|
|
166
|
-
return [method, segments];
|
|
164
|
+
export function compareRoutes(
|
|
165
|
+
[routePathA, methodA]: RouteKey,
|
|
166
|
+
[routePathB, methodB]: RouteKey
|
|
167
|
+
) {
|
|
168
|
+
function parseRoutePath(routePath: UrlPath): string[] {
|
|
169
|
+
return routePath.slice(1).split("/").filter(Boolean);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
const
|
|
170
|
-
const
|
|
172
|
+
const segmentsA = parseRoutePath(routePathA);
|
|
173
|
+
const segmentsB = parseRoutePath(routePathB);
|
|
171
174
|
|
|
172
175
|
// sort routes with fewer segments after those with more segments
|
|
173
176
|
if (segmentsA.length !== segmentsB.length) {
|
|
@@ -193,8 +196,8 @@ export function compareRoutes(a: string, b: string) {
|
|
|
193
196
|
if (methodA && !methodB) return -1;
|
|
194
197
|
if (!methodA && methodB) return 1;
|
|
195
198
|
|
|
196
|
-
// all else equal, just sort
|
|
197
|
-
return
|
|
199
|
+
// all else equal, just sort the paths lexicographically
|
|
200
|
+
return routePathA.localeCompare(routePathB);
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
async function forEachFile<T>(
|
|
@@ -204,7 +207,7 @@ async function forEachFile<T>(
|
|
|
204
207
|
const searchPaths = [baseDir];
|
|
205
208
|
const returnValues: T[] = [];
|
|
206
209
|
|
|
207
|
-
while (searchPaths
|
|
210
|
+
while (isNotEmpty(searchPaths)) {
|
|
208
211
|
const cwd = searchPaths.shift();
|
|
209
212
|
const dir = await fs.readdir(cwd, { withFileTypes: true });
|
|
210
213
|
for (const entry of dir) {
|
|
@@ -219,3 +222,10 @@ async function forEachFile<T>(
|
|
|
219
222
|
|
|
220
223
|
return returnValues;
|
|
221
224
|
}
|
|
225
|
+
|
|
226
|
+
interface NonEmptyArray<T> extends Array<T> {
|
|
227
|
+
shift(): T;
|
|
228
|
+
}
|
|
229
|
+
function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
|
|
230
|
+
return array.length > 0;
|
|
231
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { toUrlPath } from "../../src/paths";
|
|
3
4
|
import { isValidIdentifier, normalizeIdentifier } from "./identifiers";
|
|
5
|
+
import type { UrlPath } from "../../src/paths";
|
|
4
6
|
|
|
5
7
|
export const HTTP_METHODS = [
|
|
6
8
|
"HEAD",
|
|
@@ -19,7 +21,7 @@ export function isHTTPMethod(
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export type RoutesCollection = Array<{
|
|
22
|
-
routePath:
|
|
24
|
+
routePath: UrlPath;
|
|
23
25
|
methods: HTTPMethod[];
|
|
24
26
|
modules: string[];
|
|
25
27
|
middlewares: string[];
|
|
@@ -31,7 +33,7 @@ export type Config = {
|
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export type RoutesConfig = {
|
|
34
|
-
[route:
|
|
36
|
+
[route: UrlPath]: RouteConfig;
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
export type RouteConfig = {
|
|
@@ -114,7 +116,7 @@ export function parseConfig(config: Config, baseDir: string) {
|
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
for (const [route, props] of Object.entries(config.routes)) {
|
|
119
|
+
for (const [route, props] of Object.entries(config.routes ?? {})) {
|
|
118
120
|
let [_methods, routePath] = route.split(" ");
|
|
119
121
|
if (!routePath) {
|
|
120
122
|
routePath = _methods;
|
|
@@ -122,7 +124,7 @@ export function parseConfig(config: Config, baseDir: string) {
|
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
routes.push({
|
|
125
|
-
routePath,
|
|
127
|
+
routePath: toUrlPath(routePath),
|
|
126
128
|
methods: _methods.split("|").filter(isHTTPMethod),
|
|
127
129
|
middlewares: parseModuleIdentifiers(props.middleware),
|
|
128
130
|
modules: parseModuleIdentifiers(props.module),
|
|
@@ -33,17 +33,16 @@ declare const routes: RouteHandler[];
|
|
|
33
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
42
|
waitUntil: (promise: Promise<unknown>) => void;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
function* executeRequest(request: Request, env: Env) {
|
|
45
|
+
function* executeRequest(request: Request, _env: FetchEnv) {
|
|
47
46
|
const requestPath = new URL(request.url).pathname;
|
|
48
47
|
|
|
49
48
|
// First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
|
|
@@ -88,20 +87,10 @@ function* executeRequest(request: Request, env: Env) {
|
|
|
88
87
|
break;
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
// Finally, yield to the fallback service (`env.ASSETS.fetch` in Pages' case)
|
|
93
|
-
return {
|
|
94
|
-
handler: () =>
|
|
95
|
-
__FALLBACK_SERVICE__
|
|
96
|
-
? // @ts-expect-error expecting __FALLBACK_SERVICE__ to be the name of a service binding, so fetch should be defined
|
|
97
|
-
env[__FALLBACK_SERVICE__].fetch(request)
|
|
98
|
-
: fetch(request),
|
|
99
|
-
params: {} as Params,
|
|
100
|
-
};
|
|
101
90
|
}
|
|
102
91
|
|
|
103
92
|
export default {
|
|
104
|
-
async fetch(request: Request, env:
|
|
93
|
+
async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
|
|
105
94
|
const handlerIterator = executeRequest(request, env);
|
|
106
95
|
const data = {}; // arbitrary data the user can set between functions
|
|
107
96
|
const next = async (input?: RequestInfo, init?: RequestInit) => {
|
|
@@ -109,14 +98,11 @@ export default {
|
|
|
109
98
|
request = new Request(input, init);
|
|
110
99
|
}
|
|
111
100
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
string,
|
|
118
|
-
Record<string, unknown>
|
|
119
|
-
> = {
|
|
101
|
+
const result = handlerIterator.next();
|
|
102
|
+
// Note we can't use `!result.done` because this doesn't narrow to the correct type
|
|
103
|
+
if (result.done == false) {
|
|
104
|
+
const { handler, params } = result.value;
|
|
105
|
+
const context = {
|
|
120
106
|
request: new Request(request.clone()),
|
|
121
107
|
next,
|
|
122
108
|
params,
|
|
@@ -132,6 +118,12 @@ export default {
|
|
|
132
118
|
[101, 204, 205, 304].includes(response.status) ? null : response.body,
|
|
133
119
|
response
|
|
134
120
|
);
|
|
121
|
+
} else if (__FALLBACK_SERVICE__) {
|
|
122
|
+
// There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case)
|
|
123
|
+
return env[__FALLBACK_SERVICE__].fetch(request);
|
|
124
|
+
} else {
|
|
125
|
+
// There was not fallback service so actually make the request to the origin.
|
|
126
|
+
return fetch(request);
|
|
135
127
|
}
|
|
136
128
|
};
|
|
137
129
|
|
|
@@ -15,7 +15,7 @@ describe("Dev component", () => {
|
|
|
15
15
|
accountId: "some-account-id",
|
|
16
16
|
public: "some/public/path",
|
|
17
17
|
});
|
|
18
|
-
expect(lastFrame()
|
|
18
|
+
expect(lastFrame()?.split("\n").slice(0, 2).join("\n"))
|
|
19
19
|
.toMatchInlineSnapshot(`
|
|
20
20
|
"Something went wrong:
|
|
21
21
|
Error: You cannot use the service worker format with a \`public\` directory."
|
|
@@ -39,16 +39,19 @@ function renderDev({
|
|
|
39
39
|
jsxFragment,
|
|
40
40
|
bindings = {},
|
|
41
41
|
public: publicDir,
|
|
42
|
-
|
|
42
|
+
assetPaths,
|
|
43
43
|
compatibilityDate,
|
|
44
44
|
compatibilityFlags,
|
|
45
45
|
usageModel,
|
|
46
46
|
buildCommand = {},
|
|
47
|
+
enableLocalPersistence = false,
|
|
48
|
+
env = undefined,
|
|
47
49
|
}: Partial<DevProps>) {
|
|
48
50
|
return render(
|
|
49
51
|
<Dev
|
|
50
52
|
name={name}
|
|
51
53
|
entry={entry}
|
|
54
|
+
env={env}
|
|
52
55
|
port={port}
|
|
53
56
|
buildCommand={buildCommand}
|
|
54
57
|
format={format}
|
|
@@ -56,12 +59,13 @@ function renderDev({
|
|
|
56
59
|
jsxFactory={jsxFactory}
|
|
57
60
|
jsxFragment={jsxFragment}
|
|
58
61
|
accountId={accountId}
|
|
59
|
-
|
|
62
|
+
assetPaths={assetPaths}
|
|
60
63
|
public={publicDir}
|
|
61
64
|
compatibilityDate={compatibilityDate}
|
|
62
65
|
compatibilityFlags={compatibilityFlags}
|
|
63
66
|
usageModel={usageModel}
|
|
64
67
|
bindings={bindings}
|
|
68
|
+
enableLocalPersistence={enableLocalPersistence}
|
|
65
69
|
/>
|
|
66
70
|
);
|
|
67
71
|
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typings file available at `@types/cmd-shim` are out of date.
|
|
3
|
+
*/
|
|
4
|
+
module "cmd-shim" {
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* Create a cmd shim at `to` for the command line program at `from`.
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
export default function cmdShim(from: string, to: string): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ORIGINAL_CF_API_TOKEN = process.env.CF_API_TOKEN;
|
|
2
|
+
const ORIGINAL_CF_ACCOUNT_ID = process.env.CF_ACCOUNT_ID;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mock the API token so that we don't need to read it from user configuration files.
|
|
6
|
+
*/
|
|
7
|
+
export function mockApiToken({
|
|
8
|
+
apiToken = "some-api-token",
|
|
9
|
+
}: { apiToken?: string } = {}) {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
process.env.CF_API_TOKEN = apiToken;
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
process.env.CF_API_TOKEN = ORIGINAL_CF_API_TOKEN;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Mock the current account ID so that we don't need to read it from configuration files.
|
|
20
|
+
*/
|
|
21
|
+
export function mockAccountId({
|
|
22
|
+
accountId = "some-account-id",
|
|
23
|
+
}: { accountId?: string } = {}) {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
process.env.CF_ACCOUNT_ID = accountId;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
process.env.CF_ACCOUNT_ID = ORIGINAL_CF_ACCOUNT_ID;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, resolve } from "node:path";
|
|
3
|
+
import cmdShim from "cmd-shim";
|
|
4
|
+
|
|
5
|
+
const nodeShebang = "#!/usr/bin/env node";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create a binary file in a temp directory and make it available on the PATH.
|
|
9
|
+
*/
|
|
10
|
+
export async function mockBinary(binaryName: string, code: string) {
|
|
11
|
+
// Ensure there is a directory to put the mock binary in.
|
|
12
|
+
const tmpDir = resolve(mkdtempSync(".mock-binary-"));
|
|
13
|
+
|
|
14
|
+
// Use a fake extension on Windows because we will create a cmd-shim to run the binary.
|
|
15
|
+
const extension = process.platform === "win32" ? ".x-mock-bin" : "";
|
|
16
|
+
const filePath = resolve(tmpDir, `${binaryName}${extension}`);
|
|
17
|
+
writeFileSync(filePath, nodeShebang + "\n" + code);
|
|
18
|
+
chmodSync(filePath, 0o777);
|
|
19
|
+
|
|
20
|
+
if (process.platform === "win32") {
|
|
21
|
+
await cmdShim(filePath, basename(filePath, ".x-mock-bin"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Update PATH using the appropriate separator for the platform.
|
|
25
|
+
const oldPath = process.env.PATH;
|
|
26
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
27
|
+
process.env.PATH = tmpDir + sep + oldPath;
|
|
28
|
+
|
|
29
|
+
return function unMock() {
|
|
30
|
+
rmSync(tmpDir, { recursive: true });
|
|
31
|
+
process.env.PATH = process.env.PATH?.replace(tmpDir + sep, "");
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { URLSearchParams } from "node:url";
|
|
1
|
+
import { URL, URLSearchParams } from "node:url";
|
|
3
2
|
import { pathToRegexp } from "path-to-regexp";
|
|
4
|
-
import { CF_API_BASE_URL } from "
|
|
5
|
-
import type { FetchResult } from "
|
|
3
|
+
import { CF_API_BASE_URL } from "../../cfetch";
|
|
4
|
+
import type { FetchResult } from "../../cfetch";
|
|
5
|
+
import type { RequestInit } from "undici";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The signature of the function that will handle a mock request.
|
|
@@ -11,7 +11,7 @@ export type MockHandler<ResponseType> = (
|
|
|
11
11
|
uri: RegExpExecArray,
|
|
12
12
|
init: RequestInit,
|
|
13
13
|
queryParams: URLSearchParams
|
|
14
|
-
) => ResponseType
|
|
14
|
+
) => ResponseType | Promise<ResponseType>;
|
|
15
15
|
|
|
16
16
|
type RemoveMockFn = () => void;
|
|
17
17
|
|
|
@@ -38,13 +38,15 @@ export async function mockFetchInternal(
|
|
|
38
38
|
const resourcePath = new URL(resource, CF_API_BASE_URL).pathname;
|
|
39
39
|
const uri = regexp.exec(resourcePath);
|
|
40
40
|
// Do the resource path and (if specified) the HTTP method match?
|
|
41
|
-
if (uri !== null && (!method || method === init.method)) {
|
|
41
|
+
if (uri !== null && (!method || method === (init.method ?? "GET"))) {
|
|
42
42
|
// The `resource` regular expression will extract the labelled groups from the URL.
|
|
43
43
|
// These are passed through to the `handler` call, to allow it to do additional checks or behaviour.
|
|
44
|
-
return handler(uri, init, queryParams); // TODO: should we have some kind of fallthrough system? we'll see.
|
|
44
|
+
return await handler(uri, init, queryParams); // TODO: should we have some kind of fallthrough system? we'll see.
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
throw new Error(
|
|
47
|
+
throw new Error(
|
|
48
|
+
`no mocks found for ${init.method ?? "any HTTP"} request to ${resource}`
|
|
49
|
+
);
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -138,23 +140,23 @@ export function setMockResponse<ResponseType>(
|
|
|
138
140
|
/**
|
|
139
141
|
* A helper to make it easier to create `FetchResult` objects in tests.
|
|
140
142
|
*/
|
|
141
|
-
export function createFetchResult<ResponseType>(
|
|
142
|
-
result: ResponseType
|
|
143
|
+
export async function createFetchResult<ResponseType>(
|
|
144
|
+
result: ResponseType | Promise<ResponseType>,
|
|
143
145
|
success = true,
|
|
144
146
|
errors = [],
|
|
145
147
|
messages = [],
|
|
146
148
|
result_info?: unknown
|
|
147
|
-
): FetchResult<ResponseType
|
|
149
|
+
): Promise<FetchResult<ResponseType>> {
|
|
148
150
|
return result_info
|
|
149
151
|
? {
|
|
150
|
-
result,
|
|
152
|
+
result: await result,
|
|
151
153
|
success,
|
|
152
154
|
errors,
|
|
153
155
|
messages,
|
|
154
156
|
result_info,
|
|
155
157
|
}
|
|
156
158
|
: {
|
|
157
|
-
result,
|
|
159
|
+
result: await result,
|
|
158
160
|
success,
|
|
159
161
|
errors,
|
|
160
162
|
messages,
|
|
@@ -169,3 +171,37 @@ export function createFetchResult<ResponseType>(
|
|
|
169
171
|
export function unsetAllMocks() {
|
|
170
172
|
mocks.length = 0;
|
|
171
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* We special-case fetching the request for `kv:key get`, because it's
|
|
177
|
+
* the only cloudflare API endpoint that returns a plain string as the
|
|
178
|
+
* value, and not as the "standard" FetchResult-style json. Hence, we also
|
|
179
|
+
* special-case mocking it here.
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
const kvGetMocks = new Map<string, string>();
|
|
183
|
+
|
|
184
|
+
export function mockFetchKVGetValue(
|
|
185
|
+
accountId: string,
|
|
186
|
+
namespaceId: string,
|
|
187
|
+
key: string
|
|
188
|
+
) {
|
|
189
|
+
const mapKey = `${accountId}/${namespaceId}/${key}`;
|
|
190
|
+
if (kvGetMocks.has(mapKey)) {
|
|
191
|
+
return kvGetMocks.get(mapKey);
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`no mock value found for \`kv:key get\` - ${mapKey}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function setMockFetchKVGetValue(
|
|
197
|
+
accountId: string,
|
|
198
|
+
namespaceId: string,
|
|
199
|
+
key: string,
|
|
200
|
+
value: string
|
|
201
|
+
) {
|
|
202
|
+
kvGetMocks.set(`${accountId}/${namespaceId}/${key}`, value);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function unsetMockFetchKVGetValues() {
|
|
206
|
+
kvGetMocks.clear();
|
|
207
|
+
}
|