Skip to content

Commit 86b987a

Browse files
committed
fix(migrate): allow pkg.pr.new pnpm subdependencies
1 parent 2a36b25 commit 86b987a

7 files changed

Lines changed: 227 additions & 0 deletions

File tree

.github/scripts/test-pkg-pr-new-migrate.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export VP_OVERRIDE_PACKAGES="$(printf \
109109
"$vite_plus_core_spec" \
110110
"$vitest_version")"
111111
export VP_FORCE_MIGRATE=1
112+
# pkg.pr.new packages depend on URL-resolved platform binaries. pnpm blocks
113+
# those transitive URL dependencies when blockExoticSubdeps is enabled. The
114+
# migration persists the corresponding workspace setting, while this temporary
115+
# override also lets its pre-rewrite install recover a partially migrated tree.
116+
export PNPM_CONFIG_BLOCK_EXOTIC_SUBDEPS=false
112117
hash -r
113118

114119
echo
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "migration-upgrade-pkg-pr-new-pnpm",
3+
"devDependencies": {
4+
"@vitest/coverage-v8": "4.1.6",
5+
"vite": "npm:@voidzero-dev/vite-plus-core@^0.1.20",
6+
"vite-plus": "^0.1.20",
7+
"vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.20"
8+
},
9+
"packageManager": "pnpm@10.33.2"
10+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
packages:
2+
- .
3+
4+
blockExoticSubdeps: true
5+
6+
catalog:
7+
vite: npm:@voidzero-dev/vite-plus-core@^0.1.20
8+
vite-plus: ^0.1.20
9+
vitest: npm:@voidzero-dev/vite-plus-test@^0.1.20
10+
11+
overrides:
12+
vite: 'catalog:'
13+
vitest: 'catalog:'
14+
15+
peerDependencyRules:
16+
allowAny:
17+
- vite
18+
- vitest
19+
allowedVersions:
20+
vite: '*'
21+
vitest: '*'
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
> vp migrate --no-interactive # pkg.pr.new pnpm migration allows URL-resolved subdependencies
2+
◇ Migrated . to Vite+
3+
• Node <semver> pnpm <semver>
4+
• 2 config updates applied
5+
6+
> cat package.json # direct dependencies use catalogs aligned to the pkg.pr.new build
7+
{
8+
"name": "migration-upgrade-pkg-pr-new-pnpm",
9+
"devDependencies": {
10+
"@vitest/coverage-v8": "4.1.9",
11+
"vite": "catalog:",
12+
"vite-plus": "catalog:",
13+
"vitest": "catalog:"
14+
},
15+
"packageManager": "pnpm@<semver>",
16+
"pnpm": {
17+
"overrides": {
18+
"vite": "https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891",
19+
"vitest": "<semver>",
20+
"vite-plus": "https://pkg.pr.new/voidzero-dev/vite-plus@1891"
21+
}
22+
},
23+
"scripts": {
24+
"prepare": "vp config"
25+
}
26+
}
27+
28+
> cat pnpm-workspace.yaml # pkg.pr.new URLs are pinned and exotic subdependencies are allowed
29+
packages:
30+
- .
31+
32+
blockExoticSubdeps: false
33+
34+
catalog:
35+
vite: https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891
36+
vite-plus: https://pkg.pr.new/voidzero-dev/vite-plus@1891
37+
vitest: <semver>
38+
39+
overrides:
40+
vite: 'catalog:'
41+
vitest: 'catalog:'
42+
43+
peerDependencyRules:
44+
allowAny:
45+
- vite
46+
- vitest
47+
allowedVersions:
48+
vite: '*'
49+
vitest: '*'
50+
51+
> node -e "const fs = require('node:fs'); const y = fs.readFileSync('pnpm-workspace.yaml', 'utf8'); if (!y.includes('blockExoticSubdeps: false') || !y.includes('https://pkg.pr.new/voidzero-dev/vite-plus@1891') || !y.includes('https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891')) process.exit(1)" # pnpm policy and PR targets are persisted
52+
> node -e "const fs = require('node:fs'); fs.copyFileSync('package.json', 'package.after-first-migration.json'); fs.copyFileSync('pnpm-workspace.yaml', 'workspace.after-first-migration.yaml')" # capture first migration result
53+
> vp migrate --no-interactive # pkg.pr.new pnpm migration is idempotent
54+
◇ Migrated . to Vite+
55+
• Node <semver> pnpm <semver>
56+
57+
> node -e "const fs = require('node:fs'); if (fs.readFileSync('package.json', 'utf8') !== fs.readFileSync('package.after-first-migration.json', 'utf8') || fs.readFileSync('pnpm-workspace.yaml', 'utf8') !== fs.readFileSync('workspace.after-first-migration.yaml', 'utf8')) process.exit(1)" # rerun leaves manifests unchanged
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"env": {
3+
"PNPM_CONFIG_BLOCK_EXOTIC_SUBDEPS": "false",
4+
"VP_FORCE_MIGRATE": "1",
5+
"VP_OVERRIDE_PACKAGES": "{\"vite\":\"https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891\",\"vitest\":\"4.1.9\"}",
6+
"VP_VERSION": "https://pkg.pr.new/voidzero-dev/vite-plus@1891"
7+
},
8+
"commands": [
9+
"vp migrate --no-interactive # pkg.pr.new pnpm migration allows URL-resolved subdependencies",
10+
"cat package.json # direct dependencies use catalogs aligned to the pkg.pr.new build",
11+
"cat pnpm-workspace.yaml # pkg.pr.new URLs are pinned and exotic subdependencies are allowed",
12+
"node -e \"const fs = require('node:fs'); const y = fs.readFileSync('pnpm-workspace.yaml', 'utf8'); if (!y.includes('blockExoticSubdeps: false') || !y.includes('https://pkg.pr.new/voidzero-dev/vite-plus@1891') || !y.includes('https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891')) process.exit(1)\" # pnpm policy and PR targets are persisted",
13+
"node -e \"const fs = require('node:fs'); fs.copyFileSync('package.json', 'package.after-first-migration.json'); fs.copyFileSync('pnpm-workspace.yaml', 'workspace.after-first-migration.yaml')\" # capture first migration result",
14+
"vp migrate --no-interactive # pkg.pr.new pnpm migration is idempotent",
15+
"node -e \"const fs = require('node:fs'); if (fs.readFileSync('package.json', 'utf8') !== fs.readFileSync('package.after-first-migration.json', 'utf8') || fs.readFileSync('pnpm-workspace.yaml', 'utf8') !== fs.readFileSync('workspace.after-first-migration.yaml', 'utf8')) process.exit(1)\" # rerun leaves manifests unchanged"
16+
]
17+
}

