Skip to content
This repository was archived by the owner on Feb 3, 2022. It is now read-only.

Commit 10875a3

Browse files
committed
Better error handling for missing templates.
1 parent fccec9f commit 10875a3

File tree

3 files changed

+106
-40
lines changed

3 files changed

+106
-40
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
"license": "MIT",
2525
"devDependencies": {
2626
"jest": "^24.5.0",
27-
"nock": "^10.0.6",
28-
"rimraf": "^2.6.3"
27+
"nock": "^10.0.6"
2928
},
3029
"dependencies": {
3130
"boxen": "^3.0.0",
@@ -35,7 +34,8 @@
3534
"ora": "^3.2.0",
3635
"pkg-install": "^1.0.0",
3736
"twilio-run": "^2.0.0-beta.12",
38-
"yargs": "^12.0.5"
37+
"yargs": "^12.0.5",
38+
"rimraf": "^2.6.3"
3939
},
4040
"engines": {
4141
"node": ">=8.0.0"

src/create-twilio-function.js

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,45 @@ const successMessage = require('./create-twilio-function/success-message');
1515
const ora = require('ora');
1616
const boxen = require('boxen');
1717
const { downloadTemplate } = require('twilio-run/dist/templating/actions');
18+
const { promisify } = require('util');
19+
const rimraf = promisify(require('rimraf'));
20+
21+
async function cleanUpAndExit(projectDir, spinner, errorMessage) {
22+
spinner.fail(errorMessage);
23+
spinner.start('Cleaning up project directories and files');
24+
await rimraf(projectDir);
25+
spinner.stop().clear();
26+
process.exitCode = 1;
27+
}
1828

1929
async function createTwilioFunction(config) {
2030
const projectDir = `${config.path}/${config.name}`;
31+
const spinner = ora();
2132

2233
try {
34+
spinner.start('Creating project directory');
2335
await createDirectory(config.path, config.name);
36+
spinner.succeed();
2437
} catch (e) {
2538
switch (e.code) {
2639
case 'EEXIST':
27-
console.error(
40+
spinner.fail(
2841
`A directory called '${
2942
config.name
3043
}' already exists. Please create your function in a new directory.`
3144
);
3245
break;
3346
case 'EACCES':
34-
console.error(
47+
spinner.fail(
3548
`You do not have permission to create files or directories in the path '${
3649
config.path
3750
}'.`
3851
);
3952
break;
4053
default:
41-
console.error(e.message);
54+
spinner.fail(e.message);
4255
}
56+
process.exitCode = 1;
4357
return;
4458
}
4559

@@ -51,33 +65,58 @@ async function createTwilioFunction(config) {
5165
config = { ...accountDetails, ...config };
5266

5367
// Scaffold project
54-
const spinner = ora();
5568
spinner.start('Creating project directories and files');
5669

5770
await createEnvFile(projectDir, {
5871
accountSid: config.accountSid,
5972
authToken: config.authToken
6073
});
6174
await createNvmrcFile(projectDir);
75+
await createPackageJSON(projectDir, config.name);
6276
if (config.template) {
77+
spinner.succeed();
78+
spinner.start(`Downloading template: "${config.template}"`);
6379
await createDirectory(projectDir, 'functions');
6480
await createDirectory(projectDir, 'assets');
65-
await downloadTemplate(config.template, '', projectDir);
81+
try {
82+
await downloadTemplate(config.template, '', projectDir);
83+
spinner.succeed();
84+
} catch (err) {
85+
await cleanUpAndExit(
86+
projectDir,
87+
spinner,
88+
`The template "${config.template}" doesn't exist`
89+
);
90+
return;
91+
}
6692
} else {
6793
await createExampleFromTemplates(projectDir);
94+
spinner.succeed();
6895
}
69-
await createPackageJSON(projectDir, config.name);
70-
spinner.succeed();
7196

7297
// Download .gitignore file from https://github.com/github/gitignore/
73-
spinner.start('Downloading .gitignore file');
74-
await createGitignore(projectDir);
75-
spinner.succeed();
98+
try {
99+
spinner.start('Downloading .gitignore file');
100+
await createGitignore(projectDir);
101+
spinner.succeed();
102+
} catch (err) {
103+
cleanUpAndExit(projectDir, spinner, 'Could not download .gitignore file');
104+
return;
105+
}
76106

77107
// Install dependencies with npm
78-
spinner.start('Installing dependencies');
79-
await installDependencies(projectDir);
80-
spinner.succeed();
108+
try {
109+
spinner.start('Installing dependencies');
110+
await installDependencies(projectDir);
111+
spinner.succeed();
112+
} catch (err) {
113+
spinner.fail();
114+
console.log(
115+
`There was an error installing the dependencies, but your project is otherwise complete in ./${
116+
config.name
117+
}`
118+
);
119+
}
81120

82121
// Success message
83122

tests/create-twilio-function.test.js

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ jest.mock('ora');
1717
jest.mock('boxen', () => {
1818
return () => 'success message';
1919
});
20+
const spinner = {
21+
start: () => spinner,
22+
succeed: () => spinner,
23+
fail: () => spinner,
24+
clear: () => spinner,
25+
stop: () => spinner
26+
};
2027
ora.mockImplementation(() => {
21-
const spinner = {
22-
start: () => spinner,
23-
succeed: () => spinner
24-
};
2528
return spinner;
2629
});
2730
jest.mock('../src/create-twilio-function/install-dependencies.js', () => {
@@ -49,19 +52,19 @@ afterEach(async () => {
4952

5053
describe('createTwilioFunction', () => {
5154
beforeEach(() => {
52-
nock('https://raw.githubusercontent.com')
53-
.get('/github/gitignore/master/Node.gitignore')
54-
.reply(200, '*.log\n.env');
55-
});
56-
57-
test('it scaffolds a Twilio Function', async () => {
5855
inquirer.prompt = jest.fn(() =>
5956
Promise.resolve({
6057
accountSid: 'test-sid',
6158
authToken: 'test-auth-token'
6259
})
6360
);
6461

62+
nock('https://raw.githubusercontent.com')
63+
.get('/github/gitignore/master/Node.gitignore')
64+
.reply(200, '*.log\n.env');
65+
});
66+
67+
it('scaffolds a Twilio Function', async () => {
6568
const name = 'test-function';
6669
await createTwilioFunction({ name, path: './scratch' });
6770

@@ -95,14 +98,7 @@ describe('createTwilioFunction', () => {
9598
expect(console.log).toHaveBeenCalledWith('success message');
9699
});
97100

98-
test('it scaffolds a Twilio Function with a template', async () => {
99-
inquirer.prompt = jest.fn(() =>
100-
Promise.resolve({
101-
accountSid: 'test-sid',
102-
authToken: 'test-auth-token'
103-
})
104-
);
105-
101+
it('scaffolds a Twilio Function with a template', async () => {
106102
const gitHubAPI = nock('https://api.github.com');
107103
gitHubAPI
108104
.get('/repos/twilio-labs/function-templates/contents/blank?ref=next')
@@ -185,17 +181,48 @@ describe('createTwilioFunction', () => {
185181
expect(console.log).toHaveBeenCalledWith('success message');
186182
});
187183

184+
it('handles a missing template gracefully', async () => {
185+
const templateName = 'missing';
186+
const name = 'test-function';
187+
const gitHubAPI = nock('https://api.github.com');
188+
gitHubAPI
189+
.get(
190+
`/repos/twilio-labs/function-templates/contents/${templateName}?ref=next`
191+
)
192+
.reply(404);
193+
194+
const fail = jest.spyOn(spinner, 'fail');
195+
196+
await createTwilioFunction({
197+
name,
198+
path: './scratch',
199+
template: templateName
200+
});
201+
202+
expect.assertions(3);
203+
204+
expect(fail).toHaveBeenCalledTimes(1);
205+
expect(fail).toHaveBeenCalledWith(
206+
`The template "${templateName}" doesn't exist`
207+
);
208+
try {
209+
await stat(`./scratch/${name}`);
210+
} catch (e) {
211+
expect(e.toString()).toMatch('no such file or directory');
212+
}
213+
});
214+
188215
it("doesn't scaffold if the target folder name already exists", async () => {
189216
const name = 'test-function';
190217
await mkdir('./scratch/test-function');
191-
console.error = jest.fn();
218+
const fail = jest.spyOn(spinner, 'fail');
192219

193220
await createTwilioFunction({ name, path: './scratch' });
194221

195222
expect.assertions(4);
196223

197-
expect(console.error).toHaveBeenCalledTimes(1);
198-
expect(console.error).toHaveBeenCalledWith(
224+
expect(fail).toHaveBeenCalledTimes(1);
225+
expect(fail).toHaveBeenCalledWith(
199226
`A directory called '${name}' already exists. Please create your function in a new directory.`
200227
);
201228
expect(console.log).not.toHaveBeenCalled();
@@ -211,14 +238,14 @@ describe('createTwilioFunction', () => {
211238
const name = 'test-function';
212239
const chmod = promisify(fs.chmod);
213240
await chmod('./scratch', 0o555);
214-
console.error = jest.fn();
241+
const fail = jest.spyOn(spinner, 'fail');
215242

216243
await createTwilioFunction({ name, path: './scratch' });
217244

218245
expect.assertions(4);
219246

220-
expect(console.error).toHaveBeenCalledTimes(1);
221-
expect(console.error).toHaveBeenCalledWith(
247+
expect(fail).toHaveBeenCalledTimes(1);
248+
expect(fail).toHaveBeenCalledWith(
222249
`You do not have permission to create files or directories in the path './scratch'.`
223250
);
224251
expect(console.log).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)