diff --git a/.changeset/approve-builds-all-flag.md b/.changeset/approve-builds-all-flag.md new file mode 100644 index 0000000000..070909ad7e --- /dev/null +++ b/.changeset/approve-builds-all-flag.md @@ -0,0 +1,6 @@ +--- +"@pnpm/exec.build-commands": minor +"pnpm": minor +--- + +Added `--all` flag to `pnpm approve-builds` that approves all pending builds without interactive prompts [#10136](https://github.com/pnpm/pnpm/issues/10136). diff --git a/exec/build-commands/src/approveBuilds.ts b/exec/build-commands/src/approveBuilds.ts index c4ff6f1b0b..6c73bb14f3 100644 --- a/exec/build-commands/src/approveBuilds.ts +++ b/exec/build-commands/src/approveBuilds.ts @@ -9,7 +9,7 @@ import { rebuild, type RebuildCommandOpts } from '@pnpm/plugin-commands-rebuild' import { writeSettings } from '@pnpm/config.config-writer' import { getAutomaticallyIgnoredBuilds } from './getAutomaticallyIgnoredBuilds.js' -export type ApproveBuildsCommandOpts = Pick +export type ApproveBuildsCommandOpts = Pick & { all?: boolean } export const commandNames = ['approve-builds'] @@ -22,6 +22,10 @@ export function help (): string { title: 'Options', list: [ + { + description: 'Approve all pending dependencies without interactive prompts', + name: '--all', + }, { description: 'Approve dependencies of global packages', name: '--global', @@ -35,6 +39,7 @@ export function help (): string { export function cliOptionsTypes (): Record { return { + all: Boolean, global: Boolean, } } @@ -53,43 +58,48 @@ export async function handler (opts: ApproveBuildsCommandOpts & RebuildCommandOp globalInfo('There are no packages awaiting approval') return } - const { result } = await enquirer.prompt({ - choices: sortUniqueStrings([...automaticallyIgnoredBuilds]), - indicator (state: any, choice: any) { // eslint-disable-line @typescript-eslint/no-explicit-any - return ` ${choice.enabled ? '●' : '○'}` - }, - message: 'Choose which packages to build ' + - `(Press ${chalk.cyan('')} to select, ` + - `${chalk.cyan('')} to toggle all, ` + - `${chalk.cyan('')} to invert selection)`, - name: 'result', - pointer: '❯', - result () { - return this.selected - }, - styles: { - dark: chalk.reset, - em: chalk.bgBlack.whiteBright, - success: chalk.reset, - }, - type: 'multiselect', + let buildPackages: string[] = [] + if (opts.all) { + buildPackages = sortUniqueStrings([...automaticallyIgnoredBuilds]) + } else { + const { result } = await enquirer.prompt({ + choices: sortUniqueStrings([...automaticallyIgnoredBuilds]), + indicator (state: any, choice: any) { // eslint-disable-line @typescript-eslint/no-explicit-any + return ` ${choice.enabled ? '●' : '○'}` + }, + message: 'Choose which packages to build ' + + `(Press ${chalk.cyan('')} to select, ` + + `${chalk.cyan('')} to toggle all, ` + + `${chalk.cyan('')} to invert selection)`, + name: 'result', + pointer: '❯', + result () { + return this.selected + }, + styles: { + dark: chalk.reset, + em: chalk.bgBlack.whiteBright, + success: chalk.reset, + }, + type: 'multiselect', - // For Vim users (related: https://github.com/enquirer/enquirer/pull/163) - j () { - return this.down() - }, - k () { - return this.up() - }, - cancel () { - // By default, canceling the prompt via Ctrl+c throws an empty string. - // The custom cancel function prevents that behavior. - // Otherwise, pnpm CLI would print an error and confuse users. - // See related issue: https://github.com/enquirer/enquirer/issues/225 - process.exit(0) - }, - } as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any - const buildPackages = result.map(({ value }: { value: string }) => value) + // For Vim users (related: https://github.com/enquirer/enquirer/pull/163) + j () { + return this.down() + }, + k () { + return this.up() + }, + cancel () { + // By default, canceling the prompt via Ctrl+c throws an empty string. + // The custom cancel function prevents that behavior. + // Otherwise, pnpm CLI would print an error and confuse users. + // See related issue: https://github.com/enquirer/enquirer/issues/225 + process.exit(0) + }, + } as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any + buildPackages = result.map(({ value }: { value: string }) => value) + } const ignoredPackages = automaticallyIgnoredBuilds.filter((automaticallyIgnoredBuild) => !buildPackages.includes(automaticallyIgnoredBuild)) const allowBuilds: Record = { ...opts.allowBuilds } if (ignoredPackages.length) { @@ -102,19 +112,21 @@ export async function handler (opts: ApproveBuildsCommandOpts & RebuildCommandOp allowBuilds[pkg] = true } } - if (buildPackages.length) { - const confirmed = await enquirer.prompt<{ build: boolean }>({ - type: 'confirm', - name: 'build', - message: `The next packages will now be built: ${buildPackages.join(', ')}. + if (!opts.all) { + if (buildPackages.length) { + const confirmed = await enquirer.prompt<{ build: boolean }>({ + type: 'confirm', + name: 'build', + message: `The next packages will now be built: ${buildPackages.join(', ')}. Do you approve?`, - initial: false, - }) - if (!confirmed.build) { - return + initial: false, + }) + if (!confirmed.build) { + return + } + } else { + globalInfo('All packages were added to allowBuilds with value false.') } - } else { - globalInfo('All packages were added to allowBuilds with value false.') } await writeSettings({ ...opts, diff --git a/exec/build-commands/test/approveBuilds.test.ts b/exec/build-commands/test/approveBuilds.test.ts index 0335d07734..da69b92de1 100644 --- a/exec/build-commands/test/approveBuilds.test.ts +++ b/exec/build-commands/test/approveBuilds.test.ts @@ -176,6 +176,48 @@ test('should approve builds with package.json that has no allowBuilds field defi }) }) +test('approve all builds with --all flag', async () => { + prepare({ + dependencies: { + '@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0', + '@pnpm.e2e/install-script-example': '*', + }, + }) + + const cliOptions = { + argv: [], + dir: process.cwd(), + registry: `http://localhost:${REGISTRY_MOCK_PORT}`, + } + const config = { + ...omit(['reporter'], (await getConfig({ + cliOptions, + packageManager: { name: 'pnpm', version: '' }, + })).config), + storeDir: path.resolve('store'), + cacheDir: path.resolve('cache'), + pnpmfile: [], + enableGlobalVirtualStore: false, + strictDepBuilds: false, + } + await install.handler({ ...config, argv: { original: [] } }) + + prompt.mockClear() + await approveBuilds.handler({ ...config, all: true }) + + expect(prompt).not.toHaveBeenCalled() + + const workspaceManifest = readYamlFile(path.resolve('pnpm-workspace.yaml')) // eslint-disable-line + expect(workspaceManifest.allowBuilds).toStrictEqual({ + '@pnpm.e2e/install-script-example': true, + '@pnpm.e2e/pre-and-postinstall-scripts-example': true, + }) + + expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy() + expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy() + expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy() +}) + test('should retain existing allowBuilds entries when approving builds', async () => { const temp = tempDir()