wrangler 0.0.8 → 0.0.13

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.8",
3
+ "version": "0.0.13",
4
4
  "author": "wrangler@cloudflare.com",
5
5
  "description": "Command-line interface for all things Cloudflare Workers",
6
6
  "bin": {
@@ -37,9 +37,10 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "esbuild": "0.14.1",
40
- "miniflare": "2.0.0",
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"
@@ -65,13 +66,14 @@
65
66
  "finalhandler": "^1.1.2",
66
67
  "find-up": "^6.2.0",
67
68
  "formdata-node": "^4.3.1",
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",
73
75
  "mime": "^3.0.0",
74
- "node-fetch": "^3.1.0",
76
+ "node-fetch": "3.1.1",
75
77
  "open": "^8.4.0",
76
78
  "react": "^17.0.2",
77
79
  "react-error-boundary": "^3.1.4",
@@ -97,13 +99,15 @@
97
99
  "bundle": "node -r esbuild-register scripts/bundle.ts",
98
100
  "build": "npm run clean && npm run bundle",
99
101
  "start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
100
- "test": "CF_API_TOKEN=some-api-token CF_ACCOUNT_ID=some-account-id jest --silent=false --verbose=true"
102
+ "test": "CF_API_TOKEN=some-api-token CF_ACCOUNT_ID=some-account-id jest --silent=false --verbose=true",
103
+ "test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch"
101
104
  },
102
105
  "engines": {
103
106
  "node": ">=16.7.0"
104
107
  },
