Skip to content

Commit aa80b00

Browse files
committed
fix(cli): auto-pass --ignore-scripts when pnpm >=11 in migrate/create
pnpm v11 promoted ERR_PNPM_IGNORED_BUILDS from a warning to a hard exit-1 on unapproved build scripts. The auto-install run by `vp migrate` / `vp create` happens before the user has a chance to add packages to `pnpm.onlyBuiltDependencies`, so transitive deps like esbuild and @parcel/watcher fail the install — breaking the tanstack-start-helloworld and viteplus-ws-repro ecosystem-ci jobs. When the resolved package manager is pnpm and the version is >=11.0.0, inject `--ignore-scripts` into the auto-triggered install. User-invoked `vp install` is unchanged. The migrate early-return (eslint/prettier-only) path resolves a `latest` version through downloadPackageManager so the check applies there too.
1 parent 508247d commit aa80b00

4 files changed

Lines changed: 109 additions & 3 deletions

File tree

packages/cli/src/create/bin.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
917917
updateCreateProgress('Installing dependencies');
918918
const installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
919919
silent: compactOutput,
920+
packageManager: workspaceInfo.packageManager,
921+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
920922
});
921923
updateCreateProgress('Formatting code');
922924
await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput });
@@ -1038,6 +1040,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
10381040
updateCreateProgress('Installing dependencies');
10391041
installSummary = await runViteInstall(installCwd, options.interactive, installArgs, {
10401042
silent: compactOutput,
1043+
packageManager: workspaceInfo.packageManager,
1044+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
10411045
});
10421046
if (installSummary.status !== 'installed') {
10431047
return;
@@ -1126,6 +1130,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
11261130
updateCreateProgress('Installing dependencies');
11271131
installSummary = await runViteInstall(workspaceInfo.rootDir, options.interactive, installArgs, {
11281132
silent: compactOutput,
1133+
packageManager: workspaceInfo.packageManager,
1134+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
11291135
});
11301136
updateCreateProgress('Formatting code');
11311137
await runViteFmt(workspaceInfo.rootDir, options.interactive, [projectDir], {
@@ -1148,6 +1154,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
11481154
updateCreateProgress('Installing dependencies');
11491155
installSummary = await runViteInstall(fullPath, options.interactive, installArgs, {
11501156
silent: compactOutput,
1157+
packageManager: workspaceInfo.packageManager,
1158+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
11511159
});
11521160
updateCreateProgress('Formatting code');
11531161
await runViteFmt(fullPath, options.interactive, undefined, { silent: compactOutput });

packages/cli/src/migration/bin.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,8 @@ async function executeMigrationPlan(
649649
undefined,
650650
{
651651
silent: true,
652+
packageManager: workspaceInfo.packageManager,
653+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
652654
},
653655
);
654656

@@ -778,7 +780,11 @@ async function executeMigrationPlan(
778780
workspaceInfo.rootDir,
779781
interactive,
780782
installArgs,
781-
{ silent: true },
783+
{
784+
silent: true,
785+
packageManager: workspaceInfo.packageManager,
786+
packageManagerVersion: workspaceInfo.downloadPackageManager.version,
787+
},
782788
);
783789

784790
clearMigrationProgress();
@@ -868,12 +874,30 @@ async function main() {
868874
updateMigrationProgress('Rewriting configs');
869875
mergeViteConfigFiles(workspaceInfoOptional.rootDir, true, report);
870876
updateMigrationProgress('Installing dependencies');
877+
// Resolve the actual pnpm version that `vp install` will use so the
878+
// auto-install can opt into `--ignore-scripts` on pnpm v11 (which fails
879+
// unapproved build scripts with `ERR_PNPM_IGNORED_BUILDS`).
880+
let resolvedVersion = workspaceInfoOptional.packageManagerVersion;
881+
if (
882+
workspaceInfoOptional.packageManager &&
883+
!semver.valid(semver.coerce(resolvedVersion) ?? '')
884+
) {
885+
const resolved = await downloadPackageManager(
886+
workspaceInfoOptional.packageManager,
887+
resolvedVersion,
888+
options.interactive,
889+
true,
890+
);
891+
resolvedVersion = resolved.version;
892+
}
871893
const installSummary = await runViteInstall(
872894
workspaceInfoOptional.rootDir,
873895
options.interactive,
874896
undefined,
875897
{
876898
silent: true,
899+
packageManager: workspaceInfoOptional.packageManager,
900+
packageManagerVersion: resolvedVersion,
877901
},
878902
);
879903
installDurationMs += installSummary.durationMs;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { PackageManager } from '../../types/index.ts';
4+
import { shouldIgnoreScriptsForAutoInstall } from '../prompts.ts';
5+
6+
describe('shouldIgnoreScriptsForAutoInstall', () => {
7+
it('returns true for pnpm >= 11.0.0', () => {
8+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '11.0.0')).toBe(true);
9+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '11.0.8')).toBe(true);
10+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '12.0.0')).toBe(true);
11+
});
12+
13+
it('returns false for pnpm < 11.0.0', () => {
14+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '10.33.2')).toBe(false);
15+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '9.5.0')).toBe(false);
16+
});
17+
18+
it('returns false for non-pnpm package managers', () => {
19+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.npm, '11.0.0')).toBe(false);
20+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.yarn, '11.0.0')).toBe(false);
21+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.bun, '11.0.0')).toBe(false);
22+
});
23+
24+
it('returns false when version is unknown or unparseable', () => {
25+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, undefined)).toBe(false);
26+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, 'latest')).toBe(false);
27+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '')).toBe(false);
28+
});
29+
30+
it('returns false when packageManager is undefined', () => {
31+
expect(shouldIgnoreScriptsForAutoInstall(undefined, '11.0.0')).toBe(false);
32+
});
33+
34+
it('coerces non-strict semver pnpm versions', () => {
35+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, 'v11.0.0')).toBe(true);
36+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '11')).toBe(true);
37+
expect(shouldIgnoreScriptsForAutoInstall(PackageManager.pnpm, '10')).toBe(false);
38+
});
39+
});