packages/cli/src/migration/__tests__/migrator.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,66 @@ describe('ensureVitePlusBootstrap', () => {
14551455
expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false);
14561456
});
14571457

1458+
it('allows pkg.pr.new transitive URLs in pnpm workspace config and is idempotent', () => {
1459+
const savedForceMigrate = process.env.VP_FORCE_MIGRATE;
1460+
const savedViteOverride = VITE_PLUS_OVERRIDE_PACKAGES.vite;
1461+
const viteOverride =
1462+
'https://pkg.pr.new/voidzero-dev/vite-plus/@voidzero-dev/vite-plus-core@1891';
1463+
process.env.VP_FORCE_MIGRATE = '1';
1464+
VITE_PLUS_OVERRIDE_PACKAGES.vite = viteOverride;
1465+
try {
1466+
fs.writeFileSync(
1467+
path.join(tmpDir, 'package.json'),
1468+
JSON.stringify({
1469+
name: 'test',
1470+
devDependencies: { 'vite-plus': 'catalog:' },
1471+
devEngines: {
1472+
packageManager: { name: 'pnpm', version: '10.33.0', onFail: 'download' },
1473+
},
1474+
}),
1475+
);
1476+
fs.writeFileSync(
1477+
path.join(tmpDir, 'pnpm-workspace.yaml'),
1478+
[
1479+
'blockExoticSubdeps: true',
1480+
'catalog:',
1481+
` vite: '${viteOverride}'`,
1482+
' vite-plus: latest',
1483+
'overrides:',
1484+
" vite: 'catalog:'",
1485+
'peerDependencyRules:',
1486+
' allowAny:',
1487+
' - vite',
1488+
' allowedVersions:',
1489+
" vite: '*'",
1490+
].join('\n'),
1491+
);
1492+
1493+
expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(true);
1494+
const first = ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm));
1495+
1496+
expect(first.packageManagerConfig).toBe(true);
1497+
expect(
1498+
(
1499+
readYamlObject(path.join(tmpDir, 'pnpm-workspace.yaml')) as {
1500+
blockExoticSubdeps: boolean;
1501+
}
1502+
).blockExoticSubdeps,
1503+
).toBe(false);
1504+
expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false);
1505+
expect(ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm)).changed).toBe(
1506+
false,
1507+
);
1508+
} finally {
1509+
VITE_PLUS_OVERRIDE_PACKAGES.vite = savedViteOverride;
1510+
if (savedForceMigrate === undefined) {
1511+
delete process.env.VP_FORCE_MIGRATE;
1512+
} else {
1513+
process.env.VP_FORCE_MIGRATE = savedForceMigrate;
1514+
}
1515+
}
1516+
});
1517+
14581518
it('detects missing pnpm workspace catalog entry for vite-plus', () => {
14591519
fs.writeFileSync(
14601520
path.join(tmpDir, 'package.json'),

packages/cli/src/migration/migrator.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,10 @@ export function rewriteStandaloneProject(
19241924
});
19251925
}
19261926

1927+
if (packageManager === PackageManager.pnpm) {
1928+
ensurePnpmWorkspaceExoticSubdepsSetting(projectPath);
1929+
}
1930+
19271931
if (packageManager === PackageManager.yarn) {
19281932
rewriteYarnrcYml(projectPath, usesVitest);
19291933
} else if (packageManager === PackageManager.bun) {
@@ -2188,6 +2192,8 @@ function rewritePnpmWorkspaceYaml(
21882192
const managed = managedOverridePackages(usesVitest);
21892193

21902194
editYamlFile(pnpmWorkspaceYamlPath, (doc) => {
2195+
ensurePnpmExoticSubdepsSetting(doc);
2196+
21912197
// catalog
21922198
rewriteCatalog(doc, usesVitest);
21932199
if (pnpmMajorVersion !== undefined) {
@@ -3802,6 +3808,50 @@ function readPnpmWorkspacePeerDependencyRules(
38023808
return doc?.peerDependencyRules;
38033809
}
38043810

3811+
function forceOverrideUsesExoticPnpmSpec(): boolean {
3812+
if (!isForceOverrideMode()) {
3813+
return false;
3814+
}
3815+
return [VITE_PLUS_VERSION, ...Object.values(VITE_PLUS_OVERRIDE_PACKAGES)].some((spec) =>
3816+
/^(?:file|https?):/.test(spec),
3817+
);
3818+
}
3819+
3820+
function pnpmWorkspaceExoticSubdepsSettingSatisfied(projectPath: string): boolean {
3821+
if (!forceOverrideUsesExoticPnpmSpec()) {
3822+
return true;
3823+
}
3824+
const pnpmWorkspaceYamlPath = path.join(projectPath, 'pnpm-workspace.yaml');
3825+
if (!fs.existsSync(pnpmWorkspaceYamlPath)) {
3826+
return false;
3827+
}
3828+
const doc = readYamlFile(pnpmWorkspaceYamlPath) as { blockExoticSubdeps?: boolean } | null;
3829+
return doc?.blockExoticSubdeps === false;
3830+
}
3831+
3832+
function ensurePnpmExoticSubdepsSetting(doc: YamlDocument): boolean {
3833+
if (!forceOverrideUsesExoticPnpmSpec() || doc.get('blockExoticSubdeps') === false) {
3834+
return false;
3835+
}
3836+
doc.set('blockExoticSubdeps', false);
3837+
return true;
3838+
}
3839+
3840+
function ensurePnpmWorkspaceExoticSubdepsSetting(projectPath: string): boolean {
3841+
if (!forceOverrideUsesExoticPnpmSpec()) {
3842+
return false;
3843+
}
3844+
const pnpmWorkspaceYamlPath = path.join(projectPath, 'pnpm-workspace.yaml');
3845+
if (!fs.existsSync(pnpmWorkspaceYamlPath)) {
3846+
fs.writeFileSync(pnpmWorkspaceYamlPath, '');
3847+
}
3848+
let changed = false;
3849+
editYamlFile(pnpmWorkspaceYamlPath, (doc) => {
3850+
changed = ensurePnpmExoticSubdepsSetting(doc);
3851+
});
3852+
return changed;
3853+
}
3854+
38053855
function yarnrcSatisfiesVitePlus(projectPath: string, usesVitest: boolean): boolean {
38063856
const yarnrcYmlPath = path.join(projectPath, '.yarnrc.yml');
38073857
if (!fs.existsSync(yarnrcYmlPath)) {
@@ -4120,6 +4170,9 @@ export function detectVitePlusBootstrapPending(
41204170
);
41214171
}
41224172
if (packageManager === PackageManager.pnpm) {
4173+
if (!pnpmWorkspaceExoticSubdepsSettingSatisfied(projectPath)) {
4174+
return true;
4175+
}
41234176
if (pnpmConfigLivesInPackageJson(pkg, projectPath)) {
41244177
return (
41254178
vitePlusDependencyNeedsConcreteVersion(pkg) ||
@@ -4390,6 +4443,7 @@ export function ensureVitePlusBootstrap(
43904443
const catalogDependencyResolver = readPnpmWorkspaceCatalogDependencyResolver(projectPath);
43914444
if (
43924445
result.packageJson ||
4446+
!pnpmWorkspaceExoticSubdepsSettingSatisfied(projectPath) ||
43934447
defaultCatalogVitePlusDependencyPending(pkg, catalogDependencyResolver) ||
43944448
!overridesSatisfyVitePlus(
43954449
readPnpmWorkspaceOverrides(projectPath),
@@ -4415,6 +4469,9 @@ export function ensureVitePlusBootstrap(
44154469
? fs.readFileSync(pnpmWorkspaceYamlPath, 'utf-8')
44164470
: undefined;
44174471
result.packageManagerConfig = before !== after;
4472+
} else if (ensurePnpmWorkspaceExoticSubdepsSetting(projectPath)) {
4473+
ensurePnpmWorkspacePackages(projectPath, workspaceInfo.workspacePatterns);
4474+
result.packageManagerConfig = true;
44184475
}
44194476
} else if (workspaceInfo.packageManager === PackageManager.yarn) {
44204477
const yarnrcYmlPath = path.join(projectPath, '.yarnrc.yml');

0 commit comments

Comments
 (0)