mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 04:22:12 +00:00
[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:
@@ -1,4 +1,5 @@
|
|||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
|
import { valid as validSemver } from 'semver';
|
||||||
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||||
|
|
||||||
interface ErrorResponse {
|
interface ErrorResponse {
|
||||||
@@ -69,20 +70,14 @@ function getFunctionBuilder(
|
|||||||
|
|
||||||
const src = (prevBuilder && prevBuilder.src) || file;
|
const src = (prevBuilder && prevBuilder.src) || file;
|
||||||
const use = fn.runtime || (prevBuilder && prevBuilder.use);
|
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) {
|
if (!use) {
|
||||||
return prevBuilder;
|
return prevBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fn.memory) {
|
|
||||||
config.memory = fn.memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fn.maxDuration) {
|
|
||||||
config.maxDuration = fn.maxDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { use, src, config };
|
return { use, src, config };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +170,54 @@ async function checkConflictingFiles(
|
|||||||
return null;
|
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
|
// When zero config is used we can call this function
|
||||||
// to determine what builders to use
|
// to determine what builders to use
|
||||||
export async function detectBuilders(
|
export async function detectBuilders(
|
||||||
@@ -189,6 +232,16 @@ export async function detectBuilders(
|
|||||||
const errors: ErrorResponse[] = [];
|
const errors: ErrorResponse[] = [];
|
||||||
const warnings: 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
|
// Detect all builders for the `api` directory before anything else
|
||||||
const builders = await detectApiBuilders(files, options);
|
const builders = await detectApiBuilders(files, options);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export interface Config {
|
|||||||
| boolean
|
| boolean
|
||||||
| number
|
| number
|
||||||
| { [key: string]: string }
|
| { [key: string]: string }
|
||||||
|
| BuilderFunctions
|
||||||
| undefined;
|
| undefined;
|
||||||
maxLambdaSize?: string;
|
maxLambdaSize?: string;
|
||||||
includeFiles?: string | string[];
|
includeFiles?: string | string[];
|
||||||
@@ -49,8 +50,7 @@ export interface Config {
|
|||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
zeroConfig?: boolean;
|
zeroConfig?: boolean;
|
||||||
import?: { [key: string]: string };
|
import?: { [key: string]: string };
|
||||||
memory?: number;
|
functions?: BuilderFunctions;
|
||||||
maxDuration?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
export interface Meta {
|
||||||
|
|||||||
66
packages/now-build-utils/test/unit.test.js
vendored
66
packages/now-build-utils/test/unit.test.js
vendored
@@ -453,7 +453,7 @@ it('Test `detectBuilders`', async () => {
|
|||||||
};
|
};
|
||||||
const functions = {
|
const functions = {
|
||||||
'api/users/*.ts': {
|
'api/users/*.ts': {
|
||||||
runtime: 'my-custom-runtime-package',
|
runtime: 'my-custom-runtime-package@1.0.0',
|
||||||
},
|
},
|
||||||
'api/teams/members.ts': {
|
'api/teams/members.ts': {
|
||||||
memory: 128,
|
memory: 128,
|
||||||
@@ -461,7 +461,7 @@ it('Test `detectBuilders`', async () => {
|
|||||||
},
|
},
|
||||||
'package.json': {
|
'package.json': {
|
||||||
memory: 3008,
|
memory: 3008,
|
||||||
runtime: '@now/next@canary',
|
runtime: '@now/next@1.0.0-canary.12',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const files = [
|
const files = [
|
||||||
@@ -475,19 +475,71 @@ it('Test `detectBuilders`', async () => {
|
|||||||
expect(builders[0]).toEqual({
|
expect(builders[0]).toEqual({
|
||||||
src: 'api/teams/members.ts',
|
src: 'api/teams/members.ts',
|
||||||
use: '@now/node',
|
use: '@now/node',
|
||||||
config: { zeroConfig: true, memory: 128, maxDuration: 10 },
|
config: { zeroConfig: true, functions },
|
||||||
});
|
});
|
||||||
expect(builders[1]).toEqual({
|
expect(builders[1]).toEqual({
|
||||||
src: 'api/users/[id].ts',
|
src: 'api/users/[id].ts',
|
||||||
use: 'my-custom-runtime-package',
|
use: 'my-custom-runtime-package@1.0.0',
|
||||||
config: { zeroConfig: true },
|
config: { zeroConfig: true, functions },
|
||||||
});
|
});
|
||||||
expect(builders[2]).toEqual({
|
expect(builders[2]).toEqual({
|
||||||
src: 'package.json',
|
src: 'package.json',
|
||||||
use: '@now/next@canary',
|
use: '@now/next@1.0.0-canary.12',
|
||||||
config: { zeroConfig: true, memory: 3008 },
|
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 () => {
|
it('Test `detectRoutes`', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user