From f29afa9b4f67ccc0e65ab6006387d0913a2fda8a Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Thu, 17 Aug 2023 09:03:38 -0600 Subject: [PATCH] fix(nextjs): make next build package manager agnostic closes: #18653 --- .../next/src/executors/build/build.impl.ts | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/packages/next/src/executors/build/build.impl.ts b/packages/next/src/executors/build/build.impl.ts index bcb45f5644a28b..843c64f155cd45 100644 --- a/packages/next/src/executors/build/build.impl.ts +++ b/packages/next/src/executors/build/build.impl.ts @@ -1,15 +1,13 @@ import 'dotenv/config'; import { - detectPackageManager, ExecutorContext, - getPackageManagerVersion, logger, readJsonFile, workspaceRoot, writeJsonFile, } from '@nx/devkit'; import { createLockFile, createPackageJson, getLockFileName } from '@nx/js'; -import { join } from 'path'; +import { join, resolve as pathResolve } from 'path'; import { copySync, existsSync, mkdir, writeFileSync } from 'fs-extra'; import { gte } from 'semver'; import { directoryExists } from '@nx/workspace/src/utilities/fileutils'; @@ -19,9 +17,11 @@ import { updatePackageJson } from './lib/update-package-json'; import { createNextConfigFile } from './lib/create-next-config-file'; import { checkPublicDirectory } from './lib/check-project'; import { NextBuildBuilderOptions } from '../../utils/types'; -import { execSync, ExecSyncOptions } from 'child_process'; +import { ChildProcess, fork } from 'child_process'; import { createCliOptions } from '../../utils/create-cli-options'; +let childProcess: ChildProcess; + export default async function buildExecutor( options: NextBuildBuilderOptions, context: ExecutorContext @@ -49,32 +49,20 @@ export default async function buildExecutor( process.env['__NEXT_REACT_ROOT'] ||= 'true'; } - const { experimentalAppOnly, profile, debug, outputPath } = options; - // Set output path here since it can also be set via CLI // We can retrieve it inside plugins/with-nx - process.env.NX_NEXT_OUTPUT_PATH ??= outputPath; + process.env.NX_NEXT_OUTPUT_PATH ??= options.outputPath; - const args = createCliOptions({ experimentalAppOnly, profile, debug }); - const isYarnBerry = - detectPackageManager() === 'yarn' && - gte(getPackageManagerVersion('yarn', workspaceRoot), '2.0.0'); - const buildCommand = isYarnBerry - ? `yarn next build ${projectRoot}` - : 'npx next build'; - - const command = `${buildCommand} ${args.join(' ')}`; - const execSyncOptions: ExecSyncOptions = { - stdio: 'inherit', - encoding: 'utf-8', - cwd: projectRoot, - }; try { - execSync(command, execSyncOptions); + await runCliBuild(workspaceRoot, projectRoot, options); } catch (error) { - logger.error(`Error occurred while trying to run the ${command}`); + logger.error(`Error occurred while trying to run the build command`); logger.error(error); return { success: false }; + } finally { + if (childProcess) { + childProcess.kill(); + } } if (!directoryExists(options.outputPath)) { @@ -116,3 +104,39 @@ export default async function buildExecutor( } return { success: true }; } + +function runCliBuild( + workspaceRoot: string, + projectRoot: string, + options: NextBuildBuilderOptions +) { + const { experimentalAppOnly, profile, debug, outputPath } = options; + const args = createCliOptions({ experimentalAppOnly, profile, debug }); + return new Promise((resolve, reject) => { + childProcess = fork( + require.resolve('next/dist/bin/next'), + ['build', ...args], + { + cwd: pathResolve(workspaceRoot, projectRoot), + stdio: 'inherit', + env: process.env, + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +}