Skip to content

Commit 07c0b4c

Browse files
committed
feat: normalize all package manager execute commands in translateExecuteCommand
Rename translateNpxCommand to translateExecuteCommand and extend it to recognize all known execute-command formats (npx, bunx, pnpx, pnpm dlx, yarn dlx, deno run npm:*). This ensures add-on commands specified in any format are correctly translated to the user's chosen package manager.
1 parent 93228b8 commit 07c0b4c

File tree

3 files changed

+205
-6
lines changed

3 files changed

+205
-6
lines changed

packages/create/src/create-app.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { writeConfigFileToEnvironment } from './config-file.js'
66
import {
77
getPackageManagerScriptCommand,
88
packageManagerInstall,
9+
translateExecuteCommand,
910
} from './package-manager.js'
1011
import { createPackageJSON } from './package-json.js'
1112
import { createTemplateFile } from './template-file.js'
@@ -181,18 +182,19 @@ async function runCommandsAndInstallDependencies(
181182
addOn.phase === phase && addOn.command && addOn.command.command,
182183
)) {
183184
s.start(`Running commands for ${addOn.name}...`)
184-
const cmd = formatCommand({
185+
const translated = translateExecuteCommand(options.packageManager, {
185186
command: addOn.command!.command,
186187
args: addOn.command!.args || [],
187188
})
189+
const cmd = formatCommand(translated)
188190
environment.startStep({
189191
id: 'run-commands',
190192
type: 'command',
191193
message: cmd,
192194
})
193195
await environment.execute(
194-
addOn.command!.command,
195-
addOn.command!.args || [],
196+
translated.command,
197+
translated.args,
196198
options.targetDir,
197199
{ inherit: true },
198200
)
@@ -208,19 +210,20 @@ async function runCommandsAndInstallDependencies(
208210
options.starter.command.command
209211
) {
210212
s.start(`Setting up starter ${options.starter.name}...`)
211-
const cmd = formatCommand({
213+
const starterTranslated = translateExecuteCommand(options.packageManager, {
212214
command: options.starter.command.command,
213215
args: options.starter.command.args || [],
214216
})
217+
const cmd = formatCommand(starterTranslated)
215218
environment.startStep({
216219
id: 'run-starter-command',
217220
type: 'command',
218221
message: cmd,
219222
})
220223

221224
await environment.execute(
222-
options.starter.command.command,
223-
options.starter.command.args || [],
225+
starterTranslated.command,
226+
starterTranslated.args,
224227
options.targetDir,
225228
{ inherit: true },
226229
)

packages/create/src/package-manager.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,43 @@ export function packageManagerInstall(
101101
return environment.execute(command, commandArgs, cwd)
102102
}
103103

104+
export function translateExecuteCommand(
105+
packageManager: PackageManager,
106+
command: { command: string; args?: Array<string> },
107+
): { command: string; args: Array<string> } {
108+
const args = command.args || []
109+
const parsed = parseExecuteCommand(command.command, args)
110+
if (parsed) {
111+
return getPackageManagerExecuteCommand(packageManager, parsed.pkg, parsed.args)
112+
}
113+
return { command: command.command, args }
114+
}
115+
116+
function parseExecuteCommand(
117+
command: string,
118+
args: Array<string>,
119+
): { pkg: string; args: Array<string> } | null {
120+
if (command === 'npx') {
121+
const filtered = args[0] === '-y' ? args.slice(1) : args
122+
const [pkg, ...rest] = filtered
123+
return pkg ? { pkg, args: rest } : null
124+
}
125+
if (command === 'pnpx' || command === 'bunx') {
126+
const filtered = command === 'bunx' && args[0] === '--bun' ? args.slice(1) : args
127+
const [pkg, ...rest] = filtered
128+
return pkg ? { pkg, args: rest } : null
129+
}
130+
if ((command === 'pnpm' || command === 'yarn') && args[0] === 'dlx') {
131+
const [, pkg, ...rest] = args
132+
return pkg ? { pkg, args: rest } : null
133+
}
134+
if (command === 'deno' && args[0] === 'run' && args[1]?.startsWith('npm:')) {
135+
const pkg = args[1].slice(4)
136+
return pkg ? { pkg, args: args.slice(2) } : null
137+
}
138+
return null
139+
}
140+
104141
export function packageManagerExecute(
105142
environment: Environment,
106143
cwd: string,

packages/create/tests/package-manager.test.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getPackageManagerExecuteCommand,
55
getPackageManagerInstallCommand,
66
getPackageManagerScriptCommand,
7+
translateExecuteCommand,
78
} from '../src/package-manager.js'
89
import { formatCommand } from '../src/utils.js'
910

@@ -152,3 +153,161 @@ describe('getPackageManagerInstallCommand', () => {
152153
).toBe('npm install vitest -D')
153154
})
154155
})
156+
157+
describe('translateExecuteCommand', () => {
158+
it('should translate npx to bunx for bun', () => {
159+
expect(
160+
formatCommand(
161+
translateExecuteCommand('bun', {
162+
command: 'npx',
163+
args: ['shadcn', 'add', 'button'],
164+
}),
165+
),
166+
).toBe('bunx --bun shadcn add button')
167+
})
168+
it('should translate npx to pnpm dlx for pnpm', () => {
169+
expect(
170+
formatCommand(
171+
translateExecuteCommand('pnpm', {
172+
command: 'npx',
173+
args: ['shadcn', 'add', 'button'],
174+
}),
175+
),
176+
).toBe('pnpm dlx shadcn add button')
177+
})
178+
it('should translate npx to yarn dlx for yarn', () => {
179+
expect(
180+
formatCommand(
181+
translateExecuteCommand('yarn', {
182+
command: 'npx',
183+
args: ['shadcn', 'add', 'button'],
184+
}),
185+
),
186+
).toBe('yarn dlx shadcn add button')
187+
})
188+
it('should translate npx to deno run for deno', () => {
189+
expect(
190+
formatCommand(
191+
translateExecuteCommand('deno', {
192+
command: 'npx',
193+
args: ['shadcn', 'add', 'button'],
194+
}),
195+
),
196+
).toBe('deno run npm:shadcn add button')
197+
})
198+
it('should keep npx -y for npm', () => {
199+
expect(
200+
formatCommand(
201+
translateExecuteCommand('npm', {
202+
command: 'npx',
203+
args: ['shadcn', 'add', 'button'],
204+
}),
205+
),
206+
).toBe('npx -y shadcn add button')
207+
})
208+
it('should pass through non-npx commands unchanged', () => {
209+
expect(
210+
formatCommand(
211+
translateExecuteCommand('bun', {
212+
command: 'node',
213+
args: ['script.js'],
214+
}),
215+
),
216+
).toBe('node script.js')
217+
})
218+
it('should handle missing args gracefully', () => {
219+
expect(
220+
formatCommand(translateExecuteCommand('bun', { command: 'npx' })),
221+
).toBe('npx')
222+
})
223+
it('should strip -y flag from npx input', () => {
224+
expect(
225+
formatCommand(
226+
translateExecuteCommand('pnpm', {
227+
command: 'npx',
228+
args: ['-y', 'shadcn', 'add', 'button'],
229+
}),
230+
),
231+
).toBe('pnpm dlx shadcn add button')
232+
})
233+
it('should translate bunx to target package manager', () => {
234+
expect(
235+
formatCommand(
236+
translateExecuteCommand('pnpm', {
237+
command: 'bunx',
238+
args: ['shadcn', 'add', 'button'],
239+
}),
240+
),
241+
).toBe('pnpm dlx shadcn add button')
242+
})
243+
it('should strip --bun flag from bunx input', () => {
244+
expect(
245+
formatCommand(
246+
translateExecuteCommand('pnpm', {
247+
command: 'bunx',
248+
args: ['--bun', 'shadcn', 'add', 'button'],
249+
}),
250+
),
251+
).toBe('pnpm dlx shadcn add button')
252+
})
253+
it('should translate pnpx to target package manager', () => {
254+
expect(
255+
formatCommand(
256+
translateExecuteCommand('yarn', {
257+
command: 'pnpx',
258+
args: ['shadcn', 'add', 'button'],
259+
}),
260+
),
261+
).toBe('yarn dlx shadcn add button')
262+
})
263+
it('should translate pnpm dlx to target package manager', () => {
264+
expect(
265+
formatCommand(
266+
translateExecuteCommand('bun', {
267+
command: 'pnpm',
268+
args: ['dlx', 'shadcn', 'add', 'button'],
269+
}),
270+
),
271+
).toBe('bunx --bun shadcn add button')
272+
})
273+
it('should translate yarn dlx to target package manager', () => {
274+
expect(
275+
formatCommand(
276+
translateExecuteCommand('npm', {
277+
command: 'yarn',
278+
args: ['dlx', 'shadcn', 'add', 'button'],
279+
}),
280+
),
281+
).toBe('npx -y shadcn add button')
282+
})
283+
it('should translate deno run npm: to target package manager', () => {
284+
expect(
285+
formatCommand(
286+
translateExecuteCommand('pnpm', {
287+
command: 'deno',
288+
args: ['run', 'npm:shadcn', 'add', 'button'],
289+
}),
290+
),
291+
).toBe('pnpm dlx shadcn add button')
292+
})
293+
it('should pass through non-execute pnpm commands unchanged', () => {
294+
expect(
295+
formatCommand(
296+
translateExecuteCommand('bun', {
297+
command: 'pnpm',
298+
args: ['install'],
299+
}),
300+
),
301+
).toBe('pnpm install')
302+
})
303+
it('should pass through non-execute deno commands unchanged', () => {
304+
expect(
305+
formatCommand(
306+
translateExecuteCommand('npm', {
307+
command: 'deno',
308+
args: ['task', 'dev'],
309+
}),
310+
),
311+
).toBe('deno task dev')
312+
})
313+
})

0 commit comments

Comments
 (0)