diff --git a/package.json b/package.json index 93d41d0d87..01aa8cf56a 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "watch": "tsc --watch" }, "config": { - "eslint": "--cache --format=codeframe --max-warnings=0 \"{src,scripts,tests,.github}/**/*.{js,md,html}\" \"*.{js,md,html}\" \".*.{js,md,html}\"", - "prettier": "--loglevel=warn \"{src,scripts,tests,.github}/**/*.{js,md,yml,json,html}\" \"*.{js,yml,json,html}\" \".*.{js,yml,json,html}\" \"!package-lock.json\"" + "eslint": "--cache --format=codeframe --max-warnings=0 \"{src,scripts,tests,.github}/**/*.{ts,js,md,html}\" \"*.{ts,js,md,html}\" \".*.{ts,js,md,html}\"", + "prettier": "--loglevel=warn \"{src,scripts,tests,.github}/**/*.{ts,js,md,yml,json,html}\" \"*.{ts,js,yml,json,html}\" \".*.{ts,js,yml,json,html}\" \"!package-lock.json\"" }, "repository": { "type": "git", diff --git a/src/constants.ts b/src/constants.ts index c77bb40957..48523a2b86 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -22,6 +22,8 @@ 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 export const DIVIDER = ` ──────────────────────────────────────────────────────────────── diff --git a/src/helpers/cache.ts b/src/helpers/cache.ts index d2a61d100d..077dcb10e3 100644 --- a/src/helpers/cache.ts +++ b/src/helpers/cache.ts @@ -19,4 +19,4 @@ export const saveCache = async ({ cache, publish }) => { } else { console.log('No Next.js cache to save.') } -} \ No newline at end of file +} diff --git a/src/helpers/files.js b/src/helpers/files.ts similarity index 79% rename from src/helpers/files.js rename to src/helpers/files.ts index 64f476d0e4..d65560c664 100644 --- a/src/helpers/files.js +++ b/src/helpers/files.ts @@ -1,31 +1,39 @@ /* eslint-disable max-lines */ -const { cpus } = require('os') - -const { yellowBright } = require('chalk') -const { - existsSync, - readJson, - move, - copy, - writeJson, - readFile, - writeFile, - ensureDir, - readFileSync, -} = require('fs-extra') -const globby = require('globby') -const { outdent } = require('outdent') -const pLimit = require('p-limit') -const { join } = require('pathe') -const slash = require('slash') - -const { MINIMUM_REVALIDATE_SECONDS, DIVIDER } = require('../constants') +import { cpus } from 'os' + +import { NetlifyConfig } from '@netlify/build' +import { yellowBright } from 'chalk' +import { existsSync, readJson, move, copy, writeJson, readFile, writeFile, ensureDir, readFileSync } from 'fs-extra' +import globby from 'globby' +import { PrerenderManifest } from 'next/dist/build' +import { Redirect as NextRedirect } from 'next/dist/lib/load-custom-routes' +import { outdent } from 'outdent' +import pLimit from 'p-limit' +import { join } from 'pathe' +import slash from 'slash' + +import { MINIMUM_REVALIDATE_SECONDS, DIVIDER } from '../constants' + +import { NextConfig } from './config' + +interface Redirect extends NextRedirect { + regex: string + internal?: boolean +} + +type Rewrites = + | { + fallback?: Array + afterFiles?: Array + beforeFiles?: Array + } + | Array const TEST_ROUTE = /(|\/)\[[^/]+?](\/|\.html|$)/ -const isDynamicRoute = (route) => TEST_ROUTE.test(route) +export const isDynamicRoute = (route) => TEST_ROUTE.test(route) -const stripLocale = (rawPath, locales = []) => { +export const stripLocale = (rawPath: string, locales: Array = []) => { const [locale, ...segments] = rawPath.split('/') if (locales.includes(locale)) { return segments.join('/') @@ -33,14 +41,14 @@ const stripLocale = (rawPath, locales = []) => { return rawPath } -const matchMiddleware = (middleware, filePath) => +export const matchMiddleware = (middleware: Array, filePath: string): string | boolean => middleware?.includes('') || middleware?.find( (middlewarePath) => filePath === middlewarePath || filePath === `${middlewarePath}.html` || filePath.startsWith(`${middlewarePath}/`), ) -const matchesRedirect = (file, redirects) => { +export const matchesRedirect = (file: string, redirects: Rewrites): boolean => { if (!Array.isArray(redirects)) { return false } @@ -53,7 +61,7 @@ const matchesRedirect = (file, redirects) => { }) } -const matchesRewrite = (file, rewrites) => { +export const matchesRewrite = (file: string, rewrites: Rewrites): boolean => { if (Array.isArray(rewrites)) { return matchesRedirect(file, rewrites) } @@ -63,14 +71,16 @@ const matchesRewrite = (file, rewrites) => { return matchesRedirect(file, rewrites.beforeFiles) } -exports.matchesRedirect = matchesRedirect -exports.matchesRewrite = matchesRewrite -exports.matchMiddleware = matchMiddleware -exports.stripLocale = stripLocale -exports.isDynamicRoute = isDynamicRoute - // eslint-disable-next-line max-lines-per-function -exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => { +export const moveStaticPages = async ({ + netlifyConfig, + target, + i18n, +}: { + netlifyConfig: NetlifyConfig + target: 'server' | 'serverless' | 'experimental-serverless-trace' + i18n: NextConfig['i18n'] +}): Promise => { console.log('Moving static page files to serve from CDN...') const outputDir = join(netlifyConfig.build.publish, target === 'server' ? 'server' : 'serverless') const root = join(outputDir, 'pages') @@ -87,12 +97,16 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => { } } - const prerenderManifest = await readJson(join(netlifyConfig.build.publish, 'prerender-manifest.json')) - const { redirects, rewrites } = await readJson(join(netlifyConfig.build.publish, 'routes-manifest.json')) + const prerenderManifest: PrerenderManifest = await readJson( + join(netlifyConfig.build.publish, 'prerender-manifest.json'), + ) + const { redirects, rewrites }: { redirects: Array; rewrites: Rewrites } = await readJson( + join(netlifyConfig.build.publish, 'routes-manifest.json'), + ) - const isrFiles = new Set() + const isrFiles = new Set() - const shortRevalidateRoutes = [] + const shortRevalidateRoutes: Array<{ Route: string; Revalidate: number }> = [] Object.entries(prerenderManifest.routes).forEach(([route, { initialRevalidateSeconds }]) => { if (initialRevalidateSeconds) { @@ -106,8 +120,8 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => { } }) - const files = [] - const filesManifest = {} + const files: Array = [] + const filesManifest: Record = {} const moveFile = async (file) => { const isData = file.endsWith('.json') const source = join(root, file) @@ -268,7 +282,7 @@ exports.moveStaticPages = async ({ netlifyConfig, target, i18n }) => { } } -const patchFile = async ({ file, from, to }) => { +const patchFile = async ({ file, from, to }: { file: string; from: string; to: string }): Promise => { if (!existsSync(file)) { return } @@ -299,7 +313,7 @@ const getServerFile = (root) => { return serverFile } -exports.patchNextFiles = async (root) => { +export const patchNextFiles = async (root: string): Promise => { const serverFile = getServerFile(root) console.log(`Patching ${serverFile}`) if (serverFile) { @@ -311,7 +325,7 @@ exports.patchNextFiles = async (root) => { } } -exports.unpatchNextFiles = async (root) => { +export const unpatchNextFiles = async (root: string): Promise => { const serverFile = getServerFile(root) const origFile = `${serverFile}.orig` if (existsSync(origFile)) { @@ -319,7 +333,15 @@ exports.unpatchNextFiles = async (root) => { } } -exports.movePublicFiles = async ({ appDir, outdir, publish }) => { +export const movePublicFiles = async ({ + appDir, + outdir, + publish, +}: { + appDir: string + outdir?: string + publish: string +}): Promise => { // `outdir` is a config property added when using Next.js with Nx. It's typically // a relative path outside of the appDir, e.g. '../../dist/apps/', and // the parent directory of the .next directory. diff --git a/src/helpers/verification.js b/src/helpers/verification.ts similarity index 78% rename from src/helpers/verification.js rename to src/helpers/verification.ts index d89aabf428..2ab7181bdb 100644 --- a/src/helpers/verification.js +++ b/src/helpers/verification.ts @@ -1,17 +1,29 @@ -const { existsSync, promises } = require('fs') -const path = require('path') -const { relative } = require('path') +import { existsSync, promises } from 'fs' +import path, { relative } from 'path' -const { yellowBright, greenBright, blueBright, redBright, reset } = require('chalk') -const { async: StreamZip } = require('node-stream-zip') -const outdent = require('outdent') -const prettyBytes = require('pretty-bytes') -const { satisfies } = require('semver') +import { NetlifyPluginUtils } from '@netlify/build' +import { yellowBright, greenBright, blueBright, redBright, 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' // This is when nft support was added const REQUIRED_BUILD_VERSION = '>=18.16.0' -exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuild }) => { +type FailBuild = NetlifyPluginUtils['build']['failBuild'] + +export const verifyNetlifyBuildVersion = ({ + IS_LOCAL, + NETLIFY_BUILD_VERSION, + failBuild, +}: { + IS_LOCAL: boolean + NETLIFY_BUILD_VERSION: string + failBuild: FailBuild +}): void | never => { // We check for build version because that's what's available to us, but prompt about the cli because that's what they can upgrade if (IS_LOCAL && !satisfies(NETLIFY_BUILD_VERSION, REQUIRED_BUILD_VERSION, { includePrerelease: true })) { return failBuild(outdent` @@ -21,7 +33,7 @@ exports.verifyNetlifyBuildVersion = ({ IS_LOCAL, NETLIFY_BUILD_VERSION, failBuil } } -exports.checkForOldFunctions = async ({ functions }) => { +export const checkForOldFunctions = async ({ functions }: Pick): Promise => { const allOldFunctions = await functions.list() const oldFunctions = allOldFunctions.filter(({ name }) => name.startsWith('next_')) if (oldFunctions.length !== 0) { @@ -41,7 +53,13 @@ exports.checkForOldFunctions = async ({ functions }) => { } } -exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => { +export const checkNextSiteHasBuilt = ({ + publish, + failBuild, +}: { + publish: string + failBuild: FailBuild +}): void | never => { if (!existsSync(path.join(publish, 'BUILD_ID'))) { return failBuild(outdent` The directory "${path.relative( @@ -61,7 +79,13 @@ exports.checkNextSiteHasBuilt = ({ publish, failBuild }) => { } } -exports.checkForRootPublish = ({ publish, failBuild }) => { +export const checkForRootPublish = ({ + publish, + failBuild, +}: { + publish: string + failBuild: FailBuild +}): void | never => { if (path.resolve(publish) === path.resolve('.')) { failBuild(outdent` Your publish directory is pointing to the base directory of your site. This is not supported for Next.js sites, and is probably a mistake. @@ -70,10 +94,7 @@ exports.checkForRootPublish = ({ publish, failBuild }) => { } } -// 50MB, which is the documented max, though the hard max seems to be higher -const LAMBDA_MAX_SIZE = 1024 * 1024 * 50 - -exports.checkZipSize = async (file, maxSize = LAMBDA_MAX_SIZE) => { +export const checkZipSize = async (file: string, maxSize: number = LAMBDA_MAX_SIZE): Promise => { if (!existsSync(file)) { console.warn(`Could not check zip size because ${file} does not exist`) return @@ -111,7 +132,7 @@ exports.checkZipSize = async (file, maxSize = LAMBDA_MAX_SIZE) => { ) } -exports.logBetaMessage = () => +export const logBetaMessage = () => console.log( greenBright( outdent` diff --git a/src/index.js b/src/index.ts similarity index 73% rename from src/index.js rename to src/index.ts index 115a4196f9..5d99d85e1d 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,22 +1,23 @@ -const { join, relative } = require('path') - -const { ODB_FUNCTION_NAME } = require('./constants') -const { restoreCache, saveCache } = require('./helpers/cache') -const { getNextConfig, configureHandlerFunctions } = require('./helpers/config') -const { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } = require('./helpers/files') -const { generateFunctions, setupImageFunction, generatePagesResolver } = require('./helpers/functions') -const { generateRedirects } = require('./helpers/redirects') -const { +import { join, relative } from 'path' + +import { NetlifyPlugin } from '@netlify/build' + +import { ODB_FUNCTION_NAME } from './constants' +import { restoreCache, saveCache } from './helpers/cache' +import { getNextConfig, configureHandlerFunctions } from './helpers/config' +import { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } from './helpers/files' +import { generateFunctions, setupImageFunction, generatePagesResolver } from './helpers/functions' +import { generateRedirects } from './helpers/redirects' +import { verifyNetlifyBuildVersion, checkNextSiteHasBuilt, checkForRootPublish, logBetaMessage, checkZipSize, checkForOldFunctions, -} = require('./helpers/verification') +} from './helpers/verification' -/** @type import("@netlify/build").NetlifyPlugin */ -module.exports = { +const plugin: NetlifyPlugin = { async onPreBuild({ constants, netlifyConfig, @@ -71,7 +72,7 @@ module.exports = { } if (!process.env.SERVE_STATIC_FILES_FROM_ORIGIN) { - await moveStaticPages({ target, failBuild, netlifyConfig, i18n }) + await moveStaticPages({ target, netlifyConfig, i18n }) } await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath }) @@ -82,7 +83,15 @@ module.exports = { }) }, - async onPostBuild({ netlifyConfig, utils: { cache, functions, failBuild }, constants: { FUNCTIONS_DIST } }) { + async onPostBuild({ + netlifyConfig, + utils: { + cache, + functions, + build: { failBuild }, + }, + constants: { FUNCTIONS_DIST }, + }) { await saveCache({ cache, publish: netlifyConfig.build.publish }) await checkForOldFunctions({ functions }) await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`)) @@ -93,3 +102,4 @@ module.exports = { logBetaMessage() }, } +module.exports = plugin diff --git a/src/templates/handlerUtils.ts b/src/templates/handlerUtils.ts index c567b36140..84430f91ce 100644 --- a/src/templates/handlerUtils.ts +++ b/src/templates/handlerUtils.ts @@ -133,7 +133,7 @@ export const augmentFsModule = ({ return readfileOrig(file, options) }) as typeof promises.readFile - promises.stat = (async (file, options) => { + promises.stat = ((file, options) => { // We only care about page files if (file.startsWith(pageRoot)) { // We only want the part after `pages/`