Skip to content

Commit

Permalink
[build-utils][frameworks] Add support for runtime properties (#5034)
Browse files Browse the repository at this point in the history
This PR adds two properties to `frameworks.json`:

1. `useRuntime` - this moves the special case for non `@vercel/static-build` frontends, so that any framework can do the same as Next.js and RedwoodJS
2. `ignoreRuntimes` - this allows a framework to opt out of api detection such as RedwoodJS which handle's its own `.js` extensions

This also fixes 2 bugs discovered during implementing the feature:

1. `test-unit.yml` was not testing Node 12, it was testing 10 for both runs
2. `sortFilesBySegmentCount()` was non-deterministic causing node 10 and 12 to sort differently
  • Loading branch information
styfle committed Aug 13, 2020
1 parent f1bb0e4 commit 1f4f2af
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
- run: git fetch origin ${{ github.ref }} --depth=100
- run: git diff origin/master...HEAD --name-only
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn run build
- run: yarn run lint
Expand Down
4 changes: 4 additions & 0 deletions packages/frameworks/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"tagline": "Blitz.js: The Fullstack React Framework",
"description": "A brand new Blitz.js app - the result of running `npx blitz new`.",
"website": "https://blitzjs.com",
"useRuntime": { "src": "package.json", "use": "@vercel/next" },
"detectors": {
"every": [
{
Expand Down Expand Up @@ -36,6 +37,7 @@
"description": "A Next.js app and a Serverless Function API.",
"website": "https://nextjs.org",
"sort": 1,
"useRuntime": { "src": "package.json", "use": "@vercel/next" },
"detectors": {
"every": [
{
Expand Down Expand Up @@ -690,6 +692,8 @@
"tagline": "RedwoodJS is a full-stack framework for the Jamstack.",
"description": "A RedwoodJS app, bootstraped with create-redwood-app.",
"website": "https://redwoodjs.com",
"useRuntime": { "src": "package.json", "use": "@vercel/redwood" },
"ignoreRuntimes": ["@vercel/node"],
"detectors": {
"every": [
{
Expand Down
2 changes: 2 additions & 0 deletions packages/frameworks/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface Framework {
website?: string;
description: string;
sort?: number;
useRuntime?: { src: string; use: string };
ignoreRuntimes?: string[];
detectors?: {
every?: FrameworkDetectionItem[];
some?: FrameworkDetectionItem[];
Expand Down
15 changes: 15 additions & 0 deletions packages/frameworks/test/frameworks.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ const Schema = {
tagline: { type: 'string' },
website: { type: 'string' },
description: { type: 'string' },
useRuntime: {
type: 'object',
required: ['src', 'use'],
additionalProperties: false,
properties: {
src: { type: 'string' },
use: { type: 'string' },
},
},
ignoreRuntimes: {
type: 'array',
items: {
type: 'string',
},
},
detectors: {
type: 'object',
additionalProperties: false,
Expand Down
1 change: 1 addition & 0 deletions packages/now-build-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.0.18-canary.4",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",
"async-sema": "2.1.4",
Expand Down
48 changes: 26 additions & 22 deletions packages/now-build-utils/src/detect-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import minimatch from 'minimatch';
import { valid as validSemver } from 'semver';
import { parse as parsePath, extname } from 'path';
import { Route, Source } from '@vercel/routing-utils';
import _frameworks, { Framework } from '@vercel/frameworks';
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
import { isOfficialRuntime } from './';
const frameworkList = _frameworks as Framework[];
const slugToFramework = new Map<string | null, Framework>(
frameworkList.map(f => [f.slug, f])
);

interface ErrorResponse {
code: string;
Expand Down Expand Up @@ -106,7 +111,6 @@ export async function detectBuilders(
};
}

const apiMatches = getApiMatches(options);
const sortedFiles = files.sort(sortFiles);
const apiSortedFiles = files.sort(sortFilesBySegmentCount);

Expand All @@ -122,6 +126,16 @@ export async function detectBuilders(

const { projectSettings = {} } = options;
const { buildCommand, outputDirectory, framework } = projectSettings;
const ignoreRuntimes = new Set(
slugToFramework.get(framework || '')?.ignoreRuntimes
);
const withTag = options.tag ? `@${options.tag}` : '';
const apiMatches = getApiMatches()
.filter(b => !ignoreRuntimes.has(b.use))
.map(b => {
b.use = `${b.use}${withTag}`;
return b;
});

// If either is missing we'll make the frontend static
const makeFrontendStatic = buildCommand === '' || outputDirectory === '';
Expand Down Expand Up @@ -309,13 +323,6 @@ export async function detectBuilders(
options
);

if (frontendBuilder && framework === 'redwoodjs') {
// RedwoodJS uses the /api directory differently so we must
// clear any existing builders and only use `@vercel/redwood`.
builders.length = 0;
builders.push(frontendBuilder);
}

return {
warnings,
builders: builders.length ? builders : null,
Expand Down Expand Up @@ -401,16 +408,15 @@ function getFunction(fileName: string, { functions = {} }: Options) {
: { fnPattern: null, func: null };
}

function getApiMatches({ tag }: Options = {}) {
const withTag = tag ? `@${tag}` : '';
function getApiMatches() {
const config = { zeroConfig: true };

return [
{ src: 'api/**/*.js', use: `@vercel/node${withTag}`, config },
{ src: 'api/**/*.ts', use: `@vercel/node${withTag}`, config },
{ src: 'api/**/!(*_test).go', use: `@vercel/go${withTag}`, config },
{ src: 'api/**/*.py', use: `@vercel/python${withTag}`, config },
{ src: 'api/**/*.rb', use: `@vercel/ruby${withTag}`, config },
{ src: 'api/**/*.js', use: `@vercel/node`, config },
{ src: 'api/**/*.ts', use: `@vercel/node`, config },
{ src: 'api/**/!(*_test).go', use: `@vercel/go`, config },
{ src: 'api/**/*.py', use: `@vercel/python`, config },
{ src: 'api/**/*.rb', use: `@vercel/ruby`, config },
];
}

Expand Down Expand Up @@ -471,12 +477,10 @@ function detectFrontBuilder(
});
}

if (framework === 'nextjs' || framework === 'blitzjs') {
return { src: 'package.json', use: `@vercel/next${withTag}`, config };
}

if (framework === 'redwoodjs') {
return { src: 'package.json', use: `@vercel/redwood${withTag}`, config };
const f = slugToFramework.get(framework || '');
if (f && f.useRuntime) {
const { src, use } = f.useRuntime;
return { src, use: `${use}${withTag}`, config };
}

// Entrypoints for other frameworks
Expand Down Expand Up @@ -1041,5 +1045,5 @@ function sortFilesBySegmentCount(fileA: string, fileB: string): number {
return -1;
}

return 0;
return fileA.localeCompare(fileB);
}
108 changes: 97 additions & 11 deletions packages/now-build-utils/test/unit.builds-and-routes-detector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1853,23 +1853,38 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect((errorRoutes![0] as Source).status).toBe(404);
});

it('RedwoodJS should only use redwood builder', async () => {
const files = [
'package.json',
'web/index.html',
'api/one.js',
'api/two.js',
];
const redwoodFiles = [
'package.json',
'web/package.json',
'web/public/robots.txt',
'web/src/index.html',
'web/src/index.css',
'web/src/index.js',
'api/package.json',
'api/prisma/seeds.js',
'api/src/functions/graphql.js',
'api/src/graphql/.keep',
'api/src/services/.keep',
'api/src/lib/db.js',
];

it('RedwoodJS should only use Redwood builder and not Node builder', async () => {
const files = [...redwoodFiles].sort();
const projectSettings = {
framework: 'redwoodjs',
};

const { builders, errorRoutes } = await detectBuilders(files, null, {
const {
builders,
defaultRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});

expect(builders).toEqual([
expect(builders).toStrictEqual([
{
use: '@vercel/redwood',
src: 'package.json',
Expand All @@ -1879,8 +1894,79 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
},
},
]);
expect(errorRoutes!.length).toBe(1);
expect((errorRoutes![0] as Source).status).toBe(404);
expect(defaultRoutes).toStrictEqual([]);
expect(rewriteRoutes).toStrictEqual([]);
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
dest: '/404.html',
},
]);
});

it('RedwoodJS should allow usage of non-js API', async () => {
const files = [...redwoodFiles, 'api/golang.go', 'api/python.py'].sort();
const projectSettings = {
framework: 'redwoodjs',
};

const {
builders,
defaultRoutes,
rewriteRoutes,
errorRoutes,
} = await detectBuilders(files, null, {
projectSettings,
featHandleMiss,
});

expect(builders).toStrictEqual([
{
use: '@vercel/go',
src: 'api/golang.go',
config: {
zeroConfig: true,
},
},
{
use: '@vercel/python',
src: 'api/python.py',
config: {
zeroConfig: true,
},
},
{
use: '@vercel/redwood',
src: 'package.json',
config: {
zeroConfig: true,
framework: 'redwoodjs',
},
},
]);
expect(defaultRoutes).toStrictEqual([
{ handle: 'miss' },
{
src: '^/api/(.+)(?:\\.(?:go|py))$',
dest: '/api/$1',
check: true,
},
]);
expect(rewriteRoutes).toStrictEqual([
{
status: 404,
src: '^/api(/.*)?$',
continue: true,
},
]);
expect(errorRoutes).toStrictEqual([
{
status: 404,
src: '^/(?!.*api).*$',
dest: '/404.html',
},
]);
});

it('No framework, only package.json', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/now-build-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"outDir": "./dist",
"types": ["node", "jest"],
"strict": true,
"target": "esnext"
"target": "es2019"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
Expand Down

0 comments on commit 1f4f2af

Please sign in to comment.