diff --git a/helpers/doesNotNeedPlugin.js b/helpers/doesNotNeedPlugin.js index a2898f9653..c4f52a7a5b 100644 --- a/helpers/doesNotNeedPlugin.js +++ b/helpers/doesNotNeedPlugin.js @@ -1,8 +1,9 @@ // Checks all the cases for which the plugin should do nothing -const { redBright } = require('chalk') +const { redBright, yellowBright } = require('chalk') const doesSiteUseNextOnNetlify = require('./doesSiteUseNextOnNetlify') const isStaticExportProject = require('./isStaticExportProject') +const usesBuildCommand = require('./usesBuildCommand') const doesNotNeedPlugin = ({ netlifyConfig, packageJson }) => { const { build } = netlifyConfig @@ -15,6 +16,13 @@ const doesNotNeedPlugin = ({ netlifyConfig, packageJson }) => { return true } + if (usesBuildCommand({ build, scripts, command: 'build-storybook' })) { + console.log( + yellowBright`Site seems to be building a Storybook rather than the Next.js site, so the Essential Next.js plugin will not run.`, + ) + return true + } + return isStaticExportProject({ build, scripts }) || doesSiteUseNextOnNetlify({ packageJson }) } diff --git a/helpers/isStaticExportProject.js b/helpers/isStaticExportProject.js index f5a1a12d26..ff705297b9 100644 --- a/helpers/isStaticExportProject.js +++ b/helpers/isStaticExportProject.js @@ -1,19 +1,12 @@ +const usesBuildCommand = require('./usesBuildCommand') + // Takes 1. Netlify config's build details and // 2. the project's package.json scripts to determine if // the Next.js app uses static HTML export const isStaticExportProject = ({ build, scripts }) => { const NEXT_EXPORT_COMMAND = 'next export' - if (!build.command) return false - - const isSetInNetlifyConfig = build.command.includes(NEXT_EXPORT_COMMAND) - - const isSetInNpmScript = Object.keys(scripts).find((script) => { - const scriptValue = scripts[script] - return build.command.includes(script) && scriptValue.includes(NEXT_EXPORT_COMMAND) - }) - - const isStaticExport = isSetInNetlifyConfig || isSetInNpmScript + const isStaticExport = usesBuildCommand({ build, scripts, command: NEXT_EXPORT_COMMAND }) if (isStaticExport) { console.log( diff --git a/helpers/usesBuildCommand.js b/helpers/usesBuildCommand.js new file mode 100644 index 0000000000..e0fe4f1ff4 --- /dev/null +++ b/helpers/usesBuildCommand.js @@ -0,0 +1,15 @@ +// Does the build command include this value, either directly or via an npm script? +const usesBuildCommand = ({ build, scripts, command }) => { + if (!build.command) return false + + if (build.command.includes(command)) { + return true + } + + return Object.entries(scripts).some( + // Search for a npm script that is being called by the build command, and includes the searched-for command + ([scriptName, scriptValue]) => build.command.includes(scriptName) && scriptValue.includes(command), + ) +} + +module.exports = usesBuildCommand diff --git a/index.js b/index.js index b85fded5c1..9e359dc074 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -const { readdirSync } = require('fs') +const { readdirSync, existsSync } = require('fs') const path = require('path') const makeDir = require('make-dir') @@ -71,6 +71,19 @@ module.exports = { if (doesNotNeedPlugin({ netlifyConfig, packageJson, failBuild })) { return } + console.log('Detected Next.js site. Copying files...') + + const { distDir } = await getNextConfig(failBuild, nextRoot) + + const dist = path.resolve(nextRoot, distDir) + if (!existsSync(dist)) { + failBuild(` +Could not find "${distDir}" after building the site, which indicates that "next build" was not run. +Check that your build command includes "next build". If the site is a monorepo, see https://ntl.fyi/next-monorepo +for information on configuring the site. If this is not a Next.js site you should remove the Essential Next.js plugin. +See https://ntl.fyi/remove-plugin for instructions. + `) + } console.log(`** Running Next on Netlify package **`) @@ -87,7 +100,7 @@ module.exports = { utils.status.show({ title: 'Essential Next.js Build Plugin did not run', summary: netlifyConfig.build.command - ? 'The site either uses static export, or manually runs next-on-netlify' + ? 'The site either uses static export, manually runs next-on-netlify, or is not a Next.js site' : 'The site config does not specify a build command', }) return diff --git a/test/index.js b/test/index.js index b857036dd8..c124088863 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,5 @@ +/* eslint-disable max-lines */ +/* eslint-disable max-lines-per-function */ const path = require('path') const process = require('process') @@ -156,6 +158,33 @@ describe('preBuild()', () => { expect(process.env.NEXT_PRIVATE_TARGET).toBeUndefined() }) + test('do nothing if build command includes "build-storybook"', async () => { + await plugin.onPreBuild({ + netlifyConfig, + packageJson: { ...DUMMY_PACKAGE_JSON, scripts: { build: 'build-storybook' } }, + utils, + }) + expect(process.env.NEXT_PRIVATE_TARGET).toBeUndefined() + }) + + test('do nothing if build command calls a script that includes "build-storybook"', async () => { + await plugin.onPreBuild({ + netlifyConfig: { build: { command: 'npm run storybook' } }, + packageJson: { ...DUMMY_PACKAGE_JSON, scripts: { storybook: 'build-storybook' } }, + utils, + }) + expect(process.env.NEXT_PRIVATE_TARGET).toBeUndefined() + }) + + test('run plugin if app has build-storybook in an unused script', async () => { + await plugin.onPreBuild({ + netlifyConfig, + packageJson: { ...DUMMY_PACKAGE_JSON, scripts: { storybook: 'build-storybook' } }, + utils, + }) + expect(process.env.NEXT_PRIVATE_TARGET).toBe('serverless') + }) + test('fail build if the app has no package.json', async () => { await expect( plugin.onPreBuild({ @@ -289,3 +318,6 @@ describe('onPostBuild', () => { expect(path.normalize(manifestPath.digests[0])).toBe(path.normalize('build/build-manifest.json')) }) }) + +/* eslint-enable max-lines */ +/* eslint-enable max-lines-per-function */