packages/cli/src/utils/prompts.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as prompts from '@voidzero-dev/vite-plus-prompts';
2+
import semver from 'semver';
23

34
import { downloadPackageManager as downloadPackageManagerBinding } from '../../binding/index.js';
45
import { PackageManager } from '../types/index.ts';
@@ -11,6 +12,28 @@ export interface CommandRunSummary {
1112
status: 'installed' | 'formatted' | 'failed' | 'skipped';
1213
}
1314

15+
/**
16+
* pnpm v11 promoted `ERR_PNPM_IGNORED_BUILDS` from a warning to a hard
17+
* exit-1. Auto-installs run by `vp migrate` / `vp create` happen before the
18+
* user has a chance to approve build scripts via `pnpm.onlyBuiltDependencies`,
19+
* so transitive deps like `esbuild` would fail the install. Pass
20+
* `--ignore-scripts` in that window so the orchestration succeeds; the user's
21+
* own subsequent `vp install` keeps default pnpm behavior.
22+
*/
23+
export function shouldIgnoreScriptsForAutoInstall(
24+
packageManager: PackageManager | undefined,
25+
packageManagerVersion: string | undefined,
26+
): boolean {
27+
if (packageManager !== PackageManager.pnpm) {
28+
return false;
29+
}
30+
const coerced = packageManagerVersion ? semver.coerce(packageManagerVersion)?.version : undefined;
31+
if (!coerced) {
32+
return false;
33+
}
34+
return semver.gte(coerced, '11.0.0');
35+
}
36+
1437
export function cancelAndExit(message = 'Operation cancelled', exitCode = 0): never {
1538
prompts.cancel(message);
1639
process.exit(exitCode);
@@ -63,19 +86,31 @@ export async function runViteInstall(
6386
cwd: string,
6487
interactive?: boolean,
6588
extraArgs?: string[],
66-
options?: { silent?: boolean },
89+
options?: {
90+
silent?: boolean;
91+
packageManager?: PackageManager;
92+
packageManagerVersion?: string;
93+
},
6794
) {
6895
// install dependencies on non-CI environment
6996
if (process.env.VP_SKIP_INSTALL) {
7097
return { durationMs: 0, status: 'skipped' } satisfies CommandRunSummary;
7198
}
7299

100+
const installArgs = [...(extraArgs ?? [])];
101+
if (
102+
shouldIgnoreScriptsForAutoInstall(options?.packageManager, options?.packageManagerVersion) &&
103+
!installArgs.includes('--ignore-scripts')
104+
) {
105+
installArgs.push('--ignore-scripts');
106+
}
107+
73108
const spinner = options?.silent ? getSilentSpinner() : getSpinner(interactive);
74109
const startTime = Date.now();
75110
spinner.start(`Installing dependencies...`);
76111
const { exitCode, stderr, stdout } = await runCommandSilently({
77112
command: process.env.VP_CLI_BIN ?? 'vp',
78-
args: ['install', ...(extraArgs ?? [])],
113+
args: ['install', ...installArgs],
79114
cwd,
80115
envs: process.env,
81116
});

0 commit comments

Comments
 (0)