diff --git a/.changeset/storybook-pnpm-allow-build.md b/.changeset/storybook-pnpm-allow-build.md new file mode 100644 index 00000000..f681a269 --- /dev/null +++ b/.changeset/storybook-pnpm-allow-build.md @@ -0,0 +1,5 @@ +--- +'@tanstack/create': patch +--- + +Pass `--allow-build=esbuild` to `pnpm dlx` when running the Storybook add-on's `storybook init` command, so pnpm 10+ no longer stalls on a hidden interactive build-approval prompt during `tanstack create`. Add-on and starter `command` definitions can now declare `allowBuilds` to request the same treatment. diff --git a/packages/create/src/create-app.ts b/packages/create/src/create-app.ts index e72647a1..c6f8d392 100644 --- a/packages/create/src/create-app.ts +++ b/packages/create/src/create-app.ts @@ -233,6 +233,7 @@ async function runCommandsAndInstallDependencies( const translated = translateExecuteCommand(options.packageManager, { command: addOn.command!.command, args: addOn.command!.args || [], + allowBuilds: addOn.command!.allowBuilds, }) const cmd = formatCommand(translated) environment.startStep({ @@ -261,6 +262,7 @@ async function runCommandsAndInstallDependencies( const starterTranslated = translateExecuteCommand(options.packageManager, { command: options.starter.command.command, args: options.starter.command.args || [], + allowBuilds: options.starter.command.allowBuilds, }) const cmd = formatCommand(starterTranslated) environment.startStep({ diff --git a/packages/create/src/edge-create-app.ts b/packages/create/src/edge-create-app.ts index 4586262a..326cca80 100644 --- a/packages/create/src/edge-create-app.ts +++ b/packages/create/src/edge-create-app.ts @@ -354,6 +354,7 @@ async function runCommandsAndInstallDependencies( const translated = translateExecuteCommand(options.packageManager, { command: addOn.command!.command, args: addOn.command!.args || [], + allowBuilds: addOn.command!.allowBuilds, }) const cmd = formatCommand(translated) environment.startStep({ @@ -381,6 +382,7 @@ async function runCommandsAndInstallDependencies( const starterTranslated = translateExecuteCommand(options.packageManager, { command: options.starter.command.command, args: options.starter.command.args || [], + allowBuilds: options.starter.command.allowBuilds, }) const cmd = formatCommand(starterTranslated) environment.startStep({ diff --git a/packages/create/src/frameworks/react/add-ons/storybook/info.json b/packages/create/src/frameworks/react/add-ons/storybook/info.json index 929d6c1a..2122c230 100644 --- a/packages/create/src/frameworks/react/add-ons/storybook/info.json +++ b/packages/create/src/frameworks/react/add-ons/storybook/info.json @@ -10,6 +10,7 @@ "priority": 1, "command": { "command": "npx", - "args": ["storybook", "init", "-y", "--no-dev"] + "args": ["storybook", "init", "-y", "--no-dev"], + "allowBuilds": ["esbuild"] } } diff --git a/packages/create/src/package-manager.ts b/packages/create/src/package-manager.ts index c4b9eb43..1994dc45 100644 --- a/packages/create/src/package-manager.ts +++ b/packages/create/src/package-manager.ts @@ -63,16 +63,32 @@ export function getPackageManagerScriptCommand( } } +export type ExecuteCommandOptions = { + /** + * Package names that are allowed to run postinstall/build scripts while the + * executed package is being installed. Only enforced by pnpm > 10. + */ + allowBuilds?: Array +} + export function getPackageManagerExecuteCommand( packagerManager: PackageManager, pkg: string, args: Array = [], + options: ExecuteCommandOptions = {}, ) { switch (packagerManager) { case 'yarn': return { command: 'yarn', args: ['dlx', pkg, ...args] } - case 'pnpm': - return { command: 'pnpm', args: ['dlx', pkg, ...args] } + case 'pnpm': { + const allowBuildFlags = (options.allowBuilds || []).map( + (name) => `--allow-build=${name}`, + ) + return { + command: 'pnpm', + args: [...allowBuildFlags, 'dlx', pkg, ...args], + } + } case 'bun': return { command: 'bunx', args: ['--bun', pkg, ...args] } case 'deno': @@ -138,12 +154,21 @@ export function packageManagerInstall( export function translateExecuteCommand( packageManager: PackageManager, - command: { command: string; args?: Array }, + command: { + command: string + args?: Array + allowBuilds?: Array + }, ): { command: string; args: Array } { const args = command.args || [] const parsed = parseExecuteCommand(command.command, args) if (parsed) { - return getPackageManagerExecuteCommand(packageManager, parsed.pkg, parsed.args) + return getPackageManagerExecuteCommand( + packageManager, + parsed.pkg, + parsed.args, + { allowBuilds: command.allowBuilds }, + ) } return { command: command.command, args } } diff --git a/packages/create/src/types.ts b/packages/create/src/types.ts index 27e1212f..578df98c 100644 --- a/packages/create/src/types.ts +++ b/packages/create/src/types.ts @@ -75,6 +75,7 @@ export const AddOnBaseSchema = z.object({ .object({ command: z.string(), args: z.array(z.string()).optional(), + allowBuilds: z.array(z.string()).optional(), }) .optional(), routes: z diff --git a/packages/create/tests/package-manager.test.ts b/packages/create/tests/package-manager.test.ts index a69a48c8..ac89af1e 100644 --- a/packages/create/tests/package-manager.test.ts +++ b/packages/create/tests/package-manager.test.ts @@ -311,3 +311,88 @@ describe('translateExecuteCommand', () => { ).toBe('deno task dev') }) }) + +describe('allowBuilds', () => { + it('should add --allow-build flags before dlx for pnpm', () => { + expect( + formatCommand( + getPackageManagerExecuteCommand( + 'pnpm', + 'storybook', + ['init', '-y', '--no-dev'], + { allowBuilds: ['esbuild'] }, + ), + ), + ).toBe('pnpm --allow-build=esbuild dlx storybook init -y --no-dev') + }) + it('should emit one --allow-build flag per package for pnpm', () => { + expect( + formatCommand( + getPackageManagerExecuteCommand('pnpm', 'storybook', ['init'], { + allowBuilds: ['esbuild', 'sharp'], + }), + ), + ).toBe('pnpm --allow-build=esbuild --allow-build=sharp dlx storybook init') + }) + it('should ignore allowBuilds for non-pnpm package managers', () => { + expect( + formatCommand( + getPackageManagerExecuteCommand('npm', 'storybook', ['init'], { + allowBuilds: ['esbuild'], + }), + ), + ).toBe('npx -y storybook init') + expect( + formatCommand( + getPackageManagerExecuteCommand('yarn', 'storybook', ['init'], { + allowBuilds: ['esbuild'], + }), + ), + ).toBe('yarn dlx storybook init') + expect( + formatCommand( + getPackageManagerExecuteCommand('bun', 'storybook', ['init'], { + allowBuilds: ['esbuild'], + }), + ), + ).toBe('bunx --bun storybook init') + expect( + formatCommand( + getPackageManagerExecuteCommand('deno', 'storybook', ['init'], { + allowBuilds: ['esbuild'], + }), + ), + ).toBe('deno run npm:storybook init') + }) + it('should not add flags for pnpm when allowBuilds is empty', () => { + expect( + formatCommand( + getPackageManagerExecuteCommand('pnpm', 'storybook', ['init'], { + allowBuilds: [], + }), + ), + ).toBe('pnpm dlx storybook init') + }) + it('should forward allowBuilds through translateExecuteCommand for pnpm', () => { + expect( + formatCommand( + translateExecuteCommand('pnpm', { + command: 'npx', + args: ['storybook', 'init', '-y', '--no-dev'], + allowBuilds: ['esbuild'], + }), + ), + ).toBe('pnpm --allow-build=esbuild dlx storybook init -y --no-dev') + }) + it('should drop allowBuilds through translateExecuteCommand for npm', () => { + expect( + formatCommand( + translateExecuteCommand('npm', { + command: 'npx', + args: ['storybook', 'init', '-y', '--no-dev'], + allowBuilds: ['esbuild'], + }), + ), + ).toBe('npx -y storybook init -y --no-dev') + }) +})