Skip to content

Commit 88395bb

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 88395bb

File tree

8 files changed

+638
-372
lines changed

8 files changed

+638
-372
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/src/__tests__/index.test.ts

+171-45
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, std } from "./mock-console";
67
import * as fs from "node:fs";
78

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

12+
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,24 @@ 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+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
49+
let err: Error | undefined;
50+
try {
51+
await runWrangler("invalid-command");
52+
} catch (e) {
53+
err = e;
54+
} finally {
55+
expect(err?.message).toBe(`Unknown command: invalid-command.`);
56+
}
57+
58+
expect(std.out).toMatchInlineSnapshot(`""`);
59+
expect(std.err).toMatchInlineSnapshot(`
4960
"wrangler
5061
5162
Commands:
@@ -70,9 +81,6 @@ describe("wrangler", () => {
7081
7182
Unknown command: invalid-command."
7283
`);
73-
expect(error).toMatchInlineSnapshot(
74-
`[Error: Unknown command: invalid-command.]`
75-
);
7684
});
7785
});
7886

@@ -90,15 +98,19 @@ describe("wrangler", () => {
9098
});
9199

92100
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");
101+
fs.writeFileSync(
102+
"./wrangler.toml",
103+
'compatibility_date="something-else"', // use a fake value to make sure the file is not overwritten
104+
"utf-8"
105+
);
94106
mockConfirm({
95107
text: "Do you want to continue initializing this project?",
96108
result: false,
97109
});
98-
const { warnings } = await runWrangler("init");
99-
expect(warnings).toContain("wrangler.toml file already exists!");
110+
await runWrangler("init");
111+
expect(std.warn).toContain("wrangler.toml file already exists!");
100112
const parsed = TOML.parse(await fsp.readFile("./wrangler.toml", "utf-8"));
101-
expect(typeof parsed.compatibility_date).toBe("undefined");
113+
expect(parsed.compatibility_date).toBe("something-else");
102114
});
103115

104116
it("should display warning when wrangler.toml already exists, but continue if user does want to carry on", async () => {
@@ -113,10 +125,8 @@ describe("wrangler", () => {
113125
result: false,
114126
}
115127
);
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");
128+
await runWrangler("init");
129+
expect(std.warn).toContain("wrangler.toml file already exists!");
120130
});
121131

122132
it("should create a package.json if none is found and user confirms", async () => {
@@ -137,14 +147,23 @@ describe("wrangler", () => {
137147
);
138148
expect(packageJson.name).toEqual("worker"); // TODO: should we infer the name from the directory?
139149
expect(packageJson.version).toEqual("0.0.1");
150+
expect(packageJson.devDependencies).toEqual({
151+
wrangler: expect.any(String),
152+
});
140153
expect(fs.existsSync("./tsconfig.json")).toBe(false);
141154
});
142155

143156
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-
});
157+
mockConfirm(
158+
{
159+
text: "Would you like to install wrangler into your package.json?",
160+
result: false,
161+
},
162+
{
163+
text: "Would you like to use typescript?",
164+
result: false,
165+
}
166+
);
148167

