What happens
vp create / vp migrate scaffold two entries that must move in lockstep (packages/cli/src/utils/constants.ts):
catalog:
vite: npm:@voidzero-dev/vite-plus-core@0.2.8
vite-plus: 0.2.8
To a dependency bot these are two unrelated packages, vite-plus and @voidzero-dev/vite-plus-core, so a normal Renovate configuration turns one Vite+ release into two independent PRs: one bumps vite-plus and leaves the alias at the old version, the other bumps the alias and leaves vite-plus at the old version. Each PR's CI, and the repo state after merging either one, runs vite-plus X against core Y, a pairing that is never released together. Nothing ties the two PRs to each other, and any per-package difference in schedule, minimumReleaseAge, or automerge policy pushes them further apart.
Real case: in voidzero-dev/setup.viteplus.dev#36 and voidzero-dev/setup.viteplus.dev#37, one PR reached vite-plus 0.2.8 with the alias still at 0.2.1 while the other reached the alias at 0.2.7 with vite-plus still at 0.2.1, and both sat open for a month. voidzero-dev/setup-vp#119 hit the same split through mismatched age gates. Every repo the migrator produces has this shape, so any Vite+ project with Renovate or Dependabot enabled is exposed. The exact vitest override from the same constants is the same class of problem: a bot can bump it away from the version bundled in vite-plus.
Repo-level workaround
A Renovate group rule puts the bumps in one PR so they move in lockstep:
{
"groupName": "vite+",
"matchPackageNames": ["vite-plus", "@voidzero-dev/vite-plus-*", "vitest"]
}
Include vitest when the project depends on it: vp pins the vitest override to the version bundled in vite-plus, so an independent vitest PR desyncs that pin the same way. Installed @vitest/coverage-* providers belong in the group too, since vp requires them aligned with the bundled vitest. Note that grouping keeps vitest in the same PR only when updates coincide; a vitest release with no simultaneous vite-plus release still produces a lone vitest bump ahead of the bundled version, which only the skew guard below catches.
minimumReleaseAge and schedule are only needed on top of this when some other rule treats the grouped packages differently. Grouping decides where an update lands, not when it becomes eligible; eligibility is still evaluated per package. With no other rules, all members inherit the same policy and the group rule alone keeps them in lockstep. But if another rule (commonly a shared org preset that special-cases vite-plus) gives members different minimumReleaseAge or schedule values, they become eligible at different times and the grouped PR ships with only the eligible members, recreating the mismatch. That is exactly what happened in voidzero-dev/setup-vp#119: vite-plus at 0 days, the alias behind a 3-day gate and a Monday schedule. In that case set both fields on the group rule so every member shares one value; a short buffer like "1 day" works well since the packages publish seconds apart and clear the gate together.
Merged examples: voidzero-dev/setup-vp#121, voidzero-dev/setup.viteplus.dev#42. But each repo has to rediscover this rule, and nothing fails when it is missing.
Ideas
- Ship an official Renovate preset in this repo (a checked-in
default.json, consumed as "extends": ["github>voidzero-dev/vite-plus"]) with the group rule above plus the vitest handling. Document it in the migration guide; vp migrate could suggest it when the repo already has a Renovate config.
- Fail fast on skew:
vp warns or errors when the resolved @voidzero-dev/vite-plus-core version differs from the CLI version, so a mismatched bot PR fails CI instead of shipping an untested pairing. The coverage-provider guard in define-config.ts is precedent for this kind of check.
The preset prevents the split for Renovate users; the guard catches everything else (Dependabot, manual edits).
What happens
vp create/vp migratescaffold two entries that must move in lockstep (packages/cli/src/utils/constants.ts):To a dependency bot these are two unrelated packages,
vite-plusand@voidzero-dev/vite-plus-core, so a normal Renovate configuration turns one Vite+ release into two independent PRs: one bumpsvite-plusand leaves the alias at the old version, the other bumps the alias and leavesvite-plusat the old version. Each PR's CI, and the repo state after merging either one, runs vite-plus X against core Y, a pairing that is never released together. Nothing ties the two PRs to each other, and any per-package difference in schedule,minimumReleaseAge, or automerge policy pushes them further apart.Real case: in voidzero-dev/setup.viteplus.dev#36 and voidzero-dev/setup.viteplus.dev#37, one PR reached vite-plus 0.2.8 with the alias still at 0.2.1 while the other reached the alias at 0.2.7 with vite-plus still at 0.2.1, and both sat open for a month. voidzero-dev/setup-vp#119 hit the same split through mismatched age gates. Every repo the migrator produces has this shape, so any Vite+ project with Renovate or Dependabot enabled is exposed. The exact
vitestoverride from the same constants is the same class of problem: a bot can bump it away from the version bundled in vite-plus.Repo-level workaround
A Renovate group rule puts the bumps in one PR so they move in lockstep:
{ "groupName": "vite+", "matchPackageNames": ["vite-plus", "@voidzero-dev/vite-plus-*", "vitest"] }Include
vitestwhen the project depends on it: vp pins thevitestoverride to the version bundled in vite-plus, so an independent vitest PR desyncs that pin the same way. Installed@vitest/coverage-*providers belong in the group too, since vp requires them aligned with the bundled vitest. Note that grouping keepsvitestin the same PR only when updates coincide; a vitest release with no simultaneous vite-plus release still produces a lone vitest bump ahead of the bundled version, which only the skew guard below catches.minimumReleaseAgeandscheduleare only needed on top of this when some other rule treats the grouped packages differently. Grouping decides where an update lands, not when it becomes eligible; eligibility is still evaluated per package. With no other rules, all members inherit the same policy and the group rule alone keeps them in lockstep. But if another rule (commonly a shared org preset that special-casesvite-plus) gives members differentminimumReleaseAgeorschedulevalues, they become eligible at different times and the grouped PR ships with only the eligible members, recreating the mismatch. That is exactly what happened in voidzero-dev/setup-vp#119:vite-plusat 0 days, the alias behind a 3-day gate and a Monday schedule. In that case set both fields on the group rule so every member shares one value; a short buffer like"1 day"works well since the packages publish seconds apart and clear the gate together.Merged examples: voidzero-dev/setup-vp#121, voidzero-dev/setup.viteplus.dev#42. But each repo has to rediscover this rule, and nothing fails when it is missing.
Ideas
default.json, consumed as"extends": ["github>voidzero-dev/vite-plus"]) with the group rule above plus the vitest handling. Document it in the migration guide;vp migratecould suggest it when the repo already has a Renovate config.vpwarns or errors when the resolved@voidzero-dev/vite-plus-coreversion differs from the CLI version, so a mismatched bot PR fails CI instead of shipping an untested pairing. The coverage-provider guard indefine-config.tsis precedent for this kind of check.The preset prevents the split for Renovate users; the guard catches everything else (Dependabot, manual edits).