wrangler 0.0.7 → 0.0.8
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 +5 -3
- package/pages/functions/buildWorker.ts +5 -0
- package/pages/functions/filepath-routing.test.ts +8 -1
- package/pages/functions/filepath-routing.ts +9 -11
- package/pages/functions/identifiers.ts +1 -1
- package/pages/functions/routes.ts +10 -8
- package/pages/functions/template-worker.ts +21 -10
- package/src/__tests__/dev.test.tsx +66 -0
- package/src/__tests__/jest.setup.ts +5 -1
- package/src/__tests__/kv.test.ts +814 -38
- package/src/__tests__/mock-cfetch.ts +137 -29
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +6 -6
- package/src/cfetch/index.ts +102 -0
- package/src/cfetch/internal.ts +69 -0
- package/src/config.ts +1 -1
- package/src/dev.tsx +69 -177
- package/src/index.tsx +106 -34
- package/src/inspect.ts +524 -0
- package/src/kv.tsx +38 -28
- package/src/pages.tsx +316 -206
- package/src/proxy.ts +267 -76
- package/src/publish.ts +28 -20
- package/src/sites.tsx +5 -6
- package/src/tail.tsx +5 -3
- package/src/user.tsx +12 -9
- package/wrangler-dist/cli.js +7016 -6726
- package/wrangler-dist/cli.js.map +3 -3
- package/src/api/inspect.ts +0 -430
- package/src/cfetch.ts +0 -72
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wrangler",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"author": "wrangler@cloudflare.com",
|
|
5
5
|
"description": "Command-line interface for all things Cloudflare Workers",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"esbuild": "0.14.1",
|
|
40
|
-
"miniflare": "2.0.0
|
|
40
|
+
"miniflare": "2.0.0",
|
|
41
41
|
"path-to-regexp": "^6.2.0",
|
|
42
42
|
"semiver": "^1.1.0"
|
|
43
43
|
},
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@babel/types": "^7.16.0",
|
|
49
49
|
"@iarna/toml": "^2.2.5",
|
|
50
|
-
"@types/mime": "^2.0.3",
|
|
51
50
|
"@types/estree": "^0.0.50",
|
|
51
|
+
"@types/mime": "^2.0.3",
|
|
52
52
|
"@types/react": "^17.0.37",
|
|
53
53
|
"@types/serve-static": "^1.13.10",
|
|
54
54
|
"@types/signal-exit": "^3.0.1",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"chokidar": "^3.5.2",
|
|
60
60
|
"clipboardy": "^3.0.0",
|
|
61
61
|
"command-exists": "^1.2.9",
|
|
62
|
+
"devtools-protocol": "^0.0.955664",
|
|
62
63
|
"execa": "^6.0.0",
|
|
63
64
|
"faye-websocket": "^0.11.4",
|
|
64
65
|
"finalhandler": "^1.1.2",
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
"ink": "^3.2.0",
|
|
68
69
|
"ink-select-input": "^4.2.1",
|
|
69
70
|
"ink-table": "^3.0.0",
|
|
71
|
+
"ink-testing-library": "^2.1.0",
|
|
70
72
|
"ink-text-input": "^4.0.2",
|
|
71
73
|
"mime": "^3.0.0",
|
|
72
74
|
"node-fetch": "^3.1.0",
|
|
@@ -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,8 +1,15 @@
|
|
|
1
1
|
import { compareRoutes } from "./filepath-routing";
|
|
2
2
|
|
|
3
3
|
describe("compareRoutes()", () => {
|
|
4
|
+
test("routes / last", () => {
|
|
5
|
+
expect(compareRoutes("/", "/foo")).toBeGreaterThanOrEqual(1);
|
|
6
|
+
expect(compareRoutes("/", "/:foo")).toBeGreaterThanOrEqual(1);
|
|
7
|
+
expect(compareRoutes("/", "/:foo*")).toBeGreaterThanOrEqual(1);
|
|
8
|
+
});
|
|
9
|
+
|
|
4
10
|
test("routes with fewer segments come after those with more segments", () => {
|
|
5
|
-
expect(compareRoutes("/foo", "/foo/bar")).
|
|
11
|
+
expect(compareRoutes("/foo", "/foo/bar")).toBeGreaterThanOrEqual(1);
|
|
12
|
+
expect(compareRoutes("/foo", "/foo/bar/cat")).toBeGreaterThanOrEqual(1);
|
|
6
13
|
});
|
|
7
14
|
|
|
8
15
|
test("routes with wildcard segments come after those without", () => {
|
|
@@ -3,9 +3,8 @@ import fs from "fs/promises";
|
|
|
3
3
|
import { transform } from "esbuild";
|
|
4
4
|
import * as acorn from "acorn";
|
|
5
5
|
import * as acornWalk from "acorn-walk";
|
|
6
|
-
import type { Config } from "./routes";
|
|
7
|
-
import type { Identifier } from "estree";
|
|
8
|
-
import type { ExportNamedDeclaration } from "@babel/types";
|
|
6
|
+
import type { Config, RouteConfig } from "./routes";
|
|
7
|
+
import type { ExportNamedDeclaration, Identifier } from "estree";
|
|
9
8
|
|
|
10
9
|
type Arguments = {
|
|
11
10
|
baseDir: string;
|
|
@@ -16,10 +15,7 @@ export async function generateConfigFromFileTree({
|
|
|
16
15
|
baseDir,
|
|
17
16
|
baseURL,
|
|
18
17
|
}: Arguments) {
|
|
19
|
-
let routeEntries: [
|
|
20
|
-
string,
|
|
21
|
-
{ [key in "module" | "middleware"]?: string[] }
|
|
22
|
-
][] = [] as any;
|
|
18
|
+
let routeEntries: [string, RouteConfig][] = [];
|
|
23
19
|
|
|
24
20
|
if (!baseURL.startsWith("/")) {
|
|
25
21
|
baseURL = `/${baseURL}`;
|
|
@@ -31,7 +27,7 @@ export async function generateConfigFromFileTree({
|
|
|
31
27
|
|
|
32
28
|
await forEachFile(baseDir, async (filepath) => {
|
|
33
29
|
const ext = path.extname(filepath);
|
|
34
|
-
if (
|
|
30
|
+
if (/^\.(mjs|js|ts|tsx|jsx)$/.test(ext)) {
|
|
35
31
|
// transform the code to ensure we're working with vanilla JS + ESM
|
|
36
32
|
const { code } = await transform(await fs.readFile(filepath, "utf-8"), {
|
|
37
33
|
loader: ext === ".ts" ? "ts" : "js",
|
|
@@ -43,8 +39,10 @@ export async function generateConfigFromFileTree({
|
|
|
43
39
|
sourceType: "module",
|
|
44
40
|
});
|
|
45
41
|
acornWalk.simple(ast, {
|
|
46
|
-
ExportNamedDeclaration(_node) {
|
|
47
|
-
|
|
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;
|
|
48
46
|
|
|
49
47
|
// this is an array because multiple things can be exported from a single statement
|
|
50
48
|
// i.e. `export {foo, bar}` or `export const foo = "f", bar = "b"`
|
|
@@ -164,7 +162,7 @@ export function compareRoutes(a: string, b: string) {
|
|
|
164
162
|
method = null;
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
const segments = segmentedPath.slice(1).split("/");
|
|
165
|
+
const segments = segmentedPath.slice(1).split("/").filter(Boolean);
|
|
168
166
|
return [method, segments];
|
|
169
167
|
}
|
|
170
168
|
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { isValidIdentifier, normalizeIdentifier } from "./identifiers";
|
|
4
4
|
|
|
5
5
|
export const HTTP_METHODS = [
|
|
6
6
|
"HEAD",
|
|
@@ -15,7 +15,7 @@ export type HTTPMethod = typeof HTTP_METHODS[number];
|
|
|
15
15
|
export function isHTTPMethod(
|
|
16
16
|
maybeHTTPMethod: string
|
|
17
17
|
): maybeHTTPMethod is HTTPMethod {
|
|
18
|
-
return HTTP_METHODS.includes(maybeHTTPMethod
|
|
18
|
+
return (HTTP_METHODS as readonly string[]).includes(maybeHTTPMethod);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export type RoutesCollection = Array<{
|
|
@@ -27,14 +27,16 @@ export type RoutesCollection = Array<{
|
|
|
27
27
|
|
|
28
28
|
export type Config = {
|
|
29
29
|
routes?: RoutesConfig;
|
|
30
|
-
schedules?:
|
|
30
|
+
schedules?: unknown;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export type RoutesConfig = {
|
|
34
|
-
[route: string]:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
[route: string]: RouteConfig;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type RouteConfig = {
|
|
38
|
+
middleware?: string | string[];
|
|
39
|
+
module?: string | string[];
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
type ImportMap = Map<
|
|
@@ -91,7 +93,7 @@ export function parseConfig(config: Config, baseDir: string) {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// ensure the module name (if provided) is a valid identifier to guard against injection attacks
|
|
94
|
-
if (name !== "default" && !
|
|
96
|
+
if (name !== "default" && !isValidIdentifier(name)) {
|
|
95
97
|
throw new Error(`Invalid module identifier "${name}"`);
|
|
96
98
|
}
|
|
97
99
|
|
|
@@ -2,11 +2,11 @@ import { match } from "path-to-regexp";
|
|
|
2
2
|
import type { HTTPMethod } from "./routes";
|
|
3
3
|
|
|
4
4
|
/* TODO: Grab these from @cloudflare/workers-types instead */
|
|
5
|
-
type Params<P extends string =
|
|
5
|
+
type Params<P extends string = string> = Record<P, string | string[]>;
|
|
6
6
|
|
|
7
7
|
type EventContext<Env, P extends string, Data> = {
|
|
8
8
|
request: Request;
|
|
9
|
-
waitUntil: (promise: Promise<
|
|
9
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
10
10
|
next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
|
|
11
11
|
env: Env & { ASSETS: { fetch: typeof fetch } };
|
|
12
12
|
params: Params<P>;
|
|
@@ -15,7 +15,7 @@ type EventContext<Env, P extends string, Data> = {
|
|
|
15
15
|
|
|
16
16
|
declare type PagesFunction<
|
|
17
17
|
Env = unknown,
|
|
18
|
-
P extends string =
|
|
18
|
+
P extends string = string,
|
|
19
19
|
Data extends Record<string, unknown> = Record<string, unknown>
|
|
20
20
|
> = (context: EventContext<Env, P, Data>) => Response | Promise<Response>;
|
|
21
21
|
/* end @cloudflare/workers-types */
|
|
@@ -29,22 +29,25 @@ type RouteHandler = {
|
|
|
29
29
|
|
|
30
30
|
// inject `routes` via ESBuild
|
|
31
31
|
declare const routes: RouteHandler[];
|
|
32
|
+
// define `__FALLBACK_SERVICE__` via ESBuild
|
|
33
|
+
declare const __FALLBACK_SERVICE__: string;
|
|
32
34
|
|
|
33
35
|
// expect an ASSETS fetcher binding pointing to the asset-server stage
|
|
34
36
|
type Env = {
|
|
35
|
-
[name: string]:
|
|
37
|
+
[name: string]: unknown;
|
|
36
38
|
ASSETS: { fetch(url: string, init: RequestInit): Promise<Response> };
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
type WorkerContext = {
|
|
40
|
-
waitUntil: (promise: Promise<
|
|
42
|
+
waitUntil: (promise: Promise<unknown>) => void;
|
|
41
43
|
};
|
|
42
44
|
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- `env` can be used by __FALLBACK_SERVICE_FETCH__
|
|
43
46
|
function* executeRequest(request: Request, env: Env) {
|
|
44
47
|
const requestPath = new URL(request.url).pathname;
|
|
45
48
|
|
|
46
|
-
// First, iterate through the routes and execute "middlewares" on partial route matches
|
|
47
|
-
for (const route of routes) {
|
|
49
|
+
// First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
|
|
50
|
+
for (const route of [...routes].reverse()) {
|
|
48
51
|
if (
|
|
49
52
|
route.methods.length &&
|
|
50
53
|
!route.methods.includes(request.method as HTTPMethod)
|
|
@@ -86,9 +89,13 @@ function* executeRequest(request: Request, env: Env) {
|
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
// Finally, yield to the
|
|
92
|
+
// Finally, yield to the fallback service (`env.ASSETS.fetch` in Pages' case)
|
|
90
93
|
return {
|
|
91
|
-
handler: () =>
|
|
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),
|
|
92
99
|
params: {} as Params,
|
|
93
100
|
};
|
|
94
101
|
}
|
|
@@ -105,7 +112,11 @@ export default {
|
|
|
105
112
|
const { value } = handlerIterator.next();
|
|
106
113
|
if (value) {
|
|
107
114
|
const { handler, params } = value;
|
|
108
|
-
const context: EventContext<
|
|
115
|
+
const context: EventContext<
|
|
116
|
+
unknown,
|
|
117
|
+
string,
|
|
118
|
+
Record<string, unknown>
|
|
119
|
+
> = {
|
|
109
120
|
request: new Request(request.clone()),
|
|
110
121
|
next,
|
|
111
122
|
params,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render } from "ink-testing-library";
|
|
2
|
+
import patchConsole from "patch-console";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import Dev from "../dev";
|
|
5
|
+
import type { DevProps } from "../dev";
|
|
6
|
+
|
|
7
|
+
describe("Dev component", () => {
|
|
8
|
+
let restoreConsole;
|
|
9
|
+
beforeEach(() => (restoreConsole = patchConsole(() => {})));
|
|
10
|
+
afterEach(() => restoreConsole());
|
|
11
|
+
|
|
12
|
+
it("should throw if format is service-worker and there is a public directory", () => {
|
|
13
|
+
const { lastFrame } = renderDev({
|
|
14
|
+
format: "service-worker",
|
|
15
|
+
accountId: "some-account-id",
|
|
16
|
+
public: "some/public/path",
|
|
17
|
+
});
|
|
18
|
+
expect(lastFrame()).toMatchInlineSnapshot(`
|
|
19
|
+
"Something went wrong:
|
|
20
|
+
You cannot use the service worker format with a \`public\` directory."
|
|
21
|
+
`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Helper function to make it easier to setup and render the `Dev` component.
|
|
27
|
+
*
|
|
28
|
+
* All the `Dev` props are optional here, with sensible defaults for testing.
|
|
29
|
+
*/
|
|
30
|
+
function renderDev({
|
|
31
|
+
name,
|
|
32
|
+
entry = "some/entry.ts",
|
|
33
|
+
port,
|
|
34
|
+
format,
|
|
35
|
+
accountId,
|
|
36
|
+
initialMode = "remote",
|
|
37
|
+
jsxFactory,
|
|
38
|
+
jsxFragment,
|
|
39
|
+
bindings = {},
|
|
40
|
+
public: publicDir,
|
|
41
|
+
site,
|
|
42
|
+
compatibilityDate,
|
|
43
|
+
compatibilityFlags,
|
|
44
|
+
usageModel,
|
|
45
|
+
buildCommand = {},
|
|
46
|
+
}: Partial<DevProps>) {
|
|
47
|
+
return render(
|
|
48
|
+
<Dev
|
|
49
|
+
name={name}
|
|
50
|
+
entry={entry}
|
|
51
|
+
port={port}
|
|
52
|
+
buildCommand={buildCommand}
|
|
53
|
+
format={format}
|
|
54
|
+
initialMode={initialMode}
|
|
55
|
+
jsxFactory={jsxFactory}
|
|
56
|
+
jsxFragment={jsxFragment}
|
|
57
|
+
accountId={accountId}
|
|
58
|
+
site={site}
|
|
59
|
+
public={publicDir}
|
|
60
|
+
compatibilityDate={compatibilityDate}
|
|
61
|
+
compatibilityFlags={compatibilityFlags}
|
|
62
|
+
usageModel={usageModel}
|
|
63
|
+
bindings={bindings}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { mockFetchInternal } from "./mock-cfetch";
|
|
1
2
|
import { confirm, prompt } from "../dialogs";
|
|
3
|
+
import { fetchInternal } from "../cfetch/internal";
|
|
4
|
+
|
|
5
|
+
jest.mock("../cfetch/internal");
|
|
6
|
+
(fetchInternal as jest.Mock).mockImplementation(mockFetchInternal);
|
|
2
7
|
|
|
3
|
-
jest.mock("../cfetch", () => jest.requireActual("./mock-cfetch"));
|
|
4
8
|
jest.mock("../dialogs");
|
|
5
9
|
|
|
6
10
|
// By default (if not configured by mockConfirm()) calls to `confirm()` should throw.
|