Skip to content

Commit f1f9890

Browse files
committed
refactor: use execFile exclusively in installPackages
All pnpm commands (i, remove, run postinstall) now use execFile instead of exec, eliminating shell interpretation entirely. No command in installPackages needs shell features.
1 parent 28a2cfd commit f1f9890

2 files changed

Lines changed: 29 additions & 16 deletions

File tree

source/__tests__/operations/installPackages.test.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ vi.mock('../../operations/exec.js', () => ({
66
execFile: vi.fn().mockResolvedValue(''),
77
}))
88

9-
const { exec, execFile } = await import('../../operations/exec.js')
9+
const { execFile } = await import('../../operations/exec.js')
1010
const { installPackages } = await import('../../operations/installPackages.js')
1111

1212
describe('installPackages', () => {
@@ -15,23 +15,23 @@ describe('installPackages', () => {
1515
})
1616

1717
describe('full mode', () => {
18-
it('runs pnpm i', async () => {
18+
it('runs pnpm i via execFile', async () => {
1919
await installPackages('/project/my_app', 'full')
2020

21-
expect(exec).toHaveBeenCalledWith('pnpm i', { cwd: '/project/my_app' })
21+
expect(execFile).toHaveBeenCalledWith('pnpm', ['i'], { cwd: '/project/my_app' })
2222
})
2323

2424
it('runs only one command', async () => {
2525
await installPackages('/project/my_app', 'full')
2626

27-
expect(exec).toHaveBeenCalledTimes(1)
27+
expect(execFile).toHaveBeenCalledTimes(1)
2828
})
2929

3030
it('ignores features argument', async () => {
3131
await installPackages('/project/my_app', 'full', ['demo', 'subgraph'])
3232

33-
expect(exec).toHaveBeenCalledTimes(1)
34-
expect(exec).toHaveBeenCalledWith('pnpm i', { cwd: '/project/my_app' })
33+
expect(execFile).toHaveBeenCalledTimes(1)
34+
expect(execFile).toHaveBeenCalledWith('pnpm', ['i'], { cwd: '/project/my_app' })
3535
})
3636
})
3737

@@ -40,8 +40,8 @@ describe('installPackages', () => {
4040
const allFeatures = Object.keys(featureDefinitions) as Array<keyof typeof featureDefinitions>
4141
await installPackages('/project/my_app', 'custom', allFeatures)
4242

43-
expect(exec).toHaveBeenCalledTimes(1)
44-
expect(exec).toHaveBeenCalledWith('pnpm i', { cwd: '/project/my_app' })
43+
expect(execFile).toHaveBeenCalledTimes(1)
44+
expect(execFile).toHaveBeenCalledWith('pnpm', ['i'], { cwd: '/project/my_app' })
4545
})
4646
})
4747

@@ -69,10 +69,7 @@ describe('installPackages', () => {
6969
if (args[0] === 'remove') {
7070
callOrder.push('remove')
7171
}
72-
return ''
73-
})
74-
vi.mocked(exec).mockImplementation(async (cmd) => {
75-
if (typeof cmd === 'string' && cmd.includes('postinstall')) {
72+
if (args[0] === 'run' && args[1] === 'postinstall') {
7673
callOrder.push('postinstall')
7774
}
7875
return ''
@@ -119,5 +116,21 @@ describe('installPackages', () => {
119116
expect(removeArgs).toContain(pkg)
120117
}
121118
})
119+
120+
it('runs postinstall via execFile', async () => {
121+
await installPackages('/project/my_app', 'custom', ['demo'])
122+
123+
expect(execFile).toHaveBeenCalledWith('pnpm', ['run', 'postinstall'], {
124+
cwd: '/project/my_app',
125+
})
126+
})
127+
})
128+
129+
it('never uses exec (shell) for any command', async () => {
130+
const { exec } = await import('../../operations/exec.js')
131+
132+
await installPackages('/project/my_app', 'custom', ['demo'])
133+
134+
expect(exec).not.toHaveBeenCalled()
122135
})
123136
})
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import type { FeatureName } from '../constants/config.js'
22
import type { InstallationType } from '../types/types.js'
33
import { getPackagesToRemove } from '../utils/utils.js'
4-
import { exec, execFile } from './exec.js'
4+
import { execFile } from './exec.js'
55

66
export async function installPackages(
77
projectFolder: string,
88
mode: InstallationType,
99
features: FeatureName[] = [],
1010
): Promise<void> {
1111
if (mode === 'full') {
12-
await exec('pnpm i', { cwd: projectFolder })
12+
await execFile('pnpm', ['i'], { cwd: projectFolder })
1313
return
1414
}
1515

1616
const packagesToRemove = getPackagesToRemove(features)
1717

1818
if (packagesToRemove.length === 0) {
19-
await exec('pnpm i', { cwd: projectFolder })
19+
await execFile('pnpm', ['i'], { cwd: projectFolder })
2020
return
2121
}
2222

2323
await execFile('pnpm', ['remove', ...packagesToRemove], { cwd: projectFolder })
24-
await exec('pnpm run postinstall', { cwd: projectFolder })
24+
await execFile('pnpm', ['run', 'postinstall'], { cwd: projectFolder })
2525
}

0 commit comments

Comments
 (0)