Skip to content

fix: make large lambda warning message more informative #2057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/runtime/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
────────────────────────────────────────────────────────────────
Expand Down
19 changes: 12 additions & 7 deletions packages/runtime/src/helpers/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -105,7 +105,11 @@ export const checkForRootPublish = ({
}
}

export const checkZipSize = async (file: string, maxSize: number = LAMBDA_MAX_SIZE): Promise<void> => {
export const checkZipSize = async (
file: string,
maxSize: number = LAMBDA_MAX_SIZE,
warningSize: number = LAMBDA_WARNING_SIZE,
): Promise<void> => {
// Requires contacting the Netlify Support team to fully enable.
// Enabling this without contacting them can result in failed deploys.
if (isBundleSizeCheckDisabled()) {
Expand All @@ -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.
`),
Expand Down
69 changes: 62 additions & 7 deletions test/helpers/verification.spec.ts
Original file line number Diff line number Diff line change
@@ -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']
Expand All @@ -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')
Expand Down Expand Up @@ -88,24 +103,64 @@ 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'
})

afterEach(() => {
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 })

await checkZipSize('some-file.zip')

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 })

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(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining(
'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({
Expand Down