Skip to content

Commit 39aa198

Browse files
thilllonzkochan
authored andcommitted
feat(approve-builds): add --all flag to skip interactive prompts (#10619)
Allow approving all pending build dependencies at once without interactive selection, useful for CI/CD pipelines and project bootstrapping scenarios where interactive prompts are not feasible. close #10136
1 parent 229c244 commit 39aa198

File tree

3 files changed

+106
-48
lines changed

3 files changed

+106
-48
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pnpm/exec.build-commands": minor
3+
"pnpm": minor
4+
---
5+
6+
Added `--all` flag to `pnpm approve-builds` that approves all pending builds without interactive prompts [#10136](https://github.com/pnpm/pnpm/issues/10136).

exec/build-commands/src/approveBuilds.ts

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { rebuild, type RebuildCommandOpts } from '@pnpm/plugin-commands-rebuild'
1010
import { writeSettings } from '@pnpm/config.config-writer'
1111
import { getAutomaticallyIgnoredBuilds } from './getAutomaticallyIgnoredBuilds.js'
1212

13-
export type ApproveBuildsCommandOpts = Pick<Config, 'modulesDir' | 'dir' | 'rootProjectManifest' | 'rootProjectManifestDir' | 'onlyBuiltDependencies' | 'ignoredBuiltDependencies'>
13+
export type ApproveBuildsCommandOpts = Pick<Config, 'modulesDir' | 'dir' | 'rootProjectManifest' | 'rootProjectManifestDir' | 'onlyBuiltDependencies' | 'ignoredBuiltDependencies'> & { all?: boolean }
1414

1515
export const commandNames = ['approve-builds']
1616

@@ -23,6 +23,10 @@ export function help (): string {
2323
title: 'Options',
2424

2525
list: [
26+
{
27+
description: 'Approve all pending dependencies without interactive prompts',
28+
name: '--all',
29+
},
2630
{
2731
description: 'Approve dependencies of global packages',
2832
name: '--global',
@@ -36,6 +40,7 @@ export function help (): string {
3640

3741
export function cliOptionsTypes (): Record<string, unknown> {
3842
return {
43+
all: Boolean,
3944
global: Boolean,
4045
}
4146
}
@@ -54,43 +59,48 @@ export async function handler (opts: ApproveBuildsCommandOpts & RebuildCommandOp
5459
globalInfo('There are no packages awaiting approval')
5560
return
5661
}
57-
const { result } = await prompt({
58-
choices: sortUniqueStrings([...automaticallyIgnoredBuilds]),
59-
indicator (state: any, choice: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
60-
return ` ${choice.enabled ? '●' : '○'}`
61-
},
62-
message: 'Choose which packages to build ' +
63-
`(Press ${chalk.cyan('<space>')} to select, ` +
64-
`${chalk.cyan('<a>')} to toggle all, ` +
65-
`${chalk.cyan('<i>')} to invert selection)`,
66-
name: 'result',
67-
pointer: '❯',
68-
result () {
69-
return this.selected
70-
},
71-
styles: {
72-
dark: chalk.reset,
73-
em: chalk.bgBlack.whiteBright,
74-
success: chalk.reset,
75-
},
76-
type: 'multiselect',
62+
let buildPackages: string[] = []
63+
if (opts.all) {
64+
buildPackages = sortUniqueStrings([...automaticallyIgnoredBuilds])
65+
} else {
66+
const { result } = await prompt({
67+
choices: sortUniqueStrings([...automaticallyIgnoredBuilds]),
68+
indicator (state: any, choice: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
69+
return ` ${choice.enabled ? '●' : '○'}`
70+
},
71+
message: 'Choose which packages to build ' +
72+
`(Press ${chalk.cyan('<space>')} to select, ` +
73+
`${chalk.cyan('<a>')} to toggle all, ` +
74+
`${chalk.cyan('<i>')} to invert selection)`,
75+
name: 'result',
76+
pointer: '❯',
77+
result () {
78+
return this.selected
79+
},
80+
styles: {
81+
dark: chalk.reset,
82+
em: chalk.bgBlack.whiteBright,
83+
success: chalk.reset,
84+
},
85+
type: 'multiselect',
7786

78-
// For Vim users (related: https://github.com/enquirer/enquirer/pull/163)
79-
j () {
80-
return this.down()
81-
},
82-
k () {
83-
return this.up()
84-
},
85-
cancel () {
86-
// By default, canceling the prompt via Ctrl+c throws an empty string.
87-
// The custom cancel function prevents that behavior.
88-
// Otherwise, pnpm CLI would print an error and confuse users.
89-
// See related issue: https://github.com/enquirer/enquirer/issues/225
90-
process.exit(0)
91-
},
92-
} as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any
93-
const buildPackages = result.map(({ value }: { value: string }) => value)
87+
// For Vim users (related: https://github.com/enquirer/enquirer/pull/163)
88+
j () {
89+
return this.down()
90+
},
91+
k () {
92+
return this.up()
93+
},
94+
cancel () {
95+
// By default, canceling the prompt via Ctrl+c throws an empty string.
96+
// The custom cancel function prevents that behavior.
97+
// Otherwise, pnpm CLI would print an error and confuse users.
98+
// See related issue: https://github.com/enquirer/enquirer/issues/225
99+
process.exit(0)
100+
},
101+
} as any) as any // eslint-disable-line @typescript-eslint/no-explicit-any
102+
buildPackages = result.map(({ value }: { value: string }) => value)
103+
}
94104
const ignoredPackages = automaticallyIgnoredBuilds.filter((automaticallyIgnoredBuild) => !buildPackages.includes(automaticallyIgnoredBuild))
95105
const updatedSettings: PnpmSettings = {}
96106
if (ignoredPackages.length) {
@@ -113,19 +123,21 @@ export async function handler (opts: ApproveBuildsCommandOpts & RebuildCommandOp
113123
])
114124
}
115125
}
116-
if (buildPackages.length) {
117-
const confirmed = await prompt<{ build: boolean }>({
118-
type: 'confirm',
119-
name: 'build',
120-
message: `The next packages will now be built: ${buildPackages.join(', ')}.
126+
if (!opts.all) {
127+
if (buildPackages.length) {
128+
const confirmed = await prompt<{ build: boolean }>({
129+
type: 'confirm',
130+
name: 'build',
131+
message: `The next packages will now be built: ${buildPackages.join(', ')}.
121132
Do you approve?`,
122-
initial: false,
123-
})
124-
if (!confirmed.build) {
125-
return
133+
initial: false,
134+
})
135+
if (!confirmed.build) {
136+
return
137+
}
138+
} else {
139+
globalInfo('All packages were added to ignoredBuiltDependencies.')
126140
}
127-
} else {
128-
globalInfo('All packages were added to ignoredBuiltDependencies.')
129141
}
130142
await writeSettings({
131143
...opts,

exec/build-commands/test/approveBuilds.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,43 @@ test('should approve builds with package.json that has no onlyBuiltDependencies
232232
},
233233
})
234234
})
235+
236+
test('approve all builds with --all flag', async () => {
237+
prepare({
238+
dependencies: {
239+
'@pnpm.e2e/pre-and-postinstall-scripts-example': '1.0.0',
240+
'@pnpm.e2e/install-script-example': '*',
241+
},
242+
})
243+
244+
const cliOptions = {
245+
argv: [],
246+
dir: process.cwd(),
247+
registry: `http://localhost:${REGISTRY_MOCK_PORT}`,
248+
}
249+
const config = {
250+
...omit(['reporter'], (await getConfig({
251+
cliOptions,
252+
packageManager: { name: 'pnpm', version: '' },
253+
})).config),
254+
storeDir: path.resolve('store'),
255+
cacheDir: path.resolve('cache'),
256+
pnpmfile: [],
257+
}
258+
await install.handler({ ...config, argv: { original: [] } })
259+
260+
prompt.mockClear()
261+
await approveBuilds.handler({ ...config, all: true })
262+
263+
expect(prompt).not.toHaveBeenCalled()
264+
265+
const workspaceManifest = readYamlFile<any>(path.resolve('pnpm-workspace.yaml')) // eslint-disable-line
266+
expect(workspaceManifest.allowBuilds).toStrictEqual({
267+
'@pnpm.e2e/install-script-example': true,
268+
'@pnpm.e2e/pre-and-postinstall-scripts-example': true,
269+
})
270+
271+
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-preinstall.js')).toBeTruthy()
272+
expect(fs.existsSync('node_modules/@pnpm.e2e/pre-and-postinstall-scripts-example/generated-by-postinstall.js')).toBeTruthy()
273+
expect(fs.existsSync('node_modules/@pnpm.e2e/install-script-example/generated-by-install.js')).toBeTruthy()
274+
})

0 commit comments

Comments
 (0)