diff --git a/packages/runtime/docs/runtime.md b/packages/runtime/docs/runtime.md new file mode 100644 index 00000000..4e7cd149 --- /dev/null +++ b/packages/runtime/docs/runtime.md @@ -0,0 +1,178 @@ +# Runtime development sheet + +From: 20. November 2021 + +This is a description how the runtime internally works. + +## Next.js milestones + +This contains a list of milestones where functionality in Next.js was changed that affects the runtime. + +### [11.1.3-canary.96](https://github.com/vercel/next.js/releases/tag/v11.1.3-canary.96) - 23 Oct 2021 + +With this release a new manifest (`*.nft.json`) was introduced, so that each SSR page also contains the files it requires from third-party libraries. +This basically runs a version of [`@vercel/nft`](https://www.npmjs.com/package/@vercel/nft) in Next.js instead of this builder. +Therefore no file-tracing is required after this release. + +### [10.0.9-canary.4](https://github.com/vercel/next.js/releases/tag/v10.0.9-canary.4) - 09 Mar 2021 + +Beginning with this version the builder no longer sets the environment variable `NEXT_PRIVATE_TARGET=experimental-serverless-trace` and uses the default `target: server`. + +### [10.0.8-canary.15](https://github.com/vercel/next.js/releases/tag/v10.0.8-canary.15) - 03 Mar 2021 + +Beginning with this version Next.js uses the `target: server` by default instead of `target: experimental-serverless-trace`. +This can be overridden by setting the environment variable `NEXT_PRIVATE_TARGET=experimental-serverless-trace` in newer versions of Next.js. +The path of the server output folder also changed from `.serverless` to `.server` in the build output. + +### [9.0.4-canary.1](https://github.com/vercel/next.js/releases/tag/v9.0.4-canary.1) - 06 Aug 2019 + +Beginning with with this version, the builder uses `target: experimental-serverless-trace` instead of `target: serverless` when building Next.js. +This means that instead of including the whole content of `node-modules` in the Lambda, only files that are actually used in code are included in the Lambda package (File-tracing). +This is done via the [`@vercel/nft`](https://www.npmjs.com/package/@vercel/nft) package + +### [7.0.3-alpha.0](https://github.com/vercel/next.js/releases/tag/v7.0.3-alpha.0) - 30 Oct 2018 + +All versions prior to this released are considered as legacy versions by the build tool. +Legacy means that no file-tracing is active and everything from the `node_modules` is included in the Lambda. + +## Procedure + +### 1. Download of files + +All source files that are required for the build, are downloaded to a temporary folder before the actual build happens. +Some third-party packages try to detect a Vercel-like build environment like this, so we need to make sure that the following environment variables are set before staring the build: + +``` +NOW_BUILDER=1 +VERCEL=1 +``` + +Since the build script is commonly started from outside of the temporary download folder, we have to manually set the `INIT_CWD` environment variable to the temporary download folder. + +### 2. Pre-Build + +Before running the build, the `package.json` gets customized to ensure that the `next build` command is executed by the builder. +If the `build` script is not set, it gets overridden with `next build`. +When the `build` script is already set, then the `build` script is renamed and runs before the actual Next.js build. + +It is then detecting if npm or yarn is used and runs the install command of the package manager that is used. + +#### Create serverless `next.config.js` + +> This step is only executed on Next.js versions `< 10.0.9-canary.4`. + +To set the target option in the `next.config.js`, the original file (if exists) is renamed to `next.config.__vercel_builder_backup__.js`. +Then a new `next.config.js` is created with the following content: + +```js +module.exports = function (...args) { + let original = require('./next.config.__vercel_builder_backup__'); + + const finalConfig = {}; + const target = { target: 'experimental-serverless-trace' }; + + if ( + typeof original === 'function' && + original.constructor.name === 'AsyncFunction' + ) { + // AsyncFunctions will become promises + original = original(...args); + } + + if (original instanceof Promise) { + // Special case for promises, as it's currently not supported + // and will just error later on + return original + .then((orignalConfig) => Object.assign(finalConfig, orignalConfig)) + .then((config) => Object.assign(config, target)); + } else if (typeof original === 'function') { + Object.assign(finalConfig, original(...args)); + } else if (typeof original === 'object') { + Object.assign(finalConfig, original); + } + + Object.assign(finalConfig, target); + + return finalConfig; +}; +``` + +### 3. Build Next.js + +Building Next.js is simply running `next build` which produces a bunch of files that are written to the `.next` folder. + +The following files are then used in the further process: + +- Routes manifest +- Images manifest +- prerender manifest + Prerendered routes emit a `.html` file but should not be treated as a static page. + Lazily prerendered routes have a fallback `.html` file on newer Next.js versions so we need to also not treat it as a static page here. + +### 4. Building Routes + +The following things are extracted from the routes manifest: + +- redirects +- rewrites +- dataroutes (available at `/_next/data`) for both dynamic SSG and SSP pages. + - Can also have a nextLocale + +### 5. Create image config + +The image config is created from the Images manifest. + +### 6. Build Lambdas + +#### Important variables + +- `pagesDir` + Path to the server (or serverless) directory inside the `.next folder` +- `pages` + Contains a list of all `.js` files exported in the `pagesDir`. + Has at least 1 entry (`_error.js`). +- `staticPageFiles` + Container a list of all `.html` files exported in the `pagesDir` +- `staticPages` +- `dynamicPages` + Contains all SSR pages that have a dynamic pattern in its path (e.g. `[someId].js`) +- `pseudoLayers` +- `apiPseudoLayers` +- `nonLambdaSsgPages` +- `apiLambdaGroups` +- `pageLambdaGroups` + +First the static output (prerendered HTML pages `.html`) is analyzed. +They are categorized into static routes (e.g. `/test`) and dynamic pages (`/[slug]`). + +Then it is determined if the 404 page is static (prerendered) or dynamic (SSR). + +Each static route from the prerender manifest is then checked if it can be added to `nonLambdaSsgPages`. + +#### 6.x Tracing + +> Tracing is only executed for Node.js versions `>9.0.4-canary.1` + +Then the tracing is executed. + +For this every page from `pages` is categorized into `apiPages` or `nonApiPages`. +Only pages that are not already in `nonLambdaSsgPages` are added to `nonApiPages`. + +Then nft is executed for both the `apiPages` and `nonApiPages`. + +From that the traced files are collected into `tracedFiles` and `apiTracedFiles`. +Then a pseudoLayer is created from `tracedFiles` and `apiTracedFiles`, which contain the dependencies of the pages and apiPages. + +A pseudoLayer is an object that contains for each filePath the compressed buffer of the original file (PseudoFile) or the information about the symlink of the file (PseudoSymbolicLink). +The created pseudoLayers are then pushed to `pseudoLayers` and `apiPseudoLayers` + +#### 6.x Creating serverless functions + +- **Shared Lambdas** + Every page in `pages` (that is not `_app.js` or `_document.js`) is assigned to a LambdaGroup. + If the page is already in `nonLambdaSsgPages` it is not added to the Lambda. + A LambdaGroup is a collection of multiple pages or ApiPages that can be combined into a single Lambda (Which reduces the total number of Lambdas needed for serving the app.) + A LambdaGroup has the name of the form `__NEXT_PAGE_LAMBDA_` for pages and `__NEXT_API_LAMBDA_` for apiPages. + The aim is that each LambdaGroup stays below 50mb (Compressed code size limit from AWS Lambda), so when a LambdaGroup exceeds this limit, a new group is crated. + + For each page is then a new route is added to `dynamicPageLambdaRoutes` diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 90c01b57..a6f611a6 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -27,7 +27,7 @@ "@types/semver": "6.0.0", "@types/yazl": "2.4.1", "@vercel/build-utils": "2.12.1", - "@vercel/nft": "0.10.0", + "@vercel/nft": "0.17.0", "@vercel/routing-utils": "1.10.1", "async-sema": "3.0.1", "buffer-crc32": "0.2.13", diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 0dad57b9..a18539ac 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -38,6 +38,7 @@ import { EnvConfig, excludeFiles, ExperimentalTraceVersion, + NextServerTargetVersion, getDynamicRoutes, getExportIntent, getExportStatus, @@ -47,6 +48,7 @@ import { getPrerenderManifest, getRoutes, getRoutesManifest, + getServerFilesManifest, getSourceFilePathFromPage, isDynamicRoute, normalizeLocalePath, @@ -89,7 +91,7 @@ export const version = 2; const htmlContentType = 'text/html; charset=utf-8'; const nowDevChildProcesses = new Set(); -['SIGINT', 'SIGTERM'].forEach(signal => { +['SIGINT', 'SIGTERM'].forEach((signal) => { process.once(signal as NodeJS.Signals, () => { for (const child of nowDevChildProcesses) { debug( @@ -193,6 +195,18 @@ function isLegacyNext(nextVersion: string) { return true; } +/** + * Check if the target: `server` can be used to build the app + */ +function isServerTargetNext(nextVersionRange: string): boolean { + if (nextVersionRange === 'canary' || nextVersionRange === 'latest') { + return true; + } + + // >= NextServerTargetVersion + return !semver.gtr(NextServerTargetVersion, nextVersionRange); +} + const name = '[@vercel/next]'; const urls: stringMap = {}; @@ -262,12 +276,6 @@ export async function build({ ...spawnOpts.env, NOW_BUILDER: '1', VERCEL: '1', - // Next.js changed the default output folder beginning with - // 10.0.8-canary.15 from `.next/serverless` to `.next/server`. - // This is an opt-out of this behavior until we support it. - // https://github.com/dealmore/terraform-aws-next-js/issues/86 - // https://github.com/vercel/next.js/pull/22731 - NEXT_PRIVATE_TARGET: 'experimental-serverless-trace', // We override init CWD here with the entrypoint to ensure that applications // can get the CWD from the download directory root INIT_CWD: entryPath, @@ -351,8 +359,14 @@ export async function build({ console.warn('WARNING: You should not upload the `.next` directory.'); } + const isServerTarget = + nextVersionRange && isServerTargetNext(nextVersionRange); const isLegacy = nextVersionRange && isLegacyNext(nextVersionRange); - debug(`MODE: ${isLegacy ? 'legacy' : 'serverless'}`); + debug( + `MODE: ${ + isServerTarget ? 'server(less)' : isLegacy ? 'legacy' : 'serverless' + }` + ); if (isLegacy) { console.warn( @@ -436,7 +450,11 @@ export async function build({ }); } - if (!isLegacy) { + if (isServerTarget) { + debug( + `Application is being built in server mode since 10.0.9 meets minimum version of v${NextServerTargetVersion}` + ); + } else if (!isLegacy) { await createServerlessConfig(workPath, entryPath, nextVersion); } @@ -444,6 +462,19 @@ export async function build({ const env: typeof process.env = { ...spawnOpts.env }; env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`; + // Next.js changed the default output folder beginning with + // 10.0.8-canary.15 from `.next/serverless` to `.next/server`. + // This is an opt-out of this behavior for older versions. + // https://github.com/dealmore/terraform-aws-next-js/issues/86 + // https://github.com/vercel/next.js/pull/22731 + if (!isServerTarget) { + env.NEXT_PRIVATE_TARGET = 'experimental-serverless-trace'; + } + + /* --------------------------------------------------------------------------- + * Build Next.js + * -------------------------------------------------------------------------*/ + if (buildCommand) { // Add `node_modules/.bin` to PATH const nodeBinPath = await getNodeBinPath({ cwd: entryPath }); @@ -548,6 +579,10 @@ export async function build({ .replace(/\/+$/, ''); } + /* --------------------------------------------------------------------- + * DataRoutes + * -------------------------------------------------------------------*/ + if (routesManifest.dataRoutes) { // Load the /_next/data routes for both dynamic SSG and SSP pages. // These must be combined and sorted to prevent conflicts @@ -578,7 +613,7 @@ export async function build({ `${(ssgDataRoute && ssgDataRoute.dataRoute) || dataRoute.page}${ dataRoute.routeKeys ? `?${Object.keys(dataRoute.routeKeys) - .map(key => `${dataRoute.routeKeys![key]}=$${key}`) + .map((key) => `${dataRoute.routeKeys![key]}=$${key}`) .join('&')}` : '' }` @@ -597,7 +632,7 @@ export async function build({ `/${escapedBuildId}/(?${ ssgDataRoute ? '' : ':' }${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})/` ); @@ -611,7 +646,7 @@ export async function build({ `/${escapedBuildId}/(?${ ssgDataRoute ? '' : ':' }${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})[/]?` ); } @@ -646,6 +681,10 @@ export async function build({ } } + /* --------------------------------------------------------------------------- + * Image Config + * -------------------------------------------------------------------------*/ + if (imagesManifest) { switch (imagesManifest.version) { case 1: { @@ -706,6 +745,10 @@ export async function build({ const userExport = await getExportStatus(entryPath); + /* --------------------------------------------------------------------------- + * Build next export (Statically exported Next.js app) + * -------------------------------------------------------------------------*/ + if (userExport) { const exportIntent = await getExportIntent(entryPath); const { trailingSlash = false } = exportIntent || {}; @@ -862,7 +905,14 @@ export async function build({ let static404Page: string | undefined; let page404Path = ''; + /* --------------------------------------------------------------------------- + * Build with SSR + * -------------------------------------------------------------------------*/ + if (isLegacy) { + /* ------------------------------------------------------------------------- + * Build Legacy + * -----------------------------------------------------------------------*/ const filesAfterBuild = await glob('**', entryPath); debug('Preparing serverless function files...'); @@ -874,7 +924,7 @@ export async function build({ ); const nodeModules = excludeFiles( await glob('node_modules/**', entryPath), - file => file.startsWith('node_modules/.cache') + (file) => file.startsWith('node_modules/.cache') ); const launcherFiles = { 'now__bridge.js': new FileFsRef({ @@ -903,7 +953,7 @@ export async function build({ const launcherData = await readFile(launcherPath, 'utf8'); await Promise.all( - Object.keys(pages).map(async page => { + Object.keys(pages).map(async (page) => { // These default pages don't have to be handled as they'd always 404 if (['_app.js', '_error.js', '_document.js'].includes(page)) { return; @@ -956,11 +1006,15 @@ export async function build({ }) ); } else { + /* ------------------------------------------------------------------------- + * Build serverless + * -----------------------------------------------------------------------*/ + debug('Preparing serverless function files...'); const pagesDir = path.join( entryPath, outputDirectory, - 'serverless', + isServerTarget ? 'server' : 'serverless', 'pages' ); @@ -1061,9 +1115,9 @@ export async function build({ }; const isApiPage = (page: string) => - page.replace(/\\/g, '/').match(/serverless\/pages\/api(\/|\.js$)/); + page.replace(/\\/g, '/').match(/server(less)?\/pages\/api(\/|\.js$)/); - const canUsePreviewMode = Object.keys(pages).some(page => + const canUsePreviewMode = Object.keys(pages).some((page) => isApiPage(pages[page].fsPath) ); @@ -1095,7 +1149,7 @@ export async function build({ } }; - Object.keys(prerenderManifest.staticRoutes).forEach(route => + Object.keys(prerenderManifest.staticRoutes).forEach((route) => onPrerenderRouteInitial(route) ); @@ -1158,7 +1212,7 @@ export async function build({ debug(`node-file-trace result for pages: ${fileList}`); const lstatSema = new Sema(25, { - capacity: fileList.length + apiFileList.length, + capacity: fileList.size + apiFileList.size, }); const lstatResults: { [key: string]: ReturnType } = {}; @@ -1166,7 +1220,7 @@ export async function build({ reasons: NodeFileTraceReasons, files: { [filePath: string]: FileFsRef } ) => async (file: string) => { - const reason = reasons[file]; + const reason = reasons.get(file); if (reason && reason.type === 'initial') { // Initial files are manually added to the lambda later return; @@ -1188,10 +1242,12 @@ export async function build({ }; await Promise.all( - fileList.map(collectTracedFiles(nonApiReasons, tracedFiles)) + Array.from(fileList).map(collectTracedFiles(nonApiReasons, tracedFiles)) ); await Promise.all( - apiFileList.map(collectTracedFiles(apiReasons, apiTracedFiles)) + Array.from(apiFileList).map( + collectTracedFiles(apiReasons, apiTracedFiles) + ) ); if (hasLambdas) { @@ -1235,7 +1291,7 @@ export async function build({ debug( 'detected (legacy) assets to be bundled with serverless function:' ); - assetKeys.forEach(assetFile => debug(`\t${assetFile}`)); + assetKeys.forEach((assetFile) => debug(`\t${assetFile}`)); debug( '\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.' ); @@ -1361,7 +1417,7 @@ export async function build({ if (i18n) { addPageLambdaRoute( `[/]?(?:${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})?${escapeStringRegexp(outputName)}` ); } else { @@ -1395,7 +1451,7 @@ export async function build({ } } else { await Promise.all( - pageKeys.map(async page => { + pageKeys.map(async (page) => { // These default pages don't have to be handled as they'd always 404 if (['_app.js', '_document.js'].includes(page)) { return; @@ -1492,8 +1548,8 @@ export async function build({ false, routesManifest, new Set(prerenderManifest.omittedRoutes) - ).then(arr => - arr.map(route => { + ).then((arr) => + arr.map((route) => { const { i18n } = routesManifest || {}; if (i18n) { @@ -1513,7 +1569,7 @@ export async function build({ `^${dynamicPrefix ? `${dynamicPrefix}[/]?` : '[/]?'}(?${ isLocalePrefixed ? '' : ':' }${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})?` ); @@ -1533,203 +1589,254 @@ export async function build({ ); if (isSharedLambdas) { - const launcherPath = path.join(__dirname, 'templated-launcher-shared.js'); + const launcherPath = path.join( + __dirname, + isServerTarget ? 'server-launcher.js' : 'templated-launcher-shared.js' + ); const launcherData = await readFile(launcherPath, 'utf8'); - // we need to include the prerenderManifest.omittedRoutes here - // for the page to be able to be matched in the lambda for preview mode - const completeDynamicRoutes = await getDynamicRoutes( - entryPath, - entryDirectory, - dynamicPages, - false, - routesManifest - ).then(arr => - arr.map(route => { - route.src = route.src.replace('^', `^${dynamicPrefix}`); - return route; - }) - ); + let launchers: Array<{ launcher: string; group: LambdaGroup }>; - await Promise.all( - [...apiLambdaGroups, ...pageLambdaGroups].map( - async function buildLambdaGroup(group: LambdaGroup) { + if (isServerTarget) { + const serverFilesManifest = await getServerFilesManifest( + entryPath, + outputDirectory + ); + + if (!serverFilesManifest || !serverFilesManifest.config) { + throw new NowBuildError({ + code: 'NEXT_NO_SERVER_FILES_MANIFEST', + message: + 'No required-server-files.json file was found after running `next build`.', + }); + } + + const stringifiedNextConfig = JSON.stringify( + serverFilesManifest.config + ); + + // Inject Next.js config into launcher + launchers = [...apiLambdaGroups, ...pageLambdaGroups].map( + function buildLambdaGroup(group: LambdaGroup) { + const launcher = launcherData.replace( + /__LAUNCHER_NEXT_CONFIG__/g, + stringifiedNextConfig + ); + + return { + group, + launcher, + }; + } + ); + } else { + // we need to include the prerenderManifest.omittedRoutes here + // for the page to be able to be matched in the lambda for preview mode + const completeDynamicRoutes = await getDynamicRoutes( + entryPath, + entryDirectory, + dynamicPages, + false, + routesManifest + ).then((arr) => + arr.map((route) => { + route.src = route.src.replace('^', `^${dynamicPrefix}`); + return route; + }) + ); + launchers = [...apiLambdaGroups, ...pageLambdaGroups].map( + function buildLambdaGroup(group: LambdaGroup) { const groupPageKeys = Object.keys(group.pages); const launcher = launcherData.replace( /\/\/ __LAUNCHER_PAGE_HANDLER__/g, ` - const url = require('url'); - - ${ - routesManifest?.i18n - ? ` - function stripLocalePath(pathname) { - // first item will be empty string from splitting at first char - const pathnameParts = pathname.split('/') - - ;(${JSON.stringify( - routesManifest.i18n.locales - )}).some((locale) => { - if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) { - pathnameParts.splice(1, 1) - pathname = pathnameParts.join('/') || '/index' - return true - } - return false - }) + const url = require('url'); + + ${ + routesManifest?.i18n + ? ` + function stripLocalePath(pathname) { + // first item will be empty string from splitting at first char + const pathnameParts = pathname.split('/') + + ;(${JSON.stringify( + routesManifest.i18n.locales + )}).some((locale) => { + if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) { + pathnameParts.splice(1, 1) + pathname = pathnameParts.join('/') || '/index' + return true + } + return false + }) - return pathname + return pathname + } + ` + : `function stripLocalePath(pathname) { return pathname }` } - ` - : `function stripLocalePath(pathname) { return pathname }` - } - page = function(req, res) { - try { - const pages = { - ${groupPageKeys - .map( - page => - `'${page}': () => require('./${path.join( - './', - group.pages[page].pageFileName - )}')` - ) - .join(',\n')} - ${ - '' /* - creates a mapping of the page and the page's module e.g. - '/about': () => require('./.next/serverless/pages/about.js') - */ + page = function(req, res) { + try { + const pages = { + ${groupPageKeys + .map( + (page) => + `'${page}': () => require('./${path.join( + './', + group.pages[page].pageFileName + )}')` + ) + .join(',\n')} + ${ + '' /* + creates a mapping of the page and the page's module e.g. + '/about': () => require('./.next/serverless/pages/about.js') + */ + } } - } - let toRender = req.headers['x-nextjs-page'] - - if (!toRender) { - try { - const { pathname } = url.parse(req.url) - toRender = stripLocalePath(pathname).replace(/\\/$/, '') || '/index' - } catch (_) { - // handle failing to parse url - res.statusCode = 400 - return res.end('Bad Request') + let toRender = req.headers['x-nextjs-page'] + + if (!toRender) { + try { + const { pathname } = url.parse(req.url) + toRender = stripLocalePath(pathname).replace(/\\/$/, '') || '/index' + } catch (_) { + // handle failing to parse url + res.statusCode = 400 + return res.end('Bad Request') + } } - } - let currentPage = pages[toRender] + let currentPage = pages[toRender] - if ( - toRender && - !currentPage - ) { - if (toRender.includes('/_next/data')) { - toRender = toRender - .replace(new RegExp('/_next/data/${escapedBuildId}/'), '/') - .replace(/\\.json$/, '') + if ( + toRender && + !currentPage + ) { + if (toRender.includes('/_next/data')) { + toRender = toRender + .replace(new RegExp('/_next/data/${escapedBuildId}/'), '/') + .replace(/\\.json$/, '') - toRender = stripLocalePath(toRender) || '/index' - currentPage = pages[toRender] - } + toRender = stripLocalePath(toRender) || '/index' + currentPage = pages[toRender] + } - if (!currentPage) { - // for prerendered dynamic routes (/blog/post-1) we need to - // find the match since it won't match the page directly - const dynamicRoutes = ${JSON.stringify( - completeDynamicRoutes.map(route => ({ - src: route.src, - dest: route.dest, - })) - )} - - for (const route of dynamicRoutes) { - const matcher = new RegExp(route.src) - - if (matcher.test(toRender)) { - toRender = url.parse(route.dest).pathname - currentPage = pages[toRender] - break + if (!currentPage) { + // for prerendered dynamic routes (/blog/post-1) we need to + // find the match since it won't match the page directly + const dynamicRoutes = ${JSON.stringify( + completeDynamicRoutes.map((route) => ({ + src: route.src, + dest: route.dest, + })) + )} + + for (const route of dynamicRoutes) { + const matcher = new RegExp(route.src) + + if (matcher.test(toRender)) { + toRender = url.parse(route.dest).pathname + currentPage = pages[toRender] + break + } } } } - } - if (!currentPage) { - console.error( - "Failed to find matching page for", {toRender, header: req.headers['x-nextjs-page'], url: req.url }, "in lambda" - ) - console.error('pages in lambda', Object.keys(pages)) - res.statusCode = 500 - return res.end('internal server error') - } + if (!currentPage) { + console.error( + "Failed to find matching page for", {toRender, header: req.headers['x-nextjs-page'], url: req.url }, "in lambda" + ) + console.error('pages in lambda', Object.keys(pages)) + res.statusCode = 500 + return res.end('internal server error') + } - const mod = currentPage() - const method = mod.render || mod.default || mod + const mod = currentPage() + const method = mod.render || mod.default || mod - return method(req, res) - } catch (err) { - console.error('Unhandled error during request:', err) - throw err + return method(req, res) + } catch (err) { + console.error('Unhandled error during request:', err) + throw err + } } - } - ` + ` ); - const launcherFiles: { [name: string]: FileFsRef | FileBlob } = { - [path.join( - path.relative(baseDir, entryPath), - 'now__bridge.js' - )]: new FileFsRef({ - fsPath: path.join(__dirname, 'now__bridge.js'), - }), - [path.join( - path.relative(baseDir, entryPath), - 'now__launcher.js' - )]: new FileBlob({ data: launcher }), - }; - const pageLayers: PseudoLayer[] = []; + return { launcher, group }; + } + ); + } - for (const page of groupPageKeys) { - const { pageLayer } = group.pages[page]; - pageLambdaMap[page] = group.lambdaIdentifier; - pageLayers.push(pageLayer); - } + await Promise.all( + launchers.map(async function buildLambdaGroup({ + launcher, + group, + }: { + launcher: string; + group: LambdaGroup; + }) { + const groupPageKeys = Object.keys(group.pages); - if (requiresTracing) { - lambdas[ - group.lambdaIdentifier - ] = await createLambdaFromPseudoLayers({ - files: { - ...launcherFiles, - }, - layers: [ - ...(group.isApiLambda ? apiPseudoLayers : pseudoLayers), - ...pageLayers, - ], - handler: path.join( - path.relative(baseDir, entryPath), - 'now__launcher.launcher' - ), - runtime: nodeVersion.runtime, - }); - } else { - lambdas[ - group.lambdaIdentifier - ] = await createLambdaFromPseudoLayers({ - files: { - ...launcherFiles, - ...assets, - }, - layers: pageLayers, - handler: path.join( - path.relative(baseDir, entryPath), - 'now__launcher.launcher' - ), - runtime: nodeVersion.runtime, - }); - } + const launcherFiles: { [name: string]: FileFsRef | FileBlob } = { + [path.join( + path.relative(baseDir, entryPath), + 'now__bridge.js' + )]: new FileFsRef({ + fsPath: path.join(__dirname, 'now__bridge.js'), + }), + [path.join( + path.relative(baseDir, entryPath), + 'now__launcher.js' + )]: new FileBlob({ data: launcher }), + }; + + const pageLayers: PseudoLayer[] = []; + + for (const page of groupPageKeys) { + const { pageLayer } = group.pages[page]; + pageLambdaMap[page] = group.lambdaIdentifier; + pageLayers.push(pageLayer); } - ) + + if (requiresTracing) { + lambdas[ + group.lambdaIdentifier + ] = await createLambdaFromPseudoLayers({ + files: { + ...launcherFiles, + }, + layers: [ + ...(group.isApiLambda ? apiPseudoLayers : pseudoLayers), + ...pageLayers, + ], + handler: path.join( + path.relative(baseDir, entryPath), + 'now__launcher.launcher' + ), + runtime: nodeVersion.runtime, + }); + } else { + lambdas[ + group.lambdaIdentifier + ] = await createLambdaFromPseudoLayers({ + files: { + ...launcherFiles, + ...assets, + }, + layers: pageLayers, + handler: path.join( + path.relative(baseDir, entryPath), + 'now__launcher.launcher' + ), + runtime: nodeVersion.runtime, + }); + } + }) ); } @@ -1963,13 +2070,13 @@ export async function build({ } }; - Object.keys(prerenderManifest.staticRoutes).forEach(route => + Object.keys(prerenderManifest.staticRoutes).forEach((route) => onPrerenderRoute(route, { isBlocking: false, isFallback: false }) ); - Object.keys(prerenderManifest.fallbackRoutes).forEach(route => + Object.keys(prerenderManifest.fallbackRoutes).forEach((route) => onPrerenderRoute(route, { isBlocking: false, isFallback: true }) ); - Object.keys(prerenderManifest.blockingFallbackRoutes).forEach(route => + Object.keys(prerenderManifest.blockingFallbackRoutes).forEach((route) => onPrerenderRoute(route, { isBlocking: true, isFallback: false }) ); @@ -2048,7 +2155,7 @@ export async function build({ // We need to delete lambdas from output instead of omitting them from the // start since we rely on them for powering Preview Mode (read above in // onPrerenderRoute). - prerenderManifest.omittedRoutes.forEach(routeKey => { + prerenderManifest.omittedRoutes.forEach((routeKey) => { // Get the route file as it'd be mounted in the builder output const routeFileNoExt = path.posix.join( entryDirectory, @@ -2108,7 +2215,7 @@ export async function build({ const trailingSlashRedirects: Route[] = []; - redirects = redirects.filter(_redir => { + redirects = redirects.filter((_redir) => { const redir = _redir as Source; // detect the trailing slash redirect and make sure it's // kept above the wildcard mapping to prevent erroneous redirects @@ -2140,7 +2247,7 @@ export async function build({ ...staticDirectoryFiles, }, wildcard: i18n?.domains - ? i18n?.domains.map(item => { + ? i18n?.domains.map((item) => { return { domain: item.domain, value: @@ -2186,7 +2293,7 @@ export async function build({ entryDirectory, '/' )}(?!(?:_next/.*|${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})(?:/.*|$))(.*)$`, // we aren't able to ensure trailing slash mode here // so ensure this comes after the trailing slash redirect @@ -2202,7 +2309,7 @@ export async function build({ '/', entryDirectory )}/?(?:${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})?/?$`, locale: { redirect: i18n.domains.reduce( @@ -2212,7 +2319,7 @@ export async function build({ }://${item.domain}/`; if (item.locales) { - item.locales.map(locale => { + item.locales.map((locale) => { prev[locale] = `http${item.http ? '' : 's'}://${ item.domain }/${locale}`; @@ -2271,7 +2378,7 @@ export async function build({ entryDirectory, '/' )}(?!(?:_next/.*|${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})(?:/.*|$))(.*)$`, dest: `/${i18n.defaultLocale}/$1`, continue: true, @@ -2294,7 +2401,7 @@ export async function build({ entryDirectory, '/' )}(?:${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})?[/]?404`, status: 404, continue: true, @@ -2313,7 +2420,7 @@ export async function build({ { handle: 'filesystem' }, // map pages to their lambda - ...pageLambdaRoutes.filter(route => { + ...pageLambdaRoutes.filter((route) => { // filter out any SSG pages as they are already present in output if ('headers' in route) { let page = route.headers?.['x-nextjs-page']!; @@ -2361,7 +2468,7 @@ export async function build({ '/', entryDirectory )}/?(?:${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})/(.*)`, dest: `${path.join('/', entryDirectory, '/')}$1`, check: true, @@ -2380,7 +2487,7 @@ export async function build({ entryDirectory, '/' )}(?:${i18n?.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})/(.*)`, dest: '/$1', check: true, @@ -2436,7 +2543,7 @@ export async function build({ entryDirectory, '/' )}(?${i18n.locales - .map(locale => escapeStringRegexp(locale)) + .map((locale) => escapeStringRegexp(locale)) .join('|')})(/.*|$)`, dest: '/$nextLocale/404', status: 404, diff --git a/packages/runtime/src/server-launcher.ts b/packages/runtime/src/server-launcher.ts new file mode 100644 index 00000000..c0ff310e --- /dev/null +++ b/packages/runtime/src/server-launcher.ts @@ -0,0 +1,48 @@ +// The Next.js builder can emit the project in a subdirectory depending on how +// many folder levels of `node_modules` are traced. To ensure `process.cwd()` +// returns the proper path, we change the directory to the folder with the +// launcher. This mimics `yarn workspace run` behavior. +process.chdir(__dirname); + +const region = process.env.VERCEL_REGION || process.env.NOW_REGION; +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = region === 'dev1' ? 'development' : 'production'; +} + +if (process.env.NODE_ENV !== 'production' && region !== 'dev1') { + console.warn( + `Warning: NODE_ENV was incorrectly set to "${process.env.NODE_ENV}", this value is being overridden to "production"` + ); + process.env.NODE_ENV = 'production'; +} + +import { Server } from 'http'; +// @ts-ignore - copied to the `dist` output as-is +import { Bridge } from './now__bridge'; + +// eslint-disable-next-line +const NextServer = require('next/dist/server/next-server.js').default; +const nextServer = new NextServer({ + // @ts-ignore __NEXT_CONFIG__ value is injected + conf: __LAUNCHER_NEXT_CONFIG__, + dir: '.', + minimalMode: false, // TODO: Change to true, when proxy supports minimal mode + customServer: false, +}); +const requestHandler = nextServer.getRequestHandler(); +const server = new Server(async (req, res) => { + try { + // entryDirectory handler + await requestHandler(req, res); + } catch (err) { + console.error(err); + // crash the lambda immediately to clean up any bad module state, + // this was previously handled in now_bridge on an unhandled rejection + // but we can do this quicker by triggering here + process.exit(1); + } +}); + +const bridge = new Bridge(server); +bridge.listen(); +exports.launcher = bridge.launcher; diff --git a/packages/runtime/src/utils.ts b/packages/runtime/src/utils.ts index 56235d89..8de3defb 100644 --- a/packages/runtime/src/utils.ts +++ b/packages/runtime/src/utils.ts @@ -198,7 +198,7 @@ async function getRoutes( // If default pages dir isn't found check for `src/pages` if ( !pagesDir && - fileKeys.some(file => + fileKeys.some((file) => file.startsWith(path.join(entryDirectory, 'src/pages')) ) ) { @@ -260,7 +260,7 @@ async function getRoutes( entryDirectory, dynamicPages, true - ).then(arr => + ).then((arr) => arr.map((route: Source) => { // convert to make entire RegExp match as one group route.src = route.src @@ -287,7 +287,7 @@ async function getRoutes( }; // Only add the route if a page is not already using it - if (!routes.some(r => (r as Source).src === route.src)) { + if (!routes.some((r) => (r as Source).src === route.src)) { routes.push(route); } } @@ -432,7 +432,7 @@ export async function getDynamicRoutes( dest: `${!isDev ? path.join('/', entryDirectory, page) : page}${ routeKeys ? `?${Object.keys(routeKeys) - .map(key => `${routeKeys[key]}=$${key}`) + .map((key) => `${routeKeys[key]}=$${key}`) .join('&')}` : '' }`, @@ -491,13 +491,13 @@ export async function getDynamicRoutes( }); } - const pageMatchers = getSortedRoutes(dynamicPages).map(pageName => ({ + const pageMatchers = getSortedRoutes(dynamicPages).map((pageName) => ({ pageName, matcher: getRouteRegex && getRouteRegex(pageName).re, })); const routes: Source[] = []; - pageMatchers.forEach(pageMatcher => { + pageMatchers.forEach((pageMatcher) => { // in `vercel dev` we don't need to prefix the destination const dest = !isDev ? path.join('/', entryDirectory, pageMatcher.pageName) @@ -553,6 +553,38 @@ export async function getImagesManifest( return imagesManifest; } +type ServerFilesManifest = { + version: number; + appDir: string; + files: string[]; + ignore: string[]; + config: any; +}; + +export async function getServerFilesManifest( + entryPath: string, + outputDirectory: string +): Promise { + const pathServerFilesManifest = path.join( + entryPath, + outputDirectory, + 'required-server-files.json' + ); + + const hasServerFilesManifest = await fs + .access(pathServerFilesManifest) + .then(() => true) + .catch(() => false); + + if (!hasServerFilesManifest) { + return undefined; + } + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const serverFilesManifest: ServerFilesManifest = require(pathServerFilesManifest); + return serverFilesManifest; +} + function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) { // Remove any env vars from `removeEnv` // that are not present in the `addEnv` @@ -569,6 +601,11 @@ function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) { export const ExperimentalTraceVersion = `9.0.4-canary.1`; +/** + * Next.js version that uses default target `server` for serverless builds + */ +export const NextServerTargetVersion = '10.0.9-canary.4'; + export type PseudoLayer = { [fileName: string]: PseudoFile | PseudoSymbolicLink; }; @@ -919,7 +956,7 @@ export async function getPrerenderManifest( notFoundRoutes: [], }; - routes.forEach(route => { + routes.forEach((route) => { const { initialRevalidateSeconds, dataRoute, @@ -935,7 +972,7 @@ export async function getPrerenderManifest( }; }); - lazyRoutes.forEach(lazyRoute => { + lazyRoutes.forEach((lazyRoute) => { const { routeRegex, fallback, @@ -978,7 +1015,7 @@ export async function getPrerenderManifest( ret.notFoundRoutes.push(...manifest.notFoundRoutes); } - routes.forEach(route => { + routes.forEach((route) => { const { initialRevalidateSeconds, dataRoute, @@ -994,7 +1031,7 @@ export async function getPrerenderManifest( }; }); - lazyRoutes.forEach(lazyRoute => { + lazyRoutes.forEach((lazyRoute) => { const { routeRegex, fallback, @@ -1108,7 +1145,7 @@ export function normalizeLocalePath( // first item will be empty string from splitting at first char const pathnameParts = pathname.split('/'); - (locales || []).some(locale => { + (locales || []).some((locale) => { if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) { detectedLocale = locale; pathnameParts.splice(1, 1); diff --git a/packages/runtime/test/integration/serverless-config-async/package.json b/packages/runtime/test/integration/serverless-config-async/package.json index 7e24b623..6c393b17 100644 --- a/packages/runtime/test/integration/serverless-config-async/package.json +++ b/packages/runtime/test/integration/serverless-config-async/package.json @@ -1,7 +1,7 @@ { "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" + "next": "10.0.8", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/packages/runtime/test/integration/serverless-config-object/package.json b/packages/runtime/test/integration/serverless-config-object/package.json index 7e24b623..6c393b17 100644 --- a/packages/runtime/test/integration/serverless-config-object/package.json +++ b/packages/runtime/test/integration/serverless-config-object/package.json @@ -1,7 +1,7 @@ { "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" + "next": "10.0.8", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/packages/runtime/test/integration/serverless-config-promise/package.json b/packages/runtime/test/integration/serverless-config-promise/package.json index 7e24b623..6c393b17 100644 --- a/packages/runtime/test/integration/serverless-config-promise/package.json +++ b/packages/runtime/test/integration/serverless-config-promise/package.json @@ -1,7 +1,7 @@ { "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" + "next": "10.0.8", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/packages/runtime/test/integration/serverless-no-config-build/package.json b/packages/runtime/test/integration/serverless-no-config-build/package.json index 8a739edd..83d8da5e 100644 --- a/packages/runtime/test/integration/serverless-no-config-build/package.json +++ b/packages/runtime/test/integration/serverless-no-config-build/package.json @@ -3,8 +3,8 @@ "build": "next build && echo 'hello' > .next/world.txt" }, "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" + "next": "10.0.8", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/packages/runtime/test/integration/serverless-no-config/package.json b/packages/runtime/test/integration/serverless-no-config/package.json index 7e24b623..6c393b17 100644 --- a/packages/runtime/test/integration/serverless-no-config/package.json +++ b/packages/runtime/test/integration/serverless-no-config/package.json @@ -1,7 +1,7 @@ { "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" + "next": "10.0.8", + "react": "^16.0.0", + "react-dom": "^16.0.0" } } diff --git a/yarn.lock b/yarn.lock index 782aa599..040b14eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -792,6 +792,21 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@mapbox/node-pre-gyp@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz#f859d601a210537e27530f363028cde56e0cf962" + integrity sha512-qK1ECws8UxuPqOA8F5LFD90vyVU33W7N3hGfgsOVfrJaRVc8McC3JClTDHpeSbL9CBrOHly/4GsNPAvIgNZE+g== + dependencies: + detect-libc "^1.0.3" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.5" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@millihq/sammy@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@millihq/sammy/-/sammy-2.0.1.tgz#3200fbdb58449fd50d5df394dd9f21741650af77" @@ -1367,16 +1382,18 @@ resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.27.0.tgz#c0cfeebb0bebb56052719efa4a0ecc090a932c76" integrity sha512-DllIJQapnU2YwewIhh/4dYesmMQw3h2cFtabECc/zSJHqUbNa0eJuEkRa6DXbZvh1YPWBtYQoPV17NlDpBw1Vw== -"@vercel/nft@0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.10.0.tgz#6dc99c28abd073633467371a1855f08820460d45" - integrity sha512-0WABTmfebmYDR9GKuM9uFNr6MCayfl/zB6uXzkMsWlXrd7LN/ZI7sdbC/Ua+h4HEzJtvEMOrxLkIYYKqfeUf9A== +"@vercel/nft@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.17.0.tgz#28851fefe42fae7a116dc5e23a0a9da29929a18b" + integrity sha512-dXz4RAODHpEPl1Yfzk1UfVpZfqhY9O80FdI9sF/Hw2bXWj5022U9Z46VpdNYe8pDluSuQv/JUo/ZP4/OcsfDRw== dependencies: - acorn "^8.1.0" + "@mapbox/node-pre-gyp" "^1.0.5" + acorn "^8.3.0" acorn-class-fields "^1.0.0" + acorn-private-class-elements "^1.0.0" acorn-static-class-features "^1.0.0" bindings "^1.4.0" - estree-walker "^0.6.1" + estree-walker "2.0.2" glob "^7.1.3" graceful-fs "^4.1.15" micromatch "^4.0.2" @@ -1463,12 +1480,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== -acorn@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" - integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== - -acorn@^8.2.4: +acorn@^8.2.4, acorn@^8.3.0: version "8.5.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== @@ -1564,6 +1576,11 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + archiver-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" @@ -1593,6 +1610,14 @@ archiver@^5.2.0, archiver@^5.3.0: tar-stream "^2.2.0" zip-stream "^4.1.0" +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -2021,6 +2046,11 @@ chownr@^1.1.4: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -2132,6 +2162,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + colorette@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" @@ -2171,7 +2206,7 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= @@ -2402,7 +2437,7 @@ detect-indent@^6.0.0, detect-indent@^6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -detect-libc@^1.0.2: +detect-libc@^1.0.2, detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= @@ -2539,6 +2574,11 @@ estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== +estree-walker@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -2786,6 +2826,13 @@ fs-minipass@^1.2.7: dependencies: minipass "^2.6.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2811,6 +2858,21 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gauge@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.1.tgz#4bea07bcde3782f06dced8950e51307aa0f4a346" + integrity sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^1.0.1 || ^2.0.0" + strip-ansi "^3.0.1 || ^4.0.0" + wide-align "^1.1.2" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -3021,7 +3083,7 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-unicode@^2.0.0: +has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= @@ -4096,7 +4158,7 @@ macos-release@^2.5.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -4220,6 +4282,13 @@ minipass@^2.6.0, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" +minipass@^3.0.0: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" + integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== + dependencies: + yallist "^4.0.0" + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -4227,6 +4296,14 @@ minizlib@^1.3.3: dependencies: minipass "^2.9.0" +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -4234,7 +4311,7 @@ minizlib@^1.3.3: dependencies: minimist "^1.2.5" -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -4305,6 +4382,13 @@ node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.5: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" @@ -4349,6 +4433,13 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -4409,6 +4500,16 @@ npmlog@^4.0.2: gauge "~2.7.3" set-blocking "~2.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -4419,7 +4520,7 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -object-assign@^4.1.0: +object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -5284,7 +5385,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2": +"string-width@^1.0.1 || ^2.0.0", "string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -5292,7 +5393,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^4.0.0, string-width@^4.2.2: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5331,7 +5432,7 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: +"strip-ansi@^3.0.1 || ^4.0.0", strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= @@ -5437,6 +5538,18 @@ tar@^4: safe-buffer "^5.2.1" yallist "^3.1.1" +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -5921,6 +6034,13 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"