diff --git a/src/commands/build/build.js b/src/commands/build/build.js index ae8b6d3e46e..8da0da38664 100644 --- a/src/commands/build/build.js +++ b/src/commands/build/build.js @@ -7,11 +7,7 @@ const { error, exit, generateNetlifyGraphJWT, getEnvelopeEnv, getToken, normaliz /** * @param {import('../../lib/build').BuildConfig} options */ -const checkOptions = ({ cachedConfig: { siteInfo = {} }, token }) => { - if (!siteInfo.id) { - error('Could not find the site ID. Please run netlify link.') - } - +const checkOptions = ({ token }) => { if (!token) { error('Could not find the access token. Please run netlify login.') } @@ -72,7 +68,7 @@ const build = async (options, command) => { await injectEnv(command, { api, buildOptions, context, site, siteInfo }) } - const { exitCode } = await runBuild(buildOptions) + const { exitCode } = await runBuild(buildOptions, command, options) exit(exitCode) } diff --git a/src/commands/deploy/deploy.js b/src/commands/deploy/deploy.js index 3785c4a1348..ea971ab7ad6 100644 --- a/src/commands/deploy/deploy.js +++ b/src/commands/deploy/deploy.js @@ -375,10 +375,11 @@ const runDeploy = async ({ * * @param {object} config * @param {*} config.cachedConfig + * @param {*} config.command * @param {import('commander').OptionValues} config.options The options of the command * @returns */ -const handleBuild = async ({ cachedConfig, options }) => { +const handleBuild = async ({ cachedConfig, command, options }) => { if (!options.build) { return {} } @@ -388,7 +389,7 @@ const handleBuild = async ({ cachedConfig, options }) => { token, options, }) - const { configMutations, exitCode, newConfig } = await runBuild(resolvedOptions) + const { configMutations, exitCode, newConfig } = await runBuild(resolvedOptions, command, options) if (exitCode !== 0) { exit(exitCode) } @@ -573,6 +574,7 @@ const deploy = async (options, command) => { const { newConfig, configMutations = [] } = await handleBuild({ cachedConfig: command.netlify.cachedConfig, + command, options, }) const config = newConfig || command.netlify.config diff --git a/src/lib/build.js b/src/lib/build.js index ece2cf216cf..e1c9189af71 100644 --- a/src/lib/build.js +++ b/src/lib/build.js @@ -3,6 +3,8 @@ const process = require('process') const netlifyBuildPromise = import('@netlify/build') +const { NETLIFYDEVERR, detectServerSettings, error, log } = require('../utils') + /** * The buildConfig + a missing cachedConfig * @typedef BuildConfig @@ -41,11 +43,20 @@ const getBuildOptions = ({ cachedConfig, options: { context, cwd, debug, dry, js /** * run the build command - * @param {BuildConfig} options + * @param {BuildConfig} buildOptions + * @param {import('../commands/base-command').BaseCommand} command + * @param {import('commander').OptionValues} commandOptions * @returns */ -const runBuild = async (options) => { +const runBuild = async (buildOptions, command, commandOptions) => { const { default: build } = await netlifyBuildPromise + const { cachedConfig, config, site } = command.netlify + const devConfig = { + framework: '#auto', + ...(config.functionsDirectory && { functions: config.functionsDirectory }), + ...config.dev, + ...commandOptions, + } // If netlify NETLIFY_API_URL is set we need to pass this information to @netlify/build // TODO don't use testOpts, but add real properties to do this. @@ -55,10 +66,52 @@ const runBuild = async (options) => { scheme: apiUrl.protocol.slice(0, -1), host: apiUrl.host, } - options = { ...options, testOpts } + buildOptions = { ...buildOptions, testOpts } + } + + /** @type {Partial} */ + let settings = {} + try { + settings = await detectServerSettings(devConfig, commandOptions, site.root) + + const defaultConfig = { build: {} } + + if (settings.buildCommand && settings.dist) { + buildOptions.cachedConfig.config.build.command = settings.buildCommand + defaultConfig.build.command = settings.buildCommand + buildOptions.cachedConfig.config.build.publish = settings.buildCommand + defaultConfig.build.publish = settings.dist + } + + if (defaultConfig.build.command && defaultConfig.build.publish) { + buildOptions.defaultConfig = defaultConfig + } + + // If there are plugins that we should be running for this site, add them + // to the config as if they were declared in netlify.toml. We must check + // whether the plugin has already been added by another source (like the + // TOML file or the UI), as we don't want to run the same plugin twice. + if (settings.plugins) { + const { plugins: existingPlugins = [] } = cachedConfig.config + const existingPluginNames = new Set(existingPlugins.map((plugin) => plugin.package)) + const newPlugins = settings.plugins + .map((pluginName) => { + if (existingPluginNames.has(pluginName)) { + return + } + + return { package: pluginName, origin: 'config', inputs: {} } + }) + .filter(Boolean) + + buildOptions.cachedConfig.config.plugins = [...newPlugins, ...cachedConfig.config.plugins] + } + } catch (detectServerSettingsError) { + log(NETLIFYDEVERR, detectServerSettingsError.message) + error(detectServerSettingsError) } - const { configMutations, netlifyConfig: newConfig, severityCode: exitCode } = await build(options) + const { configMutations, netlifyConfig: newConfig, severityCode: exitCode } = await build(buildOptions) return { exitCode, newConfig, configMutations } } diff --git a/src/utils/detect-server-settings.js b/src/utils/detect-server-settings.js index b7d9f77a46f..b17e1c801b3 100644 --- a/src/utils/detect-server-settings.js +++ b/src/utils/detect-server-settings.js @@ -158,7 +158,10 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => { */ const getSettingsFromFramework = (framework) => { const { - build: { directory: dist }, + build: { + directory: dist, + commands: [buildCommand], + }, dev: { commands: [command], port: frameworkPort, @@ -172,6 +175,7 @@ const getSettingsFromFramework = (framework) => { return { command, + buildCommand, frameworkPort, dist: staticDir || dist, framework: frameworkName, @@ -250,6 +254,7 @@ const handleCustomFramework = ({ devConfig }) => { const mergeSettings = async ({ devConfig, frameworkSettings = {} }) => { const { command: frameworkCommand, + buildCommand, frameworkPort: frameworkDetectedPort, dist, framework, @@ -263,6 +268,7 @@ const mergeSettings = async ({ devConfig, frameworkSettings = {} }) => { const useStaticServer = !(command && frameworkPort) return { command, + buildCommand, frameworkPort: useStaticServer ? await getStaticServerPort({ devConfig }) : frameworkPort, dist: devConfig.publish || dist || getDefaultDist(), framework, diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts index e1078e4b489..99fed6b82b7 100644 --- a/src/utils/types.d.ts +++ b/src/utils/types.d.ts @@ -3,6 +3,7 @@ export type FrameworkNames = '#static' | '#auto' | '#custom' | string export type FrameworkInfo = { build: { directory: string + commands: string[] } dev: { commands: string[] @@ -32,6 +33,8 @@ export type BaseServerSettings = { env?: NodeJS.ProcessEnv pollingStrategies?: string[] plugins?: string[] + /** The command that was provided for the dev config */ + buildCommand?: string } export type ServerSettings = BaseServerSettings & { diff --git a/tests/integration/110.command.build.test.js b/tests/integration/110.command.build.test.js index a1d38c04f92..5cff240f4be 100644 --- a/tests/integration/110.command.build.test.js +++ b/tests/integration/110.command.build.test.js @@ -241,23 +241,6 @@ test('should error when using invalid netlify.toml', async (t) => { }) }) -test('should error when a site id is missing', async (t) => { - await withSiteBuilder('no-site-id-site', async (builder) => { - builder.withNetlifyToml({ config: { build: { command: 'echo testCommand' } } }) - - await builder.buildAsync() - - await withMockApi(routes, async ({ apiUrl }) => { - await runBuildCommand(t, builder.directory, { - apiUrl, - exitCode: 1, - output: 'Could not find the site ID', - env: { ...defaultEnvs, NETLIFY_SITE_ID: '' }, - }) - }) - }) -}) - test('should not require a linked site when offline flag is set', async (t) => { await withSiteBuilder('success-site', async (builder) => { await builder.withNetlifyToml({ config: { build: { command: 'echo testCommand' } } }).buildAsync() @@ -285,3 +268,27 @@ test('should not send network requests when offline flag is set', async (t) => { }) }) }) + +test('should run without site id', async (t) => { + await withSiteBuilder('success-site', async (builder) => { + builder.withNetlifyToml({ config: { build: { command: 'echo testCommand' } } }) + + await builder.buildAsync() + await withMockApi(routesWithCommand, async ({ apiUrl }) => { + await runBuildCommand(t, builder.directory, { apiUrl, output: 'testCommand' }) + }) + }) +}) + +test('should add plugin if framework is detected', async (t) => { + await withSiteBuilder('success-site', async (builder) => { + builder.withPackageJson({ packageJson: { dependencies: { next: '^12.2.0' }, scripts: { build: 'next build' } } }) + + await builder.buildAsync() + + await withMockApi(routes, async ({ apiUrl }) => { + // Error expected as this isn't a real next app + await runBuildCommand(t, builder.directory, { apiUrl, output: '@netlify/plugin-nextjs', exitCode: 2 }) + }) + }) +})