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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "0.0.7",
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-rc.5",
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")).toBe(1);
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 (/\.(mjs|js|ts)/.test(ext)) {
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
- const node: ExportNamedDeclaration = _node as any;
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 isValidIdentifer = (identifier: string) =>
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 { isValidIdentifer, normalizeIdentifier } from "./identifiers";
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 as any);
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?: any;
30
+ schedules?: unknown;
31
31
  };
32
32
 
33
33
  export type RoutesConfig = {
34
- [route: string]: {
35
- middleware?: string | string[];
36
- module?: string | string[];
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" && !isValidIdentifer(name)) {
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 = any> = Record<P, string | 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<any>) => void;
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 = any,
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]: any;
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<any>) => void;
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 asset-server
92
+ // Finally, yield to the fallback service (`env.ASSETS.fetch` in Pages' case)
90
93
  return {
91
- handler: () => env.ASSETS.fetch(request.url, request),
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<unknown, any, any> = {
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.