[now-build-utils] Validate functions and allow them as config (#3218)

* [now-build-utils] Validate functions and allow them as config

* Apply suggestions from code review

Co-Authored-By: Steven <steven@ceriously.com>

* Change memory check

* Adjust test
This commit is contained in:
Andy
2019-10-29 21:03:04 +01:00
committed by Leo Lamprecht
parent 51146f5baf
commit ca8f347e3a
3 changed files with 123 additions and 18 deletions

View File

@@ -1,4 +1,5 @@
import minimatch from 'minimatch';
import { valid as validSemver } from 'semver';
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
interface ErrorResponse {
@@ -69,20 +70,14 @@ function getFunctionBuilder(
const src = (prevBuilder && prevBuilder.src) || file;
const use = fn.runtime || (prevBuilder && prevBuilder.use);
const config: Config = Object.assign({}, prevBuilder && prevBuilder.config);
const config: Config = Object.assign({}, prevBuilder && prevBuilder.config, {
functions,
});
if (!use) {
return prevBuilder;
}
if (fn.memory) {
config.memory = fn.memory;
}
if (fn.maxDuration) {
config.maxDuration = fn.maxDuration;
}
return { use, src, config };
}
@@ -175,6 +170,54 @@ async function checkConflictingFiles(
return null;
}
function validateFunctions({ functions = {} }: Options) {
for (const [path, func] of Object.entries(functions)) {
if (path.length > 256) {
return {
code: 'invalid_function_glob',
message: 'Function globs must be less than 256 characters long.',
};
}
if (
func.maxDuration !== undefined &&
(func.maxDuration < 1 ||
func.maxDuration > 900 ||
!Number.isInteger(func.maxDuration))
) {
return {
code: 'invalid_function_duration',
message: 'Functions must have a duration between 1 and 900.',
};
}
if (
func.memory !== undefined &&
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
) {
return {
code: 'invalid_function_memory',
message:
'Functions must have a memory value between 128 and 3008 in steps of 64.',
};
}
if (func.runtime !== undefined) {
const tag = `${func.runtime}`.split('@').pop();
if (!tag || !validSemver(tag)) {
return {
code: 'invalid_function_runtime',
message:
'Function runtimes must have a valid version, for example `@now/node@1.0.0`.',
};
}
}
}
return null;
}
// When zero config is used we can call this function
// to determine what builders to use
export async function detectBuilders(
@@ -189,6 +232,16 @@ export async function detectBuilders(
const errors: ErrorResponse[] = [];
const warnings: ErrorResponse[] = [];
const functionError = validateFunctions(options);
if (functionError) {
return {
builders: null,
errors: [functionError],
warnings,
};
}
// Detect all builders for the `api` directory before anything else
const builders = await detectApiBuilders(files, options);

View File

@@ -39,6 +39,7 @@ export interface Config {
| boolean
| number
| { [key: string]: string }
| BuilderFunctions
| undefined;
maxLambdaSize?: string;
includeFiles?: string | string[];
@@ -49,8 +50,7 @@ export interface Config {
debug?: boolean;
zeroConfig?: boolean;
import?: { [key: string]: string };
memory?: number;
maxDuration?: number;
functions?: BuilderFunctions;
}
export interface Meta {

View File

@@ -453,7 +453,7 @@ it('Test `detectBuilders`', async () => {
};
const functions = {
'api/users/*.ts': {
runtime: 'my-custom-runtime-package',
runtime: 'my-custom-runtime-package@1.0.0',
},
'api/teams/members.ts': {
memory: 128,
@@ -461,7 +461,7 @@ it('Test `detectBuilders`', async () => {
},
'package.json': {
memory: 3008,
runtime: '@now/next@canary',
runtime: '@now/next@1.0.0-canary.12',
},
};
const files = [
@@ -475,19 +475,71 @@ it('Test `detectBuilders`', async () => {
expect(builders[0]).toEqual({
src: 'api/teams/members.ts',
use: '@now/node',
config: { zeroConfig: true, memory: 128, maxDuration: 10 },
config: { zeroConfig: true, functions },
});
expect(builders[1]).toEqual({
src: 'api/users/[id].ts',
use: 'my-custom-runtime-package',
config: { zeroConfig: true },
use: 'my-custom-runtime-package@1.0.0',
config: { zeroConfig: true, functions },
});
expect(builders[2]).toEqual({
src: 'package.json',
use: '@now/next@canary',
config: { zeroConfig: true, memory: 3008 },
use: '@now/next@1.0.0-canary.12',
config: { zeroConfig: true, functions },
});
}
{
// invalid function key
const functions = { ['a'.repeat(1000)]: { memory: 128 } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
});
expect(builders).toBe(null);
expect(errors.length).toBe(1);
expect(errors[0].code).toBe('invalid_function_glob');
}
{
// invalid function maxDuration
const functions = { 'pages/index.ts': { maxDuration: -1 } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
});
expect(builders).toBe(null);
expect(errors.length).toBe(1);
expect(errors[0].code).toBe('invalid_function_duration');
}
{
// invalid function memory
const functions = { 'pages/index.ts': { memory: 200 } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
});
expect(builders).toBe(null);
expect(errors.length).toBe(1);
expect(errors[0].code).toBe('invalid_function_memory');
}
{
// missing runtime version
const functions = { 'pages/index.ts': { runtime: 'haha' } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
});
expect(builders).toBe(null);
expect(errors.length).toBe(1);
expect(errors[0].code).toBe('invalid_function_runtime');
}
});
it('Test `detectRoutes`', async () => {