From b651e6719ef3957c50d20e6490e8a114328abf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Jes=C3=BAs?= Date: Thu, 20 Apr 2023 15:16:12 +0200 Subject: [PATCH 1/3] fix: make large lambda warning message more informative --- package-lock.json | 5 ++--- packages/runtime/src/constants.ts | 6 ++++-- packages/runtime/src/helpers/verification.ts | 19 ++++++++++++------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0254a4b73..9bcf9ed0f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,8 +21,7 @@ "demos/next-with-edge-functions" ], "dependencies": { - "next": "^13.1.6", - "regexp-tree": "^0.1.24" + "next": "^13.1.6" }, "devDependencies": { "@babel/core": "^7.15.8", @@ -24339,7 +24338,7 @@ }, "packages/runtime": { "name": "@netlify/plugin-nextjs", - "version": "4.34.0", + "version": "4.35.0", "license": "MIT", "dependencies": { "@netlify/esbuild": "0.14.39", diff --git a/packages/runtime/src/constants.ts b/packages/runtime/src/constants.ts index d26c5c8e8a..7bc87c82df 100644 --- a/packages/runtime/src/constants.ts +++ b/packages/runtime/src/constants.ts @@ -27,8 +27,10 @@ export const CATCH_ALL_REGEX = /\/\[\.{3}(.*)](.json)?$/ export const OPTIONAL_CATCH_ALL_REGEX = /\/\[{2}\.{3}(.*)]{2}(.json)?$/ export const DYNAMIC_PARAMETER_REGEX = /\/\[(.*?)]/g export const MINIMUM_REVALIDATE_SECONDS = 60 -// 50MB, which is the documented max, though the hard max seems to be higher -export const LAMBDA_MAX_SIZE = 1024 * 1024 * 50 +// 50MB, which is the warning max +export const LAMBDA_WARNING_SIZE = 1024 * 1024 * 50 +// 250MB, which is the hard max +export const LAMBDA_MAX_SIZE = 1024 * 1024 * 250 export const DIVIDER = ` ──────────────────────────────────────────────────────────────── diff --git a/packages/runtime/src/helpers/verification.ts b/packages/runtime/src/helpers/verification.ts index ed1f6e1108..137302e3bb 100644 --- a/packages/runtime/src/helpers/verification.ts +++ b/packages/runtime/src/helpers/verification.ts @@ -2,13 +2,13 @@ import { existsSync, promises } from 'fs' import path, { relative, join } from 'path' import type { NetlifyConfig, NetlifyPluginUtils } from '@netlify/build' -import { yellowBright, greenBright, blueBright, redBright, reset } from 'chalk' +import { yellowBright, greenBright, blueBright, reset } from 'chalk' import { async as StreamZip } from 'node-stream-zip' import { outdent } from 'outdent' import prettyBytes from 'pretty-bytes' import { satisfies } from 'semver' -import { LAMBDA_MAX_SIZE } from '../constants' +import { LAMBDA_MAX_SIZE, LAMBDA_WARNING_SIZE } from '../constants' import { isBundleSizeCheckDisabled } from './utils' @@ -105,7 +105,11 @@ export const checkForRootPublish = ({ } } -export const checkZipSize = async (file: string, maxSize: number = LAMBDA_MAX_SIZE): Promise => { +export const checkZipSize = async ( + file: string, + maxSize: number = LAMBDA_MAX_SIZE, + warningSize: number = LAMBDA_WARNING_SIZE, +): Promise => { // Requires contacting the Netlify Support team to fully enable. // Enabling this without contacting them can result in failed deploys. if (isBundleSizeCheckDisabled()) { @@ -120,15 +124,16 @@ export const checkZipSize = async (file: string, maxSize: number = LAMBDA_MAX_SI return } const fileSize = await promises.stat(file).then(({ size }) => size) - if (fileSize < maxSize) { + if (fileSize < warningSize) { return } // We don't fail the build, because the actual hard max size is larger so it might still succeed console.log( - redBright(outdent` - The function zip ${yellowBright(relative(process.cwd(), file))} size is ${prettyBytes( + yellowBright(outdent` + The function zip ${blueBright(relative(process.cwd(), file))} size is ${prettyBytes( fileSize, - )}, which is larger than the maximum supported size of ${prettyBytes(maxSize)}. + )}, which is larger than the recommended maximum size of ${prettyBytes(warningSize)}. + This will fail the build if the unzipped size is bigger than the maximum size of ${prettyBytes(maxSize)}. There are a few reasons this could happen. You may have accidentally bundled a large dependency, or you might have a large number of pre-rendered pages included. `), From 95681a16e3afb3e1d89ac03d79260daef91384c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Jes=C3=BAs?= Date: Mon, 24 Apr 2023 12:27:12 +0200 Subject: [PATCH 2/3] chore: added unit tests for size warning --- test/helpers/verification.spec.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/helpers/verification.spec.ts b/test/helpers/verification.spec.ts index 2edcb24699..7ef8401f22 100644 --- a/test/helpers/verification.spec.ts +++ b/test/helpers/verification.spec.ts @@ -89,9 +89,11 @@ describe('checkNextSiteHasBuilt', () => { describe('checkZipSize', () => { let consoleSpy + const { existsSync, promises } = require('fs') beforeEach(() => { consoleSpy = jest.spyOn(global.console, 'warn') + process.env.DISABLE_BUNDLE_ZIP_SIZE_CHECK = 'false' }) afterEach(() => { @@ -103,6 +105,28 @@ describe('checkZipSize', () => { await checkZipSize(chance.string()) expect(consoleSpy).toHaveBeenCalledWith('Function bundle size check was DISABLED with the DISABLE_BUNDLE_ZIP_SIZE_CHECK environment variable. Your deployment will break if it exceeds the maximum supported size of function zip files in your account.') }) + + it('does not emit a warning if the file size is below the warning size', async () => { + existsSync.mockReturnValue(true) + jest.spyOn(promises, 'stat').mockResolvedValue({ size: (1024 * 1024 * 20) }) + + await checkZipSize('some-file.zip') + + expect(consoleSpy).not.toHaveBeenCalled() + }) + + it('emits a warning if the file size is above the warning size', async () => { + existsSync.mockReturnValue(true) + jest.spyOn(promises, 'stat').mockResolvedValue({ size: (1024 * 1024 * 200) }) + + await checkZipSize('some-file.zip') + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'The function zip some-file.zip size is 200 MB, which is larger than the recommended maximum size of 250 MB.' + ) + ) + }) }) describe("getProblematicUserRewrites", () => { From f7f8bd427f1de737f4467632dbb46d5bc7a61cf1 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 27 Apr 2023 13:59:11 +0200 Subject: [PATCH 3/3] test: fix verification unit tests --- test/helpers/verification.spec.ts | 59 +++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/test/helpers/verification.spec.ts b/test/helpers/verification.spec.ts index a30302c7e4..07dd33efd8 100644 --- a/test/helpers/verification.spec.ts +++ b/test/helpers/verification.spec.ts @@ -1,10 +1,19 @@ import Chance from 'chance' -import { checkNextSiteHasBuilt, checkZipSize, getProblematicUserRewrites } from '../../packages/runtime/src/helpers/verification' +import { + checkNextSiteHasBuilt, + checkZipSize, + getProblematicUserRewrites, +} from '../../packages/runtime/src/helpers/verification' import { outdent } from 'outdent' import type { NetlifyPluginOptions } from '@netlify/build' -import { describeCwdTmpDir, moveNextDist } from "../test-utils" +import { describeCwdTmpDir, moveNextDist } from '../test-utils' -const netlifyConfig = { build: { command: 'npm run build' }, functions: {}, redirects: [], headers: [] } as NetlifyPluginOptions["netlifyConfig"] +const netlifyConfig = { + build: { command: 'npm run build' }, + functions: {}, + redirects: [], + headers: [], +} as NetlifyPluginOptions['netlifyConfig'] import type { NetlifyPluginUtils } from '@netlify/build' type FailBuild = NetlifyPluginUtils['build']['failBuild'] @@ -18,6 +27,12 @@ jest.mock('fs', () => { } }) +// disable chalk colors to easier validate console text output +jest.mock(`chalk`, () => { + process.env.FORCE_COLOR = '0' + return jest.requireActual('chalk') +}) + describe('checkNextSiteHasBuilt', () => { let failBuildMock const { existsSync } = require('fs') @@ -88,11 +103,14 @@ describe('checkNextSiteHasBuilt', () => { }) describe('checkZipSize', () => { - let consoleSpy + let consoleWarnSpy, consoleLogSpy const { existsSync, promises } = require('fs') beforeEach(() => { - consoleSpy = jest.spyOn(global.console, 'warn') + consoleWarnSpy = jest.spyOn(global.console, 'warn') + consoleWarnSpy.mockClear() + consoleLogSpy = jest.spyOn(global.console, 'log') + consoleLogSpy.mockClear() process.env.DISABLE_BUNDLE_ZIP_SIZE_CHECK = 'false' }) @@ -100,36 +118,49 @@ describe('checkZipSize', () => { delete process.env.DISABLE_BUNDLE_ZIP_SIZE_CHECK }) + afterAll(() => { + consoleWarnSpy.mockReset() + consoleLogSpy.mockReset() + existsSync.mockReset() + }) + it('emits a warning that DISABLE_BUNDLE_ZIP_SIZE_CHECK was enabled', async () => { process.env.DISABLE_BUNDLE_ZIP_SIZE_CHECK = 'true' await checkZipSize(chance.string()) - expect(consoleSpy).toHaveBeenCalledWith('Function bundle size check was DISABLED with the DISABLE_BUNDLE_ZIP_SIZE_CHECK environment variable. Your deployment will break if it exceeds the maximum supported size of function zip files in your account.') + expect(consoleWarnSpy).toHaveBeenCalledWith( + 'Function bundle size check was DISABLED with the DISABLE_BUNDLE_ZIP_SIZE_CHECK environment variable. Your deployment will break if it exceeds the maximum supported size of function zip files in your account.', + ) }) it('does not emit a warning if the file size is below the warning size', async () => { existsSync.mockReturnValue(true) - jest.spyOn(promises, 'stat').mockResolvedValue({ size: (1024 * 1024 * 20) }) + jest.spyOn(promises, 'stat').mockResolvedValue({ size: 1024 * 1024 * 20 }) await checkZipSize('some-file.zip') - expect(consoleSpy).not.toHaveBeenCalled() + expect(consoleWarnSpy).not.toHaveBeenCalled() }) it('emits a warning if the file size is above the warning size', async () => { existsSync.mockReturnValue(true) - jest.spyOn(promises, 'stat').mockResolvedValue({ size: (1024 * 1024 * 200) }) + jest.spyOn(promises, 'stat').mockResolvedValue({ size: 1024 * 1024 * 200 }) - await checkZipSize('some-file.zip') + try { + await checkZipSize('some-file.zip') + } catch (e) { + // StreamZip is not mocked, so ultimately the call will throw an error, + // but we are logging message before that so we can assert it + } - expect(consoleSpy).toHaveBeenCalledWith( + expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining( - 'The function zip some-file.zip size is 200 MB, which is larger than the recommended maximum size of 250 MB.' - ) + 'The function zip some-file.zip size is 210 MB, which is larger than the recommended maximum size of 52.4 MB.', + ), ) }) }) -describeCwdTmpDir("getProblematicUserRewrites", () => { +describeCwdTmpDir('getProblematicUserRewrites', () => { it('finds problematic user rewrites', async () => { await moveNextDist() const rewrites = getProblematicUserRewrites({