Skip to content

Commit bddad8c

Browse files
authored
feat(twilio-run): add runtime-handler version checks (#280)
1 parent a40291d commit bddad8c

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { mocked } from 'ts-jest/utils';
2+
import {
3+
checkForValidRuntimeHandlerVersion,
4+
isExactSemVerVersion,
5+
} from '../../src/checks/check-runtime-handler';
6+
import { logger } from '../../src/utils/logger';
7+
8+
jest.mock('../../src/utils/logger', () => {
9+
return {
10+
logger: {
11+
error: jest.fn(),
12+
},
13+
};
14+
});
15+
16+
describe('isExactSemVerVersion', () => {
17+
test('should not accept version ranges', () => {
18+
[
19+
'>=1.2.7',
20+
'>=1.2.7 <1.3.0',
21+
'1.2.7 || >=1.2.9 <2.0.0',
22+
'<1.3.0',
23+
'>1.2.3-alpha.3',
24+
'1.2.3 - 2.3',
25+
'>=1.2.3 <2.4.0-0',
26+
'1.x',
27+
'*',
28+
'~1.2.3',
29+
'~1.2',
30+
'~1.2.3-beta.2',
31+
'^1.2.3',
32+
'1.0',
33+
'1',
34+
'next',
35+
].forEach((version) => {
36+
expect(isExactSemVerVersion(version)).toEqual(false);
37+
});
38+
});
39+
40+
test('should return true for exact versions', () => {
41+
['1.1.0', '1.1.0-rc.1', '1.2.0-alpha.3'].forEach((version) => {
42+
expect(isExactSemVerVersion(version)).toEqual(true);
43+
});
44+
});
45+
});
46+
47+
describe('checkForValidRuntimeHandlerversion', () => {
48+
test('should by default accept a missing runtime-handler', () => {
49+
expect(checkForValidRuntimeHandlerVersion({})).toEqual(true);
50+
expect(checkForValidRuntimeHandlerVersion({ dependencies: {} })).toEqual(
51+
true
52+
);
53+
expect(checkForValidRuntimeHandlerVersion({ devDependencies: {} })).toEqual(
54+
true
55+
);
56+
});
57+
58+
test('should return false if missing runtime-handler and flag passed', () => {
59+
expect(checkForValidRuntimeHandlerVersion({}, true)).toEqual(false);
60+
expect(
61+
checkForValidRuntimeHandlerVersion({ dependencies: {} }, true)
62+
).toEqual(false);
63+
expect(
64+
checkForValidRuntimeHandlerVersion({ devDependencies: {} }, true)
65+
).toEqual(false);
66+
67+
expect(mocked(logger.error)).toHaveBeenCalledTimes(3);
68+
expect(mocked(logger.error).mock.calls[0][1]).toEqual(
69+
'Missing @twilio/runtime-handler declaration'
70+
);
71+
});
72+
73+
test('should detect if your runtime-handler is defined in devDependencies', () => {
74+
expect(
75+
checkForValidRuntimeHandlerVersion({
76+
devDependencies: { '@twilio/runtime-handler': '1.2.0' },
77+
})
78+
).toEqual(false);
79+
80+
expect(mocked(logger.error)).toHaveBeenCalledTimes(1);
81+
expect(mocked(logger.error).mock.calls[0][1]).toEqual(
82+
'Wrongly configured @twilio/runtime-handler declaration'
83+
);
84+
});
85+
86+
test('should specify the right version if possible', () => {
87+
expect(
88+
checkForValidRuntimeHandlerVersion({
89+
dependencies: { '@twilio/runtime-handler': '~1.2.0' },
90+
})
91+
).toEqual(false);
92+
93+
expect(mocked(logger.error)).toHaveBeenCalledTimes(1);
94+
expect(mocked(logger.error).mock.calls[0][1]).toEqual(
95+
'Invalid @twilio/runtime-handler version'
96+
);
97+
expect(mocked(logger.error).mock.calls[0][0].includes('"~1.2.0"')).toEqual(
98+
true
99+
);
100+
expect(mocked(logger.error).mock.calls[0][0].includes('"1.2.0"')).toEqual(
101+
true
102+
);
103+
});
104+
105+
test('should suggest a fallback when version is invalid', () => {
106+
expect(
107+
checkForValidRuntimeHandlerVersion({
108+
dependencies: { '@twilio/runtime-handler': 'alpha' },
109+
})
110+
).toEqual(false);
111+
112+
expect(mocked(logger.error)).toHaveBeenCalledTimes(1);
113+
expect(mocked(logger.error).mock.calls[0][1]).toEqual(
114+
'Invalid @twilio/runtime-handler version'
115+
);
116+
expect(mocked(logger.error).mock.calls[0][0].includes('"alpha"')).toEqual(
117+
true
118+
);
119+
expect(mocked(logger.error).mock.calls[0][0].includes('"1.1.0"')).toEqual(
120+
true
121+
);
122+
});
123+
124+
test('should pass a valid exact version', () => {
125+
expect(
126+
checkForValidRuntimeHandlerVersion({
127+
dependencies: { '@twilio/runtime-handler': '1.1.0' },
128+
})
129+
).toEqual(true);
130+
131+
expect(mocked(logger.error)).toHaveBeenCalledTimes(0);
132+
});
133+
});

packages/twilio-run/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@types/inquirer": "^6.0.3",
4242
"@types/is-ci": "^2.0.0",
4343
"@types/qs": "^6.9.4",
44+
"@types/semver": "^7.3.6",
4445
"@types/wrap-ansi": "^3.0.0",
4546
"body-parser": "^1.18.3",
4647
"boxen": "^1.3.0",
@@ -72,6 +73,7 @@
7273
"ow": "^0.19.0",
7374
"pkg-install": "^1.0.0",
7475
"rimraf": "^3.0.2",
76+
"semver": "^7.3.5",
7577
"serialize-error": "^7.0.1",
7678
"terminal-link": "^1.3.0",
7779
"title": "^3.4.1",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { stripIndent } from 'common-tags';
2+
import { coerce as semverCoerce, parse as semverParse } from 'semver';
3+
import { PackageJson } from 'type-fest';
4+
import { logger } from '../utils/logger';
5+
6+
export function isExactSemVerVersion(version?: string): boolean {
7+
return semverParse(version) !== null;
8+
}
9+
10+
export function checkForValidRuntimeHandlerVersion(
11+
packageJson: PackageJson,
12+
shouldFailIfMissing = false
13+
): boolean {
14+
const runtimeHandlerVersion =
15+
packageJson.dependencies?.['@twilio/runtime-handler'];
16+
17+
let title: string | undefined;
18+
let message: string;
19+
20+
if (typeof runtimeHandlerVersion !== 'string') {
21+
if (packageJson.devDependencies?.['@twilio/runtime-handler']) {
22+
title = 'Wrongly configured @twilio/runtime-handler declaration';
23+
24+
message = stripIndent`
25+
You defined the @twilio/runtime-handler dependency inside the "devDependencies" field of your package.json.
26+
27+
The @twilio/runtime-handler has to be defined in the "dependencies" field instead to be passed to Twilio Functions.
28+
`;
29+
} else if (shouldFailIfMissing) {
30+
title = 'Missing @twilio/runtime-handler declaration';
31+
message = stripIndent`
32+
We could not find a specific @twilio/runtime-handler version in your package.json "dependencies" field.
33+
34+
The @twilio/runtime-handler defines which features inside your Twilio Functions environment should be available.
35+
36+
Please add one manually or run the following inside your project to install the latest version of the @twilio/runtime-handler:
37+
38+
npm install @twilio/runtime-handler --save-exact
39+
`;
40+
} else {
41+
return true;
42+
}
43+
} else if (isExactSemVerVersion(runtimeHandlerVersion)) {
44+
return true;
45+
} else {
46+
title = 'Invalid @twilio/runtime-handler version';
47+
message = stripIndent`
48+
The @twilio/runtime-handler version has to be an exact valid semver version.
49+
Please update the "dependencies" field in your package.json accordingly.
50+
51+
Received:
52+
"@twilio/runtime-handler": "${runtimeHandlerVersion}",
53+
Expected Format:
54+
"@twilio/runtime-handler": "${
55+
semverCoerce(runtimeHandlerVersion) || '1.1.0'
56+
}",
57+
`;
58+
}
59+
60+
if (message) {
61+
logger.error(message, title);
62+
}
63+
64+
return false;
65+
}

packages/twilio-run/src/commands/deploy.ts

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Ora } from 'ora';
44
import path from 'path';
55
import { Argv } from 'yargs';
66
import { checkConfigForCredentials } from '../checks/check-credentials';
7+
import { checkForValidRuntimeHandlerVersion } from '../checks/check-runtime-handler';
78
import checkLegacyConfig from '../checks/legacy-config';
89
import checkProjectStructure from '../checks/project-structure';
910
import {
@@ -105,6 +106,11 @@ export async function handler(
105106
return;
106107
}
107108

109+
if (!checkForValidRuntimeHandlerVersion(config.pkgJson)) {
110+
process.exit(1);
111+
return;
112+
}
113+
108114
debug('Deploy Config %P', config);
109115

110116
checkConfigForCredentials(config);

0 commit comments

Comments
 (0)