149168
fs.writeFileSync(
150169
"./package.json",
@@ -160,11 +179,46 @@ describe("wrangler", () => {
160179
expect(packageJson.version).toEqual("1.0.0");
161180
});
162181

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

169223
fs.writeFileSync(
170224
"./package.json",
@@ -210,13 +264,53 @@ describe("wrangler", () => {
210264
);
211265
expect(packageJson.devDependencies).toEqual({
212266
"@cloudflare/workers-types": expect.any(String),
267+
wrangler: expect.any(String),
213268
});
214269
});
215270

216271
it("should not touch an existing tsconfig.json in the same directory", async () => {
217272
fs.writeFileSync(
218273
"./package.json",
219-
JSON.stringify({ name: "test", version: "1.0.0" }),
274+
JSON.stringify({
275+
name: "test",
276+
version: "1.0.0",
277+
devDependencies: {
278+
wrangler: "0.0.0",
279+
"@cloudflare/workers-types": "0.0.0",
280+
},
281+
}),
282+
"utf-8"
283+
);
284+
fs.writeFileSync(
285+
"./tsconfig.json",
286+
JSON.stringify({ compilerOptions: {} }),
287+
"utf-8"
288+
);
289+
290+
await runWrangler("init");
291+
const tsconfigJson = JSON.parse(
292+
fs.readFileSync("./tsconfig.json", "utf-8")
293+
);
294+
expect(tsconfigJson.compilerOptions).toEqual({});
295+
});
296+
297+
it("should offer to install type definitions in an existing typescript project", async () => {
298+
mockConfirm(
299+
{
300+
text: "Would you like to install wrangler into your package.json?",
301+
result: false,
302+
},
303+
{
304+
text: "Would you like to install the type definitions for Workers into your package.json?",
305+
result: true,
306+
}
307+
);
308+
fs.writeFileSync(
309+
"./package.json",
310+
JSON.stringify({
311+
name: "test",
312+
version: "1.0.0",
313+
}),
220314
"utf-8"
221315
);
222316
fs.writeFileSync(
@@ -229,13 +323,27 @@ describe("wrangler", () => {
229323
const tsconfigJson = JSON.parse(
230324
fs.readFileSync("./tsconfig.json", "utf-8")
231325
);
326+
// unchanged tsconfig
232327
expect(tsconfigJson.compilerOptions).toEqual({});
328+
const packageJson = JSON.parse(
329+
fs.readFileSync("./package.json", "utf-8")
330+
);
331+
expect(packageJson.devDependencies).toEqual({
332+
"@cloudflare/workers-types": expect.any(String),
333+
});
233334
});
234335

235336
it("should not touch an existing package.json in an ancestor directory", async () => {
236337
fs.writeFileSync(
237338
"./package.json",
238-
JSON.stringify({ name: "test", version: "1.0.0" }),
339+
JSON.stringify({
340+
name: "test",
341+
version: "1.0.0",
342+
devDependencies: {
343+
wrangler: "0.0.0",
344+
"@cloudflare/workers-types": "0.0.0",
345+
},
346+
}),
239347
"utf-8"
240348
);
241349
fs.writeFileSync(
@@ -258,32 +366,50 @@ describe("wrangler", () => {
258366
});
259367

260368
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-
);
369+
let err: undefined | Error;
370+
try {
371+
await runWrangler("init --type");
372+
} catch (e) {
373+
err = e;
374+
} finally {
375+
expect(err?.message).toBe(`The --type option is no longer supported.`);
376+
}
265377
});
266378

267379
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-
);
380+
let err: undefined | Error;
381+
try {
382+
await runWrangler("init --type javascript");
383+
} catch (e) {
384+
err = e;
385+
} finally {
386+
expect(err?.message).toBe(`The --type option is no longer supported.`);
387+
}
272388
});
273389

274390
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-
);
391+
let err: undefined | Error;
392+
try {
393+
await runWrangler("init --type rust");
394+
} catch (e) {
395+
err = e;
396+
} finally {
397+
expect(err?.message).toBe(`The --type option is no longer supported.`);
398+
}
279399
});
280400

281401
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-
`);
402+
let err: undefined | Error;
403+
try {
404+
await runWrangler("init --type webpack");
405+
} catch (e) {
406+
err = e;
407+
} finally {
408+
expect(err?.message).toBe(
409+
`The --type option is no longer supported.
410+
If you wish to use webpack then you will need to create a custom build.`
411+
);
412+
}
287413
});
288414
});
289415
});

packages/wrangler/src/__tests__/jest.setup.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ jest.mock("../dialogs");
2020
"Unexpected call to `prompt()`. You should use `mockPrompt()` to mock calls to `prompt()` with expectations. Search the codebase for `mockPrompt` to learn more."
2121
);
2222
});
23+
24+
jest.setTimeout(30000);

0 commit comments

Comments
 (0)