Skip to content

Commit 0c769f8

Browse files
committed
feat(cli): add VITE_PLUS_FORCE_MIGRATE env var for force migration
When VITE_PLUS_FORCE_MIGRATE=1 is set, vp migrate forces full dependency rewriting even for projects already using vite-plus. This replaces the fragile hack logic in patch-project.ts that manually removed/re-added vite-plus and patched pnpm overrides. Now patch-project.ts simply sets the env var for projects with forceFreshMigration configured, and vp migrate handles everything correctly including pnpm.overrides in package.json.
1 parent 4c284c2 commit 0c769f8

5 files changed

Lines changed: 48 additions & 98 deletions

File tree

.claude/skills/add-ecosystem-ci/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Fetch the repository's root to check if the main package.json is in a subdirecto
2727

2828
### 2.2 Check if Project Already Uses Vite-Plus
2929

30-
Check the project's root `package.json` for `vite-plus` in `dependencies` or `devDependencies`. If the project already uses vite-plus, set `forceFreshMigration: true` in `repo.json`. This is required because `patch-project.ts` runs `vp migrate` with locally built tgz overrides — without this flag, `vp migrate` detects "already using Vite+" and skips override injection, so the e2e test wouldn't actually test the locally built packages.
30+
Check the project's root `package.json` for `vite-plus` in `dependencies` or `devDependencies`. If the project already uses vite-plus, set `forceFreshMigration: true` in `repo.json`. This tells `patch-project.ts` to set `VITE_PLUS_FORCE_MIGRATE=1` so `vp migrate` forces full dependency rewriting instead of skipping with "already using Vite+".
3131

3232
### 2.3 Auto-detect Commands from GitHub Workflows
3333

@@ -115,5 +115,5 @@ node ecosystem-ci/clone.ts project-name
115115
- The `directory` field is optional - only add it if the package.json is not in the project root
116116
- If `directory` is specified in repo.json, it must also be specified in the workflow matrix
117117
- `patch-project.ts` automatically handles running `vp migrate` in the correct directory
118-
- `forceFreshMigration` is required for projects that already have `vite-plus` in their package.json — it removes the existing dependency before migration so the locally built tgz overrides get injected
118+
- `forceFreshMigration` is required for projects that already have `vite-plus` in their package.json — it sets `VITE_PLUS_FORCE_MIGRATE=1` so `vp migrate` forces full dependency rewriting instead of skipping
119119
- OS exclusions are added to the existing `exclude` section in the workflow matrix

ecosystem-ci/patch-project.ts

Lines changed: 10 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { execSync } from 'node:child_process';
2-
import { existsSync } from 'node:fs';
32
import { readFile, writeFile } from 'node:fs/promises';
43
import { join } from 'node:path';
54

@@ -22,67 +21,6 @@ const cwd = directory ? join(repoRoot, directory) : repoRoot;
2221
// run vp migrate
2322
const cli = process.env.VITE_PLUS_CLI_BIN ?? 'vp';
2423

