Compare commits

..

7 Commits

Author SHA1 Message Date
Nathan Rajlich
70a53515bd Publish Stable
- vercel@28.16.2
 - @vercel/fs-detectors@3.8.0
 - @vercel/next@3.5.0
 - @vercel/remix@1.3.4
 - @vercel/ruby@1.3.66
 - @vercel/static-build@1.3.10
2023-02-16 15:12:31 -08:00
Sean Massa
4d4f0fa672 [next] Add Operation Types to Next.js Lambdas (#9196)
In order to have Next.js Lambdas show their operation types more specifically in the build output in the dashboard, the builder needs to return the Lambdas with `operationType` set to the appropriate value.

This PR adds those values. This allows the Richer Deployment Outputs to show the different types of serverless functions:
<img width="228" alt="Screenshot 2023-02-03 at 3 49 42 PM" src="https://user-images.githubusercontent.com/41545/216717479-d02fbd4a-fa62-479d-8b65-bd77fdcdb26c.png">
2023-02-16 22:59:14 +00:00
Felix Haus
46c0fd153a [fs-detectors] Remove increments of 64 limit for function memory (#9465)
Missed this occurrence so it still prevents the upload of serverless
functions that have a mem value that is not dividable by 64.
Should be the last place before we can ship the documentation update.

#### Related PRs
- #9440

Co-authored-by: Steven <steven@ceriously.com>
2023-02-16 16:42:42 -05:00
Marc Greenstock
1c8b4717e3 [ruby] fix: HEAD requests (#9436)
When WEBrick receives `HEAD` requests it discards the body (i.e.
`req.body.nil? => true`), this causes Vercel to throw a
`BODY_NOT_A_STRING_FROM_FUNCTION` since it is expecting the serverless
function to respond with a string in the body.

---------

Co-authored-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Steven <steven@ceriously.com>
2023-02-16 16:41:56 -05:00
Nathan Rajlich
d52d26eaac [remix] Install Node globals (#9467)
Fixes https://github.com/vercel/community/discussions/1547.
Fixes https://github.com/vercel/community/discussions/1549.
2023-02-16 21:25:19 +00:00
Vincent Voyer
db65728fc4 Publish Stable
- vercel@28.16.1
2023-02-16 15:36:16 +01:00
Vincent Voyer
a788d06f85 [cli]: fix merging of vercel.json and build result crons (#9464)
Ensures that existing crons and crons from vercel.json are merged
together correctly.
2023-02-16 15:34:56 +01:00
29 changed files with 243 additions and 34 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.16.0",
"version": "28.16.2",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -44,13 +44,13 @@
"@vercel/build-utils": "6.3.0",
"@vercel/go": "2.3.7",
"@vercel/hydrogen": "0.0.53",
"@vercel/next": "3.4.7",
"@vercel/next": "3.5.0",
"@vercel/node": "2.9.6",
"@vercel/python": "3.1.49",
"@vercel/redwood": "1.1.5",
"@vercel/remix": "1.3.3",
"@vercel/ruby": "1.3.65",
"@vercel/static-build": "1.3.9"
"@vercel/remix": "1.3.4",
"@vercel/ruby": "1.3.66",
"@vercel/static-build": "1.3.10"
},
"devDependencies": {
"@alex_neo/jest-expect-message": "1.0.5",
@@ -96,7 +96,7 @@
"@vercel/client": "12.4.0",
"@vercel/error-utils": "1.0.8",
"@vercel/frameworks": "1.3.1",
"@vercel/fs-detectors": "3.7.14",
"@vercel/fs-detectors": "3.8.0",
"@vercel/fun": "1.0.4",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.1.9",

View File

@@ -751,12 +751,12 @@ function mergeImages(
}
function mergeCrons(
crons: BuildOutputConfig['crons'],
crons: BuildOutputConfig['crons'] = [],
buildResults: Iterable<BuildResult | BuildOutputConfig>
): BuildOutputConfig['crons'] {
for (const result of buildResults) {
if ('crons' in result && result.crons) {
crons = Object.assign({}, crons, result.crons);
crons = crons.concat(result.crons);
}
}
return crons;

View File

@@ -0,0 +1,7 @@
{
"orgId": ".",
"projectId": ".",
"settings": {
"framework": null
}
}

View File

@@ -0,0 +1,9 @@
const fs = require('fs');
const path = require('path');
fs.rmSync(path.join(__dirname, '.vercel', 'output'), { recursive: true });
fs.mkdirSync(path.join(__dirname, '.vercel', 'output'));
fs.copyFileSync(
path.join(__dirname, 'output', 'config.json'),
path.join(__dirname, '.vercel', 'output', 'config.json')
);

View File

@@ -0,0 +1 @@
<h1>Vercel</h1>

View File

@@ -0,0 +1,9 @@
{
"version": 3,
"crons": [
{
"path": "/api/cron-job-build-output",
"schedule": "0 0 * * *"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"scripts": {
"build": "node build"
}
}

View File

@@ -0,0 +1,8 @@
{
"crons": [
{
"path": "/api/cron-job",
"schedule": "0 0 * * *"
}
]
}

View File

@@ -0,0 +1,3 @@
export default function (req, res) {
res.send('Hello from cron job!');
}

View File

@@ -1124,6 +1124,32 @@ describe('build', () => {
}
});
it('should merge crons property from build output with vercel.json crons property', async () => {
const cwd = fixture('with-cron-merge');
const output = join(cwd, '.vercel', 'output');
try {
process.chdir(cwd);
const exitCode = await build(client);
expect(exitCode).toBe(0);
const config = await fs.readJSON(join(output, 'config.json'));
expect(config).toHaveProperty('crons', [
{
path: '/api/cron-job',
schedule: '0 0 * * *',
},
{
path: '/api/cron-job-build-output',
schedule: '0 0 * * *',
},
]);
} finally {
process.chdir(originalCwd);
delete process.env.__VERCEL_BUILD_RUNNING;
}
});
describe('should find packages with different main/module/browser keys', function () {
let output: string;

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/fs-detectors",
"version": "3.7.14",
"version": "3.8.0",
"description": "Vercel filesystem detectors",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -602,12 +602,11 @@ function validateFunctions({ functions = {} }: Options) {
if (
func.memory !== undefined &&
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
(func.memory < 128 || func.memory > 3008)
) {
return {
code: 'invalid_function_memory',
message:
'Functions must have a memory value between 128 and 3008 in steps of 64.',
message: 'Functions must have a memory value between 128 and 3008',
};
}

View File

@@ -473,7 +473,7 @@ describe('Test `detectBuilders`', () => {
});
it('invalid function memory', async () => {
const functions = { 'pages/index.ts': { memory: 200 } };
const functions = { 'pages/index.ts': { memory: 127 } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
@@ -484,6 +484,17 @@ describe('Test `detectBuilders`', () => {
expect(errors![0].code).toBe('invalid_function_memory');
});
it('should build with function memory not dividable by 64', async () => {
const functions = { 'api/index.ts': { memory: 1000 } };
const files = ['api/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
});
expect(builders![0].use).toBe('@vercel/node');
expect(errors).toBeNull();
});
it('missing runtime version', async () => {
const functions = { 'pages/index.ts': { runtime: 'haha' } };
const files = ['pages/index.ts'];
@@ -1720,7 +1731,7 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
});
it('invalid function memory', async () => {
const functions = { 'pages/index.ts': { memory: 200 } };
const functions = { 'pages/index.ts': { memory: 127 } };
const files = ['pages/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
@@ -1732,6 +1743,18 @@ describe('Test `detectBuilders` with `featHandleMiss=true`', () => {
expect(errors![0].code).toBe('invalid_function_memory');
});
it('should build with function memory not dividable by 64', async () => {
const functions = { 'api/index.ts': { memory: 1000 } };
const files = ['api/index.ts'];
const { builders, errors } = await detectBuilders(files, null, {
functions,
featHandleMiss,
});
expect(builders![0].use).toBe('@vercel/node');
expect(errors).toBeNull();
});
it('missing runtime version', async () => {
const functions = { 'pages/index.ts': { runtime: 'haha' } };
const files = ['pages/index.ts'];

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.4.7",
"version": "3.5.0",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",

View File

@@ -88,6 +88,8 @@ import {
PseudoLayerResult,
updateRouteSrc,
validateEntrypoint,
getOperationType,
isApiPage,
} from './utils';
export const version = 2;
@@ -1090,7 +1092,7 @@ export const build: BuildV2 = async ({
handler: '___next_launcher.cjs',
runtime: nodeVersion.runtime,
...lambdaOptions,
operationType: 'SSR',
operationType: 'SSR', // always SSR because we're in legacy mode
shouldAddHelpers: false,
shouldAddSourcemapSupport: false,
supportsMultiPayloads: !!process.env.NEXT_PRIVATE_MULTI_PAYLOAD,
@@ -1126,10 +1128,6 @@ export const build: BuildV2 = async ({
outputDirectory,
appPathRoutesManifest,
});
const isApiPage = (page: string) =>
page
.replace(/\\/g, '/')
.match(/(serverless|server)\/pages\/api(\/|\.js$)/);
const canUsePreviewMode = Object.keys(pages).some(page =>
isApiPage(pages[page].fsPath)
@@ -1598,6 +1596,10 @@ export const build: BuildV2 = async ({
internalPages: [],
});
for (const group of initialApiLambdaGroups) {
group.isApiLambda = true;
}
debug(
JSON.stringify(
{
@@ -1819,6 +1821,10 @@ export const build: BuildV2 = async ({
path.relative(baseDir, entryPath),
'___next_launcher.cjs'
),
operationType: getOperationType({
prerenderManifest,
pageFileName,
}),
runtime: nodeVersion.runtime,
nextVersion,
...lambdaOptions,
@@ -1839,6 +1845,7 @@ export const build: BuildV2 = async ({
path.relative(baseDir, entryPath),
'___next_launcher.cjs'
),
operationType: getOperationType({ pageFileName }), // can only be API or SSR
runtime: nodeVersion.runtime,
nextVersion,
...lambdaOptions,
@@ -2040,6 +2047,11 @@ export const build: BuildV2 = async ({
pageLambdaMap[page] = group.lambdaIdentifier;
}
const operationType = getOperationType({
group,
prerenderManifest,
});
lambdas[group.lambdaIdentifier] =
await createLambdaFromPseudoLayers({
files: {
@@ -2051,6 +2063,7 @@ export const build: BuildV2 = async ({
path.relative(baseDir, entryPath),
'___next_launcher.cjs'
),
operationType,
runtime: nodeVersion.runtime,
nextVersion,
});

View File

@@ -43,6 +43,7 @@ import {
getMiddlewareBundle,
getFilesMapFromReasons,
UnwrapPromise,
getOperationType,
} from './utils';
import {
nodeFileTrace,
@@ -748,6 +749,10 @@ export async function serverBuild({
internalPages,
});
for (const group of apiLambdaGroups) {
group.isApiLambda = true;
}
debug(
JSON.stringify(
{
@@ -856,6 +861,8 @@ export async function serverBuild({
}
}
const operationType = getOperationType({ group, prerenderManifest });
const lambda = await createLambdaFromPseudoLayers({
files: {
...launcherFiles,
@@ -869,6 +876,7 @@ export async function serverBuild({
),
'___next_launcher.cjs'
),
operationType,
memory: group.memory,
runtime: nodeVersion.runtime,
maxDuration: group.maxDuration,

View File

@@ -1313,6 +1313,7 @@ export type LambdaGroup = {
maxDuration?: number;
isStreaming?: boolean;
isPrerenders?: boolean;
isApiLambda: boolean;
pseudoLayer: PseudoLayer;
pseudoLayerBytes: number;
pseudoLayerUncompressedBytes: number;
@@ -1419,6 +1420,7 @@ export async function getPageLambdaGroups({
pages: [page],
...opts,
isPrerenders: isPrerenderRoute,
isApiLambda: !!isApiPage(page),
pseudoLayerBytes: initialPseudoLayer.pseudoLayerBytes,
pseudoLayerUncompressedBytes: initialPseudoLayerUncompressed,
pseudoLayer: Object.assign({}, initialPseudoLayer.pseudoLayer),
@@ -2677,3 +2679,49 @@ function transformSourceMap(
return { ...sourcemap, sources };
}
interface LambdaGroupTypeInterface {
isApiLambda: boolean;
isPrerenders?: boolean;
}
export function getOperationType({
group,
prerenderManifest,
pageFileName,
}: {
group?: LambdaGroupTypeInterface;
prerenderManifest?: NextPrerenderedRoutes;
pageFileName?: string;
}) {
if (group?.isApiLambda || isApiPage(pageFileName)) {
return 'API';
}
if (group?.isPrerenders) {
return 'ISR';
}
if (pageFileName && prerenderManifest) {
const { blockingFallbackRoutes = {}, fallbackRoutes = {} } =
prerenderManifest;
if (
pageFileName in blockingFallbackRoutes ||
pageFileName in fallbackRoutes
) {
return 'ISR';
}
}
return 'SSR';
}
export function isApiPage(page: string | undefined) {
if (!page) {
return false;
}
return page
.replace(/\\/g, '/')
.match(/(serverless|server)\/pages\/api(\/|\.js$)/);
}

View File

@@ -134,41 +134,70 @@ it('should build using server build', async () => {
expect(output['index'].allowQuery).toBe(undefined);
expect(output['index'].memory).toBe(512);
expect(output['index'].maxDuration).toBe(5);
expect(output['index'].operationType).toBe('SSR');
expect(output['another'].type).toBe('Lambda');
expect(output['another'].memory).toBe(512);
expect(output['another'].maxDuration).toBe(5);
expect(output['another'].allowQuery).toBe(undefined);
expect(output['another'].operationType).toBe('SSR');
expect(output['dynamic/[slug]'].type).toBe('Lambda');
expect(output['dynamic/[slug]'].memory).toBe(undefined);
expect(output['dynamic/[slug]'].maxDuration).toBe(5);
expect(output['dynamic/[slug]'].operationType).toBe('SSR');
expect(output['fallback/[slug]'].type).toBe('Prerender');
expect(output['fallback/[slug]'].allowQuery).toEqual(['slug']);
expect(output['fallback/[slug]'].lambda.operationType).toBe('ISR');
expect(output['_next/data/testing-build-id/fallback/[slug].json'].type).toBe(
'Prerender'
);
expect(
output['_next/data/testing-build-id/fallback/[slug].json'].allowQuery
).toEqual(['slug']);
expect(
output['_next/data/testing-build-id/fallback/[slug].json'].lambda
.operationType
).toBe('ISR');
expect(output['fallback/first'].type).toBe('Prerender');
expect(output['fallback/first'].allowQuery).toEqual([]);
expect(output['fallback/first'].lambda.operationType).toBe('ISR');
expect(output['_next/data/testing-build-id/fallback/first.json'].type).toBe(
'Prerender'
);
expect(
output['_next/data/testing-build-id/fallback/first.json'].allowQuery
).toEqual([]);
expect(
output['_next/data/testing-build-id/fallback/first.json'].lambda
.operationType
).toBe('ISR');
expect(output['api'].type).toBe('Lambda');
expect(output['api'].allowQuery).toBe(undefined);
expect(output['api'].memory).toBe(128);
expect(output['api'].maxDuration).toBe(5);
expect(output['api'].operationType).toBe('API');
expect(output['api/another'].type).toBe('Lambda');
expect(output['api/another'].allowQuery).toBe(undefined);
expect(output['api/another'].operationType).toBe('API');
expect(output['api/blog/[slug]'].type).toBe('Lambda');
expect(output['api/blog/[slug]'].allowQuery).toBe(undefined);
expect(output['api/blog/[slug]'].operationType).toBe('API');
expect(output['static'].type).toBe('FileFsRef');
expect(output['static'].allowQuery).toBe(undefined);
expect(output['static'].operationType).toBe(undefined);
expect(output['ssg'].type).toBe('Prerender');
expect(output['ssg'].allowQuery).toEqual([]);
expect(output['ssg'].lambda.operationType).toBe('ISR');
expect(output['index'] === output['another']).toBe(true);
expect(output['dynamic/[slug]'] !== output['fallback/[slug]'].lambda).toBe(

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/remix",
"version": "1.3.3",
"version": "1.3.4",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",

View File

@@ -4,7 +4,11 @@ import {
Headers as NodeHeaders,
Request as NodeRequest,
writeReadableStreamToWritable,
installGlobals,
} from '@remix-run/node';
installGlobals();
import build from './index.js';
const handleRequest = createRemixRequestHandler(build, process.env.NODE_ENV);

View File

@@ -0,0 +1,13 @@
import { json } from '@remix-run/server-runtime';
import { useLoaderData } from '@remix-run/react';
import type { LoaderArgs } from '@remix-run/server-runtime';
export const loader = ({ request }: LoaderArgs) => {
const instanceOfRequest = request instanceof Request;
return json({ instanceOfRequest });
};
export default function InstanceOf() {
const data = useLoaderData<typeof loader>();
return <div>{`InstanceOfRequest: ${data.instanceOfRequest}`}</div>;
}

View File

@@ -16,6 +16,7 @@
{ "path": "/nested", "mustContain": "Nested index page" },
{ "path": "/nested/another", "mustContain": "Nested another page" },
{ "path": "/nested/index", "mustContain": "Not Found" },
{ "path": "/asdf", "mustContain": "Not Found" }
{ "path": "/asdf", "mustContain": "Not Found" },
{ "path": "/instanceof", "mustContain": "InstanceOfRequest: true" }
]
}

View File

@@ -1,7 +1,7 @@
{
"name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.65",
"version": "1.3.66",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",

View File

@@ -1,5 +1,8 @@
{
"version": 2,
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }],
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
"probes": [
{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" },
{ "path": "/", "method": "HEAD", "status": 200 }
]
}

View File

@@ -73,7 +73,7 @@ def webrick_handler(httpMethod, path, body, headers)
{
:statusCode => res.code.to_i,
:headers => res_headers,
:body => res.body,
:body => res.body.nil? ? "" : res.body,
}
end

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/static-build",
"version": "1.3.9",
"version": "1.3.10",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/build-step",
@@ -44,7 +44,7 @@
"@types/semver": "7.3.13",
"@vercel/build-utils": "6.3.0",
"@vercel/frameworks": "1.3.1",
"@vercel/fs-detectors": "3.7.14",
"@vercel/fs-detectors": "3.8.0",
"@vercel/ncc": "0.24.0",
"@vercel/routing-utils": "2.1.9",
"@vercel/static-config": "2.0.13",

12
pnpm-lock.yaml generated
View File

@@ -208,19 +208,19 @@ importers:
'@vercel/client': 12.4.0
'@vercel/error-utils': 1.0.8
'@vercel/frameworks': 1.3.1
'@vercel/fs-detectors': 3.7.14
'@vercel/fs-detectors': 3.8.0
'@vercel/fun': 1.0.4
'@vercel/go': 2.3.7
'@vercel/hydrogen': 0.0.53
'@vercel/ncc': 0.24.0
'@vercel/next': 3.4.7
'@vercel/next': 3.5.0
'@vercel/node': 2.9.6
'@vercel/python': 3.1.49
'@vercel/redwood': 1.1.5
'@vercel/remix': 1.3.3
'@vercel/remix': 1.3.4
'@vercel/routing-utils': 2.1.9
'@vercel/ruby': 1.3.65
'@vercel/static-build': 1.3.9
'@vercel/ruby': 1.3.66
'@vercel/static-build': 1.3.10
'@zeit/source-map-support': 0.6.2
ajv: 6.12.2
alpha-sort: 2.0.1
@@ -989,7 +989,7 @@ importers:
'@types/semver': 7.3.13
'@vercel/build-utils': 6.3.0
'@vercel/frameworks': 1.3.1
'@vercel/fs-detectors': 3.7.14
'@vercel/fs-detectors': 3.8.0
'@vercel/gatsby-plugin-vercel-analytics': 1.0.7
'@vercel/gatsby-plugin-vercel-builder': 1.1.7
'@vercel/ncc': 0.24.0

View File

@@ -384,7 +384,7 @@ async function fetchDeploymentUrl(url, opts) {
for (let i = 0; i < 50; i += 1) {
const resp = await fetch(url, opts);
const text = await resp.text();
if (text && !text.includes('Join Free')) {
if (typeof text !== 'undefined' && !text.includes('Join Free')) {
return { resp, text };
}