105
108
  "jest": {
106
109
  "restoreMocks": true,
110
+ "testTimeout": 30000,
107
111
  "testRegex": ".*.(test|spec)\\.[jt]sx?$",
108
112
  "transformIgnorePatterns": [
109
113
  "node_modules/(?!node-fetch|fetch-blob|find-up|locate-path|p-locate|p-limit|yocto-queue|path-exists|data-uri-to-buffer|formdata-polyfill|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream)"
@@ -52,7 +52,7 @@ export async function generateConfigFromFileTree({
52
52
  const declaration = node.declaration;
53
53
 
54
54
  // `export async function onRequest() {...}`
55
- if (declaration.type === "FunctionDeclaration") {
55
+ if (declaration.type === "FunctionDeclaration" && declaration.id) {
56
56
  exportNames.push(declaration.id.name);
57
57
  }
58
58
 
@@ -155,12 +155,12 @@ export async function generateConfigFromFileTree({
155
155
  // more specific routes aren't occluded from matching due to
156
156
  // less specific routes appearing first in the route list.
157
157
  export function compareRoutes(a: string, b: string) {
158
- function parseRoutePath(routePath: string) {
159
- let [method, segmentedPath] = routePath.split(" ");
160
- if (!segmentedPath) {
161
- segmentedPath = method;
162
- method = null;
163
- }
158
+ function parseRoutePath(routePath: string): [string | null, string[]] {
159
+ const parts = routePath.split(" ", 2);
160
+ // split() will guarantee at least one element.
161
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
162
+ const segmentedPath = parts.pop()!;
163
+ const method = parts.pop() ?? null;
164
164
 
165
165
  const segments = segmentedPath.slice(1).split("/").filter(Boolean);
166
166
  return [method, segments];
@@ -204,7 +204,7 @@ async function forEachFile<T>(
204
204
  const searchPaths = [baseDir];
205
205
  const returnValues: T[] = [];
206
206
 
207
- while (searchPaths.length) {
207
+ while (isNotEmpty(searchPaths)) {
208
208
  const cwd = searchPaths.shift();
209
209
  const dir = await fs.readdir(cwd, { withFileTypes: true });
210
210
  for (const entry of dir) {
@@ -219,3 +219,10 @@ async function forEachFile<T>(
219
219
 
220
220
  return returnValues;
221
221
  }
222
+
223
+ interface NonEmptyArray<T> extends Array<T> {
224
+ shift(): T;
225
+ }
226
+ function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
227
+ return array.length > 0;
228
+ }
@@ -114,7 +114,7 @@ export function parseConfig(config: Config, baseDir: string) {
114
114
  });
115
115
  }
116
116
 
117
- for (const [route, props] of Object.entries(config.routes)) {
117
+ for (const [route, props] of Object.entries(config.routes ?? {})) {
118
118
  let [_methods, routePath] = route.split(" ");
119
119
  if (!routePath) {
120
120
  routePath = _methods;
@@ -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 Env = {
37
- [name: string]: unknown;
38
- ASSETS: { fetch(url: string, init: RequestInit): Promise<Response> };
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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- `env` can be used by __FALLBACK_SERVICE_FETCH__
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: Env, workerContext: WorkerContext) {
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 { value } = handlerIterator.next();
113
- if (value) {
114
- const { handler, params } = value;
115
- const context: EventContext<
116
- unknown,
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,9 +15,10 @@ describe("Dev component", () => {
15
15
  accountId: "some-account-id",
16
16
  public: "some/public/path",
17
17
  });
18
- expect(lastFrame()).toMatchInlineSnapshot(`
18
+ expect(lastFrame()?.split("\n").slice(0, 2).join("\n"))
19
+ .toMatchInlineSnapshot(`
19
20
  "Something went wrong:
20
- You cannot use the service worker format with a \`public\` directory."
21
+ Error: You cannot use the service worker format with a \`public\` directory."
21
22
  `);
22
23
  });
23
24
  });
@@ -38,7 +39,7 @@ function renderDev({
38
39
  jsxFragment,
39
40
  bindings = {},
40
41
  public: publicDir,
41
- site,
42
+ assetPaths,
42
43
  compatibilityDate,
43
44
  compatibilityFlags,
44
45
  usageModel,
@@ -55,7 +56,7 @@ function renderDev({
55
56
  jsxFactory={jsxFactory}
56
57
  jsxFragment={jsxFragment}
57
58
  accountId={accountId}
58
- site={site}
59
+ assetPaths={assetPaths}
59
60
  public={publicDir}
60
61
  compatibilityDate={compatibilityDate}
61
62
  compatibilityFlags={compatibilityFlags}
@@ -3,20 +3,24 @@ import * as TOML from "@iarna/toml";
3
3
  import { mockConfirm } from "./mock-dialogs";
4
4
  import { runWrangler } from "./run-wrangler";
5
5
  import { runInTempDir } from "./run-in-tmp";
6
+ import { mockConsoleMethods } from "./mock-console";
6
7
  import * as fs from "node:fs";
7
8
 
8
9
  describe("wrangler", () => {
9
10
  runInTempDir();
10
11
 
12
+ const std = mockConsoleMethods();
13
+
11
14
  describe("no command", () => {
12
15
  it("should display a list of available commands", async () => {
13
- const { stdout, stderr } = await runWrangler();
16
+ await runWrangler();
14
17
 
15
- expect(stdout).toMatchInlineSnapshot(`
18
+ expect(std.out).toMatchInlineSnapshot(`
16
19
  "wrangler
17
20
 
18
21
  Commands:
19
22
  wrangler init [name] 📥 Create a wrangler.toml configuration file
23
+ wrangler whoami 🕵️ Retrieve your user info and test your auth config
20
24
  wrangler dev <filename> 👂 Start a local server for developing your worker
21
25
  wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
22
26
  wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker.
@@ -35,20 +39,28 @@ describe("wrangler", () => {
35
39
  -l, --local Run on my machine [boolean] [default: false]"
36
40
  `);
37
41
 
38
- expect(stderr).toMatchInlineSnapshot(`""`);
42
+ expect(std.err).toMatchInlineSnapshot(`""`);
39
43
  });
40
44
  });
41
45
 
42
46
  describe("invalid command", () => {
43
47
  it("should display an error", async () => {
44
- const { error, stdout, stderr } = await runWrangler("invalid-command");
45
-
46
- expect(stdout).toMatchInlineSnapshot(`""`);
47
- expect(stderr).toMatchInlineSnapshot(`
48
+ let err: Error | undefined;
49
+ try {
50
+ await runWrangler("invalid-command");
51
+ } catch (e) {
52
+ err = e;
53
+ } finally {
54
+ expect(err?.message).toBe(`Unknown command: invalid-command.`);
55
+ }
56
+
57
+ expect(std.out).toMatchInlineSnapshot(`""`);
58
+ expect(std.err).toMatchInlineSnapshot(`
48
59
  "wrangler
49
60
 
50
61
  Commands:
51
62
  wrangler init [name] 📥 Create a wrangler.toml configuration file
63
+ wrangler whoami 🕵️ Retrieve your user info and test your auth config
52
64
  wrangler dev <filename> 👂 Start a local server for developing your worker
53
65
  wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
54
66
  wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker.
@@ -68,9 +80,6 @@ describe("wrangler", () => {
68
80
 
69
81
  Unknown command: invalid-command."
70
82
  `);
71
- expect(error).toMatchInlineSnapshot(
72
- `[Error: Unknown command: invalid-command.]`
73
- );
74
83
  });
75
84
  });
76
85
 
@@ -88,19 +97,27 @@ describe("wrangler", () => {
88
97
  });
89
98
 
90
99
  it("should display warning when wrangler.toml already exists, and exit if user does not want to carry on", async () => {
91
- fs.writeFileSync("./wrangler.toml", "", "utf-8");
100
+ fs.writeFileSync(
101
+ "./wrangler.toml",
102
+ 'compatibility_date="something-else"', // use a fake value to make sure the file is not overwritten
103
+ "utf-8"
104
+ );
92
105
  mockConfirm({
93
106
  text: "Do you want to continue initializing this project?",
94
107
  result: false,
95
108
  });
96
- const { warnings } = await runWrangler("init");
97
- expect(warnings).toContain("wrangler.toml file already exists!");
109
+ await runWrangler("init");
110
+ expect(std.warn).toContain("wrangler.toml file already exists!");
98
111
  const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8"));
99
- expect(typeof parsed.compatibility_date).toBe("undefined");
112
+ expect(parsed.compatibility_date).toBe("something-else");
100
113
  });
101
114
 
102
115
  it("should display warning when wrangler.toml already exists, but continue if user does want to carry on", async () => {
103
- fs.writeFileSync("./wrangler.toml", "", "utf-8");
116
+ fs.writeFileSync(
117
+ "./wrangler.toml",
118
+ `compatibility_date="something-else"`,
119
+ "utf-8"
120
+ );
104
121
  mockConfirm(
105
122
  {
106
123
  text: "Do you want to continue initializing this project?",
@@ -111,10 +128,10 @@ describe("wrangler", () => {
111
128
  result: false,
112
129
  }
113
130
  );
114
- const { warnings } = await runWrangler("init");
115
- expect(warnings).toContain("wrangler.toml file already exists!");
131
+ await runWrangler("init");
132
+ expect(std.warn).toContain("wrangler.toml file already exists!");
116
133
  const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8"));
117
- expect(typeof parsed.compatibility_date).toBe("string");
134
+ expect(parsed.compatibility_date).toBe("something-else");
118
135
  });
119
136
 
120
137
  it("should create a package.json if none is found and user confirms", async () => {
@@ -135,14 +152,23 @@ describe("wrangler", () => {
135
152
  );
136
153
  expect(packageJson.name).toEqual("worker"); // TODO: should we infer the name from the directory?
137
154
  expect(packageJson.version).toEqual("0.0.1");
155
+ expect(packageJson.devDependencies).toEqual({
156
+ wrangler: expect.any(String),
157
+ });
138
158
  expect(fs.existsSync("./tsconfig.json")).toBe(false);
139
159
  });
140
160
 
141
161
  it("should not touch an existing package.json in the same directory", async () => {
142
- mockConfirm({
143
- text: "Would you like to use typescript?",
144
- result: false,
145
- });
162
+ mockConfirm(
163
+ {
164
+ text: "Would you like to install wrangler into your package.json?",
165
+ result: false,
166
+ },
167
+ {
168
+ text: "Would you like to use typescript?",
169
+ result: false,
170
+ }
171
+ );
146
172
 
147
173
  fs.writeFileSync(
148
174
  "./package.json",
@@ -158,11 +184,46 @@ describe("wrangler", () => {
158
184
  expect(packageJson.version).toEqual("1.0.0");
159
185
  });
160
186
 
161
- it("should not touch an existing package.json in an ancestor directory", async () => {
162
- mockConfirm({
163
- text: "Would you like to use typescript?",
164
- result: false,
187
+ it("should offer to install wrangler into an existing package.json", async () => {
188
+ mockConfirm(
189
+ {
190
+ text: "Would you like to install wrangler into your package.json?",
191
+ result: true,
192
+ },
193
+ {
194
+ text: "Would you like to use typescript?",
195
+ result: false,
196
+ }
197
+ );
198
+
199
+ fs.writeFileSync(
200
+ "./package.json",
201
+ JSON.stringify({ name: "test", version: "1.0.0" }),
202
+ "utf-8"
203
+ );
204
+
205
+ await runWrangler("init");
206
+ const packageJson = JSON.parse(
207
+ fs.readFileSync("./package.json", "utf-8")
208
+ );
209
+ expect(packageJson.name).toEqual("test");
210
+ expect(packageJson.version).toEqual("1.0.0");
211
+ expect(packageJson.devDependencies).toEqual({
212
+ wrangler: expect.any(String),
165
213
  });
214
+ });
215
+
216
+ it("should not touch an existing package.json in an ancestor directory", async () => {
217
+ mockConfirm(
218
+ {
219
+ text: "Would you like to install wrangler into your package.json?",
220
+ result: false,
221
+ },
222
+ {
223
+ text: "Would you like to use typescript?",
224
+ result: false,
225
+ }
226
+ );
166
227
 
167
228
  fs.writeFileSync(
168
229
  "./package.json",
@@ -180,8 +241,12 @@ describe("wrangler", () => {
180
241
  const packageJson = JSON.parse(
181
242
  fs.readFileSync("../../package.json", "utf-8")
182
243
  );
183
- expect(packageJson.name).toEqual("test");
184
- expect(packageJson.version).toEqual("1.0.0");
244
+ expect(packageJson).toMatchInlineSnapshot(`
245
+ Object {
246
+ "name": "test",
247
+ "version": "1.0.0",
248
+ }
249
+ `);
185
250
  });
186
251
 
187
252
  it("should create a tsconfig.json and install `workers-types` if none is found and user confirms", async () => {
@@ -208,13 +273,21 @@ describe("wrangler", () => {
208
273
  );
209
274
  expect(packageJson.devDependencies).toEqual({
210
275
  "@cloudflare/workers-types": expect.any(String),
276
+ wrangler: expect.any(String),
211
277
  });
212
278
  });
213
279
 
214
280
  it("should not touch an existing tsconfig.json in the same directory", async () => {
215
281
  fs.writeFileSync(
216
282
  "./package.json",
217
- JSON.stringify({ name: "test", version: "1.0.0" }),
283
+ JSON.stringify({
284
+ name: "test",
285
+ version: "1.0.0",
286
+ devDependencies: {
287
+ wrangler: "0.0.0",
288
+ "@cloudflare/workers-types": "0.0.0",
289
+ },
290
+ }),
218
291
  "utf-8"
219
292
  );
220
293
  fs.writeFileSync(
@@ -230,10 +303,56 @@ describe("wrangler", () => {
230
303
  expect(tsconfigJson.compilerOptions).toEqual({});
231
304
  });
232
305
 
233
- it("should not touch an existing package.json in an ancestor directory", async () => {
306
+ it("should offer to install type definitions in an existing typescript project", async () => {
307
+ mockConfirm(
308
+ {
309
+ text: "Would you like to install wrangler into your package.json?",
310
+ result: false,
311
+ },
312
+ {
313
+ text: "Would you like to install the type definitions for Workers into your package.json?",
314
+ result: true,
315
+ }
316
+ );
234
317
  fs.writeFileSync(
235
318
  "./package.json",
236
- JSON.stringify({ name: "test", version: "1.0.0" }),
319
+ JSON.stringify({
320
+ name: "test",
321
+ version: "1.0.0",
322
+ }),
323
+ "utf-8"
324
+ );
325
+ fs.writeFileSync(
326
+ "./tsconfig.json",
327
+ JSON.stringify({ compilerOptions: {} }),
328
+ "utf-8"
329
+ );
330
+
331
+ await runWrangler("init");
332
+ const tsconfigJson = JSON.parse(
333
+ fs.readFileSync("./tsconfig.json", "utf-8")
334
+ );
335
+ // unchanged tsconfig
336
+ expect(tsconfigJson.compilerOptions).toEqual({});
337
+ const packageJson = JSON.parse(
338
+ fs.readFileSync("./package.json", "utf-8")
339
+ );
340
+ expect(packageJson.devDependencies).toEqual({
341
+ "@cloudflare/workers-types": expect.any(String),
342
+ });
343
+ });
344
+
345
+ it("should not touch an existing tsconfig.json in an ancestor directory", async () => {
346
+ fs.writeFileSync(
347
+ "./package.json",
348
+ JSON.stringify({
349
+ name: "test",
350
+ version: "1.0.0",
351
+ devDependencies: {
352
+ wrangler: "0.0.0",
353
+ "@cloudflare/workers-types": "0.0.0",
354
+ },
355
+ }),
237
356
  "utf-8"
238
357
  );
239
358
  fs.writeFileSync(
@@ -256,32 +375,50 @@ describe("wrangler", () => {
256
375
  });
257
376
 
258
377
  it("should error if `--type` is used", async () => {
259
- const { error } = await runWrangler("init --type");
260
- expect(error).toMatchInlineSnapshot(
261
- `[Error: The --type option is no longer supported.]`
262
- );
378
+ let err: undefined | Error;
379
+ try {
380
+ await runWrangler("init --type");
381
+ } catch (e) {
382
+ err = e;
383
+ } finally {
384
+ expect(err?.message).toBe(`The --type option is no longer supported.`);
385
+ }
263
386
  });
264
387
 
265
388
  it("should error if `--type javascript` is used", async () => {
266
- const { error } = await runWrangler("init --type javascript");
267
- expect(error).toMatchInlineSnapshot(
268
- `[Error: The --type option is no longer supported.]`
269
- );
389
+ let err: undefined | Error;
390
+ try {
391
+ await runWrangler("init --type javascript");
392
+ } catch (e) {
393
+ err = e;
394
+ } finally {
395
+ expect(err?.message).toBe(`The --type option is no longer supported.`);
396
+ }
270
397
  });
271
398
 
272
399
  it("should error if `--type rust` is used", async () => {
273
- const { error } = await runWrangler("init --type rust");
274
- expect(error).toMatchInlineSnapshot(
275
- `[Error: The --type option is no longer supported.]`
276
- );
400
+ let err: undefined | Error;
401
+ try {
402
+ await runWrangler("init --type rust");
403
+ } catch (e) {
404
+ err = e;
405
+ } finally {
406
+ expect(err?.message).toBe(`The --type option is no longer supported.`);
407
+ }
277
408
  });
278
409
 
279
410
  it("should error if `--type webpack` is used", async () => {
280
- const { error } = await runWrangler("init --type webpack");
281
- expect(error).toMatchInlineSnapshot(`
282
- [Error: The --type option is no longer supported.
283
- If you wish to use webpack then you will need to create a custom build.]
284
- `);
411
+ let err: undefined | Error;
412
+ try {
413
+ await runWrangler("init --type webpack");
414
+ } catch (e) {
415
+ err = e;
416
+ } finally {
417
+ expect(err?.message).toBe(
418
+ `The --type option is no longer supported.
419
+ If you wish to use webpack then you will need to create a custom build.`
420
+ );
421
+ }
285
422
  });
286
423
  });
287
424
  });