25-
const tgzPaths = {
26-
vite: `file:${tgzDir}/voidzero-dev-vite-plus-core-0.0.0.tgz`,
27-
vitest: `file:${tgzDir}/voidzero-dev-vite-plus-test-0.0.0.tgz`,
28-
'vite-plus': `file:${tgzDir}/vite-plus-0.0.0.tgz`,
29-
'@voidzero-dev/vite-plus-core': `file:${tgzDir}/voidzero-dev-vite-plus-core-0.0.0.tgz`,
30-
'@voidzero-dev/vite-plus-test': `file:${tgzDir}/voidzero-dev-vite-plus-test-0.0.0.tgz`,
31-
};
32-
33-
// Projects that already have vite-plus need it removed before migration so
34-
// vp migrate treats them as fresh and applies tgz overrides. Without this,
35-
// vp migrate detects "already using Vite+" and skips override injection.
36-
const forceFreshMigration = 'forceFreshMigration' in repoConfig && repoConfig.forceFreshMigration;
37-
if (forceFreshMigration) {
38-
const pkgPath = join(cwd, 'package.json');
39-
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
40-
delete pkg.devDependencies?.['vite-plus'];
41-
delete pkg.dependencies?.['vite-plus'];
42-
43-
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
44-
45-
// Also update pnpm-workspace.yaml overrides for projects that don't have
46-
// pnpm.overrides in package.json (pnpm-workspace.yaml overrides are only
47-
// used when package.json has no overrides).
48-
const workspaceYamlPath = join(cwd, 'pnpm-workspace.yaml');
49-
if (existsSync(workspaceYamlPath)) {
50-
const yaml = await readFile(workspaceYamlPath, 'utf-8');
51-
const lines = yaml.split('\n');
52-
const result: string[] = [];
53-
let inOverrides = false;
54-
55-
for (const line of lines) {
56-
if (/^overrides:\s*$/.test(line)) {
57-
inOverrides = true;
58-
result.push('overrides:');
59-
for (const [name, value] of Object.entries(tgzPaths)) {
60-
const yamlKey = name.includes('@') ? `"${name}"` : name;
61-
result.push(` ${yamlKey}: ${value}`);
62-
}
63-
continue;
64-
}
65-
if (inOverrides) {
66-
if (line.startsWith(' ')) {
67-
continue;
68-
}
69-
inOverrides = false;
70-
}
71-
result.push(line);
72-
}
73-
74-
if (!inOverrides && !result.some((l) => l.startsWith('overrides:'))) {
75-
result.push('overrides:');
76-
for (const [name, value] of Object.entries(tgzPaths)) {
77-
const yamlKey = name.includes('@') ? `"${name}"` : name;
78-
result.push(` ${yamlKey}: ${value}`);
79-
}
80-
}
81-
82-
await writeFile(workspaceYamlPath, result.join('\n'), 'utf-8');
83-
}
84-
}
85-
8624
if (project === 'rollipop') {
8725
const oxfmtrc = await readFile(join(repoRoot, '.oxfmtrc.json'), 'utf-8');
8826
await writeFile(
@@ -92,35 +30,22 @@ if (project === 'rollipop') {
9230
);
9331
}
9432

33+
// Projects that already use vite-plus need VITE_PLUS_FORCE_MIGRATE=1 so
34+
// vp migrate runs full dependency rewriting instead of skipping.
35+
const forceFreshMigration = 'forceFreshMigration' in repoConfig && repoConfig.forceFreshMigration;
36+
9537
execSync(`${cli} migrate --no-agent --no-interactive`, {
9638
cwd,
9739
stdio: 'inherit',
9840
env: {
9941
...process.env,
42+
...(forceFreshMigration ? { VITE_PLUS_FORCE_MIGRATE: '1' } : {}),
10043
VITE_PLUS_OVERRIDE_PACKAGES: JSON.stringify({
101-
vite: tgzPaths.vite,
102-
vitest: tgzPaths.vitest,
103-
'@voidzero-dev/vite-plus-core': tgzPaths['@voidzero-dev/vite-plus-core'],
104-
'@voidzero-dev/vite-plus-test': tgzPaths['@voidzero-dev/vite-plus-test'],
44+
vite: `file:${tgzDir}/voidzero-dev-vite-plus-core-0.0.0.tgz`,
45+
vitest: `file:${tgzDir}/voidzero-dev-vite-plus-test-0.0.0.tgz`,
46+
'@voidzero-dev/vite-plus-core': `file:${tgzDir}/voidzero-dev-vite-plus-core-0.0.0.tgz`,
47+
'@voidzero-dev/vite-plus-test': `file:${tgzDir}/voidzero-dev-vite-plus-test-0.0.0.tgz`,
10548
}),
106-
VITE_PLUS_VERSION: tgzPaths['vite-plus'],
49+
VITE_PLUS_VERSION: `file:${tgzDir}/vite-plus-0.0.0.tgz`,
10750
},
10851
});
109-
110-
// Post-migration: ensure tgz overrides are set in pnpm.overrides in package.json.
111-
// vp migrate may overwrite overrides set before migration, and pnpm ignores
112-
// pnpm-workspace.yaml overrides when pnpm.overrides exists in package.json.
113-
if (forceFreshMigration) {
114-
const pkgPath = join(cwd, 'package.json');
115-
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
116-
if (!pkg.pnpm) {
117-
pkg.pnpm = {};
118-
}
119-
if (!pkg.pnpm.overrides) {
120-
pkg.pnpm.overrides = {};
121-
}
122-
for (const [name, value] of Object.entries(tgzPaths)) {
123-
pkg.pnpm.overrides[name] = value;
124-
}
125-
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
126-
}

packages/cli/src/migration/bin.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
selectAgentTargetPaths,
1919
writeAgentInstructions,
2020
} from '../utils/agent.js';
21+
import { isForceOverrideMode } from '../utils/constants.js';
2122
import {
2223
detectEditorConflicts,
2324
type EditorId,
@@ -747,8 +748,9 @@ async function main() {
747748
const resolvedPackageManager = workspaceInfoOptional.packageManager ?? 'unknown';
748749

749750
// Early return if already using Vite+ (only ESLint/hooks migration may be needed)
751+
// In force-override mode (file: tgz overrides), skip this check and run full migration
750752
const rootPkg = readNearestPackageJson<PackageDependencies>(workspaceInfoOptional.rootDir);
751-
if (hasVitePlusDependency(rootPkg)) {
753+
if (hasVitePlusDependency(rootPkg) && !isForceOverrideMode()) {
752754
let didMigrate = false;
753755
let installDurationMs = 0;
754756
const report = createMigrationReport();

packages/cli/src/migration/migrator.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
VITE_PLUS_NAME,
2323
VITE_PLUS_OVERRIDE_PACKAGES,
2424
VITE_PLUS_VERSION,
25+
isForceOverrideMode,
2526
} from '../utils/constants.js';
2627
import { editJsonFile, isJsonFile, readJsonFile } from '../utils/json.js';
2728
import { detectPackageMetadata } from '../utils/package.js';
@@ -982,16 +983,29 @@ function rewriteRootWorkspacePackageJson(
982983
...VITE_PLUS_OVERRIDE_PACKAGES,
983984
};
984985
} else if (packageManager === PackageManager.pnpm) {
985-
// pnpm use overrides field at pnpm-workspace.yaml
986-
// so we don't need to set overrides field at package.json
987-
// remove packages from `resolutions` field and `pnpm.overrides` field if they exist
988-
// https://pnpm.io/9.x/package_json#resolutions
989-
for (const key of [...Object.keys(VITE_PLUS_OVERRIDE_PACKAGES), ...REMOVE_PACKAGES]) {
990-
if (pkg.pnpm?.overrides?.[key]) {
991-
delete pkg.pnpm.overrides[key];
992-
}
993-
if (pkg.resolutions?.[key]) {
994-
delete pkg.resolutions[key];
986+
if (isForceOverrideMode()) {
987+
// In force-override mode, keep overrides in package.json pnpm.overrides
988+
// because pnpm ignores pnpm-workspace.yaml overrides when pnpm.overrides
989+
// exists in package.json (even with unrelated entries like rollup).
990+
pkg.pnpm = {
991+
...pkg.pnpm,
992+
overrides: {
993+
...pkg.pnpm?.overrides,
994+
...VITE_PLUS_OVERRIDE_PACKAGES,
995+
},
996+
};
997+
} else {
998+
// pnpm use overrides field at pnpm-workspace.yaml
999+
// so we don't need to set overrides field at package.json
1000+
// remove packages from `resolutions` field and `pnpm.overrides` field if they exist
1001+
// https://pnpm.io/9.x/package_json#resolutions
1002+
for (const key of [...Object.keys(VITE_PLUS_OVERRIDE_PACKAGES), ...REMOVE_PACKAGES]) {
1003+
if (pkg.pnpm?.overrides?.[key]) {
1004+
delete pkg.pnpm.overrides[key];
1005+
}
1006+
if (pkg.resolutions?.[key]) {
1007+
delete pkg.resolutions[key];
1008+
}
9951009
}
9961010
}
9971011
// remove dependency selector from vite, e.g. "vite-plugin-svgr>vite": "npm:vite@7.0.12"

packages/cli/src/utils/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ export const VITE_PLUS_OVERRIDE_PACKAGES: Record<string, string> = process.env
1111
vitest: 'npm:@voidzero-dev/vite-plus-test@latest',
1212
};
1313

14+
/**
15+
* When VITE_PLUS_FORCE_MIGRATE is set, force full dependency rewriting
16+
* even for projects already using vite-plus. Used by ecosystem CI to
17+
* override dependencies with locally built tgz packages.
18+
*/
19+
export function isForceOverrideMode(): boolean {
20+
return process.env.VITE_PLUS_FORCE_MIGRATE === '1';
21+
}
22+
1423
const require = createRequire(import.meta.url);
1524

1625
export function resolve(path: string) {

0 commit comments

Comments
 (0)