Skip to content

Commit a5afdae

Browse files
committed
feat: enhance wrangler init
This PR adds some enhancements/fixes to the `wrangler init` command. - doesn't overwrite `wrangler.toml` if it already exists - installs `wrangler` when creating `package.json` - offers to install `wrangler` into `package.json` even if `package.json` already exists - offers to install `@cloudflare/workers-types` even if `tsconfig.json` already exists - pipes stdio back to the terminal so there's feedback when it's installing npm packages This does have the side effect of making our tests a _little_ slower. I added `--prefer-offline` to the `npm install` calls to make this a shade quicker, but I can't figure out a good way of mocking these. I'll think about it some more later. We should work on making the installs themselves quicker (re: #66) This PR also fixes an issue with our tests: `runWrangler()` would catch thrown errors, and if we didn't manually verify the error, tests would pass. Instead, it now throws correctly, and I modified all the tests to assert on thrown errors. It seems like a lot, but it was just mechanical rewriting.
1 parent 71b0fab commit a5afdae

File tree

8 files changed

+644
-375
lines changed

8 files changed

+644
-375
lines changed

.changeset/sour-toys-heal.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: enhance `wrangler init`
6+
7+
This PR adds some enhancements/fixes to the `wrangler init` command.
8+
9+
- doesn't overwrite `wrangler.toml` if it already exists
10+
- installs `wrangler` when creating `package.json`
11+
- offers to install `wrangler` into `package.json` even if `package.json` already exists
12+
- offers to install `@cloudflare/workers-types` even if `tsconfig.json` already exists
13+
- pipes stdio back to the terminal so there's feedback when it's installing npm packages
14+
15+
This does have the side effect of making out tests slower. I added `--prefer-offline` to the `npm install` calls to make this a shade quicker, but I can't figure out a good way of mocking these. I'll think about it some more later. We should work on making the installs themselves quicker (re: https://github.com/cloudflare/wrangler2/issues/66)
16+
17+
This PR also fixes a bug with our tests - `runWrangler` would catch thrown errors, and if we didn't manually verify the error, tests would pass. Instead, it now throws correctly, and I modified all the tests to assert on thrown errors. It seems like a lot, but it was just mechanical rewriting.

packages/wrangler/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
},
108108
"jest": {
109109
"restoreMocks": true,
110+
"testTimeout": 30000,
110111
"testRegex": ".*.(test|spec)\\.[jt]sx?$",
111112
"transformIgnorePatterns": [
112113
"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)"

packages/wrangler/src/__tests__/index.test.ts

+177-48
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import * as TOML from "@iarna/toml";
33
import { mockConfirm } from "./mock-dialogs";
44
import { runWrangler } from "./run-wrangler";
55
import { runInTempDir } from "./run-in-tmp";
6+
import { mockConsoleMethods } from "./mock-console";
67
import * as fs from "node:fs";
78

89
describe("wrangler", () => {
910
runInTempDir();
1011

12+
const std = mockConsoleMethods();
13+
1114
describe("no command", () => {
1215
it("should display a list of available commands", async () => {
13-
const { stdout, stderr } = await runWrangler();
16+
await runWrangler();
1417

15-
expect(stdout).toMatchInlineSnapshot(`
18+
expect(std.out).toMatchInlineSnapshot(`
1619
"wrangler
1720
1821
Commands:
@@ -36,16 +39,23 @@ describe("wrangler", () => {
3639
-l, --local Run on my machine [boolean] [default: false]"
3740
`);
3841

39-
expect(stderr).toMatchInlineSnapshot(`""`);
42+
expect(std.err).toMatchInlineSnapshot(`""`);
4043
});
4144
});
4245

4346
describe("invalid command", () => {
4447
it("should display an error", async () => {
45-
const { error, stdout, stderr } = await runWrangler("invalid-command");
46-
47-
expect(stdout).toMatchInlineSnapshot(`""`);
48-
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(`
4959
"wrangler
5060
5161
Commands:
@@ -70,9 +80,6 @@ describe("wrangler", () => {
7080
7181
Unknown command: invalid-command."
7282
`);
73-
expect(error).toMatchInlineSnapshot(
74-
`[Error: Unknown command: invalid-command.]`
75-
);
7683
});
7784
});
7885

@@ -90,15 +97,19 @@ describe("wrangler", () => {
9097
});
9198

9299
it("should display warning when wrangler.toml already exists, and exit if user does not want to carry on", async () => {
93-
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+
);
94105
mockConfirm({
95106
text: "Do you want to continue initializing this project?",
96107
result: false,
97108
});
98-
const { warnings } = await runWrangler("init");
99-
expect(warnings).toContain("wrangler.toml file already exists!");
109+
await runWrangler("init");
110+
expect(std.warn).toContain("wrangler.toml file already exists!");
100111
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8"));
101-
expect(typeof parsed.compatibility_date).toBe("undefined");
112+
expect(parsed.compatibility_date).toBe("something-else");
102113
});
103114

104115
it("should display warning when wrangler.toml already exists, but continue if user does want to carry on", async () => {
@@ -113,10 +124,8 @@ describe("wrangler", () => {
113124
result: false,
114125
}
115126
);
116-
const { warnings } = await runWrangler("init");
117-
expect(warnings).toContain("wrangler.toml file already exists!");
118-
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8"));
119-
expect(typeof parsed.compatibility_date).toBe("string");
127+
await runWrangler("init");
128+
expect(std.warn).toContain("wrangler.toml file already exists!");
120129
});
121130

122131
it("should create a package.json if none is found and user confirms", async () => {
@@ -137,14 +146,23 @@ describe("wrangler", () => {
137146
);
138147
expect(packageJson.name).toEqual("worker"); // TODO: should we infer the name from the directory?
139148
expect(packageJson.version).toEqual("0.0.1");
149+
expect(packageJson.devDependencies).toEqual({
150+
wrangler: expect.any(String),
151+
});
140152
expect(fs.existsSync("./tsconfig.json")).toBe(false);
141153
});
142154

143155
it("should not touch an existing package.json in the same directory", async () => {
144-
mockConfirm({
145-
text: "Would you like to use typescript?",
146-
result: false,
147-
});
156+
mockConfirm(
157+
{
158+
text: "Would you like to install wrangler into your package.json?",
159+
result: false,
160+
},
161+
{
162+
text: "Would you like to use typescript?",
163+
result: false,
164+
}
165+
);
148166

149167
fs.writeFileSync(
150168
"./package.json",
@@ -160,11 +178,46 @@ describe("wrangler", () => {
160178
expect(packageJson.version).toEqual("1.0.0");
161179
});
162180

163-
it("should not touch an existing package.json in an ancestor directory", async () => {
164-
mockConfirm({
165-
text: "Would you like to use typescript?",
166-
result: false,
181+
it("should offer to install wrangler into an existing package.json", async () => {
182+
mockConfirm(
183+
{
184+
text: "Would you like to install wrangler into your package.json?",
185+
result: true,
186+
},
187+
{
188+
text: "Would you like to use typescript?",
189+
result: false,
190+
}
191+
);
192+
193+
fs.writeFileSync(
194+
"./package.json",
195+
JSON.stringify({ name: "test", version: "1.0.0" }),
196+
"utf-8"
197+
);
198+
199+
await runWrangler("init");
200+
const packageJson = JSON.parse(
201+
fs.readFileSync("./package.json", "utf-8")
202+
);
203+
expect(packageJson.name).toEqual("test");
204+
expect(packageJson.version).toEqual("1.0.0");
205+
expect(packageJson.devDependencies).toEqual({
206+
wrangler: expect.any(String),
167207
});
208+
});
209+
210+
it("should not touch an existing package.json in an ancestor directory", async () => {
211+
mockConfirm(
212+
{
213+
text: "Would you like to install wrangler into your package.json?",
214+
result: false,
215+
},
216+
{
217+
text: "Would you like to use typescript?",
218+
result: false,
219+
}
220+
);
168221

169222
fs.writeFileSync(
170223
"./package.json",
@@ -182,8 +235,12 @@ describe("wrangler", () => {
182235
const packageJson = JSON.parse(
183236
fs.readFileSync("../../package.json", "utf-8")
184237
);
185-
expect(packageJson.name).toEqual("test");
186-
expect(packageJson.version).toEqual("1.0.0");
238+
expect(packageJson).toMatchInlineSnapshot(`
239+
Object {
240+
"name": "test",
241+
"version": "1.0.0",
242+
}
243+
`);
187244
});
188245

189246
it("should create a tsconfig.json and install `workers-types` if none is found and user confirms", async () => {
@@ -210,13 +267,21 @@ describe("wrangler", () => {
210267
);
211268
expect(packageJson.devDependencies).toEqual({
212269
"@cloudflare/workers-types": expect.any(String),
270+
wrangler: expect.any(String),
213271
});
214272
});
215273

216274
it("should not touch an existing tsconfig.json in the same directory", async () => {
217275
fs.writeFileSync(
218276
"./package.json",
219-
JSON.stringify({ name: "test", version: "1.0.0" }),
277+
JSON.stringify({
278+
name: "test",
279+
version: "1.0.0",
280+
devDependencies: {
281+
wrangler: "0.0.0",
282+
"@cloudflare/workers-types": "0.0.0",
283+
},
284+
}),
220285
"utf-8"
221286
);
222287
fs.writeFileSync(
@@ -232,10 +297,56 @@ describe("wrangler", () => {
232297
expect(tsconfigJson.compilerOptions).toEqual({});
233298
});
234299

235-
it("should not touch an existing package.json in an ancestor directory", async () => {
300+
it("should offer to install type definitions in an existing typescript project", async () => {
301+
mockConfirm(
302+
{
303+
text: "Would you like to install wrangler into your package.json?",
304+
result: false,
305+
},
306+
{
307+
text: "Would you like to install the type definitions for Workers into your package.json?",
308+
result: true,
309+
}
310+
);
236311
fs.writeFileSync(
237312
"./package.json",
238-
JSON.stringify({ name: "test", version: "1.0.0" }),
313+
JSON.stringify({
314+
name: "test",
315+
version: "1.0.0",
316+
}),
317+
"utf-8"
318+
);
319+
fs.writeFileSync(
320+
"./tsconfig.json",
321+
JSON.stringify({ compilerOptions: {} }),
322+
"utf-8"
323+
);
324+
325+
await runWrangler("init");
326+
const tsconfigJson = JSON.parse(
327+
fs.readFileSync("./tsconfig.json", "utf-8")
328+
);
329+
// unchanged tsconfig
330+
expect(tsconfigJson.compilerOptions).toEqual({});
331+
const packageJson = JSON.parse(
332+
fs.readFileSync("./package.json", "utf-8")
333+
);
334+
expect(packageJson.devDependencies).toEqual({
335+
"@cloudflare/workers-types": expect.any(String),
336+
});
337+
});
338+
339+
it("should not touch an existing tsconfig.json in an ancestor directory", async () => {
340+
fs.writeFileSync(
341+
"./package.json",
342+
JSON.stringify({
343+
name: "test",
344+
version: "1.0.0",
345+
devDependencies: {
346+
wrangler: "0.0.0",
347+
"@cloudflare/workers-types": "0.0.0",
348+
},
349+
}),
239350
"utf-8"
240351
);
241352
fs.writeFileSync(
@@ -258,32 +369,50 @@ describe("wrangler", () => {
258369
});
259370

260371
it("should error if `--type` is used", async () => {
261-
const { error } = await runWrangler("init --type");
262-
expect(error).toMatchInlineSnapshot(
263-
`[Error: The --type option is no longer supported.]`
264-
);
372+
let err: undefined | Error;
373+
try {
374+
await runWrangler("init --type");
375+
} catch (e) {
376+
err = e;
377+
} finally {
378+
expect(err?.message).toBe(`The --type option is no longer supported.`);
379+
}
265380
});
266381

267382
it("should error if `--type javascript` is used", async () => {
268-
const { error } = await runWrangler("init --type javascript");
269-
expect(error).toMatchInlineSnapshot(
270-
`[Error: The --type option is no longer supported.]`
271-
);
383+
let err: undefined | Error;
384+
try {
385+
await runWrangler("init --type javascript");
386+
} catch (e) {
387+
err = e;
388+
} finally {
389+
expect(err?.message).toBe(`The --type option is no longer supported.`);
390+
}
272391
});
273392

274393
it("should error if `--type rust` is used", async () => {
275-
const { error } = await runWrangler("init --type rust");
276-
expect(error).toMatchInlineSnapshot(
277-
`[Error: The --type option is no longer supported.]`
278-
);
394+
let err: undefined | Error;
395+
try {
396+
await runWrangler("init --type rust");
397+
} catch (e) {
398+
err = e;
399+
} finally {
400+
expect(err?.message).toBe(`The --type option is no longer supported.`);
401+
}
279402
});
280403

281404
it("should error if `--type webpack` is used", async () => {
282-
const { error } = await runWrangler("init --type webpack");
283-
expect(error).toMatchInlineSnapshot(`
284-
[Error: The --type option is no longer supported.
285-
If you wish to use webpack then you will need to create a custom build.]
286-
`);
405+
let err: undefined | Error;
406+
try {
407+
await runWrangler("init --type webpack");
408+
} catch (e) {
409+
err = e;
410+
} finally {
411+
expect(err?.message).toBe(
412+
`The --type option is no longer supported.
413+
If you wish to use webpack then you will need to create a custom build.`
414+
);
415+
}
287416
});
288417
});
289418
});

0 commit comments

Comments
 (0)