Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/test-vp-create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,29 @@ jobs:
create-args: vite:monorepo --directory test-project
template-args: ''
verify-command: vp run ready
verify-migration: 'false'
- name: application
create-args: vite:application --directory test-project
template-args: '-- --template vanilla-ts'
verify-command: vp run build
verify-migration: 'false'
- name: library
create-args: vite:library --directory test-project
template-args: ''
verify-command: |
vp run build
vp run test
verify-migration: 'false'
# Remote template that ships ESLint (+ an eslint.config.js importing
# @eslint/js etc.). Exercises the migrate-before-rewrite reorder in
# `vp create`: after scaffold, ESLint → oxlint and Prettier → oxfmt
# run before the vite-plus rewrite so `.oxlintrc` / `.oxfmtrc` get
# merged into vite.config.ts.
- name: remote-vite-react-ts
create-args: vite@9.0.5
template-args: '-- test-project --template react-ts'
verify-command: vp run build
verify-migration: 'true'
package-manager:
- pnpm
- npm
Expand Down Expand Up @@ -253,6 +266,41 @@ jobs:
console.log('✓ vite-plus@' + pkg.version + ' installed correctly');
"

- name: Verify ESLint/Prettier auto-migration
if: matrix.template.verify-migration == 'true'
working-directory: ${{ runner.temp }}/test-project
run: |
# eslint.config.js must be gone (migration deleted it)
test ! -f eslint.config.js
echo "✓ eslint.config.js removed"

# .oxlintrc.json must NOT be loose on disk — it was merged into
# vite.config.ts by the rewrite step that runs after migration.
test ! -f .oxlintrc.json
echo "✓ .oxlintrc.json merged into vite.config.ts"

# vite.config.ts must contain the merged oxlint config.
grep -q '^[[:space:]]*lint:' vite.config.ts
echo "✓ vite.config.ts has merged lint section"

# package.json: eslint devDep removed, vite-plus present, lint script rewritten.
node -e "
const pkg = require('./package.json');
if (pkg.devDependencies && pkg.devDependencies.eslint) {
console.error('✗ eslint devDependency should have been removed');
process.exit(1);
}
if (!pkg.devDependencies || !pkg.devDependencies['vite-plus']) {
console.error('✗ vite-plus devDependency missing');
process.exit(1);
}
if (!pkg.scripts || !pkg.scripts.lint || !pkg.scripts.lint.includes('vp lint')) {
console.error('✗ lint script should invoke vp lint, got: ' + (pkg.scripts && pkg.scripts.lint));
process.exit(1);
}
console.log('✓ package.json migrated (eslint gone, vite-plus added, lint script rewritten)');
"

- name: Run vp check
working-directory: ${{ runner.temp }}/test-project
run: vp check
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
> vp create vite@9.0.5 --no-interactive -- my-react-ts --template react-ts # create vite app with pinned version + react-ts template, should auto-migrate ESLint -> Oxlint and merge lint config into vite.config.ts
> test ! -f my-react-ts/eslint.config.js && echo 'eslint.config.js removed' # eslint config deleted
eslint.config.js removed

> test ! -f my-react-ts/.oxlintrc.json && echo '.oxlintrc.json merged into vite.config.ts' # migration output merged by rewrite step (matches vp migrate)
.oxlintrc.json merged into vite.config.ts

> cat my-react-ts/vite.config.ts # merged vite config should contain lint and fmt sections
import { defineConfig } from "vite-plus";
import react from "@vitejs/plugin-react";

// https://vite.dev/config/
export default defineConfig({
staged: {
"*": "vp check --fix",
},
fmt: {},
lint: {
plugins: ["oxc", "typescript", "unicorn", "react"],
categories: {
correctness: "warn",
},
env: {
builtin: true,
},
ignorePatterns: ["dist"],
overrides: [
{
files: ["**/*.{ts,tsx}"],
rules: {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-undef": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": "error",
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error",
"no-array-constructor": "error",
"no-unused-expressions": "error",
"typescript/ban-ts-comment": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-empty-object-type": "error",
"typescript/no-explicit-any": "error",
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-misused-new": "error",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "error",
"typescript/no-require-imports": "error",
"typescript/no-this-alias": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-declaration-merging": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-wrapper-object-types": "error",
"typescript/prefer-as-const": "error",
"typescript/prefer-namespace-keyword": "error",
"typescript/triple-slash-reference": "error",
"react/rules-of-hooks": "error",
"react/exhaustive-deps": "warn",
"react/only-export-components": [
"error",
{
allowConstantExport: true,
},
],
},
env: {
es2020: true,
browser: true,
},
},
],
options: {
typeAware: true,
typeCheck: true,
},
},
plugins: [react()],
});

> node -e "const p=require('./my-react-ts/package.json');console.log('lint:', p.scripts && p.scripts.lint);console.log('eslint dep:', !!(p.devDependencies && p.devDependencies.eslint));console.log('vite-plus dep:', !!(p.devDependencies && p.devDependencies['vite-plus']));" # scripts rewritten, eslint dep removed, vite-plus added
lint: vp lint .
eslint dep: false
vite-plus dep: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"env": {
"VP_SKIP_INSTALL": "",
"CI": ""
},
"commands": [
{
"command": "vp create vite@9.0.5 --no-interactive -- my-react-ts --template react-ts # create vite app with pinned version + react-ts template, should auto-migrate ESLint -> Oxlint and merge lint config into vite.config.ts",
"ignoreOutput": true
},
"test ! -f my-react-ts/eslint.config.js && echo 'eslint.config.js removed' # eslint config deleted",
"test ! -f my-react-ts/.oxlintrc.json && echo '.oxlintrc.json merged into vite.config.ts' # migration output merged by rewrite step (matches vp migrate)",
"cat my-react-ts/vite.config.ts # merged vite config should contain lint and fmt sections",
"node -e \"const p=require('./my-react-ts/package.json');console.log('lint:', p.scripts && p.scripts.lint);console.log('eslint dep:', !!(p.devDependencies && p.devDependencies.eslint));console.log('vite-plus dep:', !!(p.devDependencies && p.devDependencies['vite-plus']));\" # scripts rewritten, eslint dep removed, vite-plus added"
]
}
65 changes: 58 additions & 7 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'node:fs';
import path from 'node:path';
import { styleText } from 'node:util';

Expand All @@ -7,12 +8,17 @@ import mri from 'mri';
import { vitePlusHeader } from '../../binding/index.js';
import {
addFrameworkShim,
detectEslintProject,
detectFramework,
detectPrettierProject,
hasFrameworkShim,
installGitHooks,
promptEslintMigration,
promptPrettierMigration,
rewriteMonorepo,
rewriteMonorepoProject,
rewriteStandaloneProject,
setPackageManager,
} from '../migration/migrator.ts';
import { DependencyType, PackageManager, type WorkspaceInfo } from '../types/index.ts';
import {
Expand Down Expand Up @@ -893,18 +899,50 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
});
resumeCreateProgress();

// The migrate-before-rewrite reorder is only needed when the template
// actually ships ESLint or Prettier (e.g. `create-vite --template
// react-ts`). Builtin templates (vite:library, vite:application,
// vite:monorepo) don't — their package.json already references vite-plus
// and relies on `rewrite*Project` to add tarball overrides BEFORE the
// first install, so install-first would break CI's local-tarball resolve.
const shouldMigrateLintFmtTools =
detectEslintProject(fullPath).hasDependency || detectPrettierProject(fullPath).hasDependency;

let installSummary: CommandRunSummary | undefined;

// For templates that ship ESLint/Prettier, install template deps first so
// `@oxlint/migrate` can resolve eslint.config.js's plugin imports, then
// migrate before the vite-plus rewrite so the generated .oxlintrc/.oxfmtrc
// get merged into vite.config.ts — matching `vp migrate`. Pin the
// packageManager field (vite_install hardcodes pnpm in CI/non-TTY when no
// signal is present) and force yarn's classic node_modules layout
// (Plug'n'Play zip entries break @oxlint/migrate's fileURLToPath resolution).
const installAndMigrate = async (installCwd: string) => {
setPackageManager(fullPath, workspaceInfo.downloadPackageManager);
if (workspaceInfo.packageManager === PackageManager.yarn) {
const yarnrcPath = path.join(fullPath, '.yarnrc.yml');
if (!fs.existsSync(yarnrcPath)) {
fs.writeFileSync(yarnrcPath, 'nodeLinker: node-modules\n');
}
}
updateCreateProgress('Installing dependencies');
installSummary = await runViteInstall(installCwd, options.interactive, installArgs, {
silent: compactOutput,
});
if (installSummary.status !== 'installed') {
return;
}
updateCreateProgress('Migrating lint and format tools');
pauseCreateProgress();
await promptEslintMigration(fullPath, /* interactive */ false);
await promptPrettierMigration(fullPath, /* interactive */ false);
resumeCreateProgress();
};

if (isMonorepo) {
if (!compactOutput) {
prompts.log.step('Monorepo integration...');
}
updateCreateProgress('Integrating into monorepo');
rewriteMonorepoProject(fullPath, workspaceInfo.packageManager, undefined, compactOutput);
for (const framework of detectFramework(fullPath)) {
if (!hasFrameworkShim(fullPath, framework)) {
addFrameworkShim(fullPath, framework);
}
}

if (workspaceInfo.packages.length > 0) {
if (options.interactive) {
Expand Down Expand Up @@ -965,6 +1003,16 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
}

updateWorkspaceConfig(projectDir, workspaceInfo);
if (shouldMigrateLintFmtTools) {
await installAndMigrate(workspaceInfo.rootDir);
}
updateCreateProgress('Integrating into monorepo');
rewriteMonorepoProject(fullPath, workspaceInfo.packageManager, undefined, compactOutput);
for (const framework of detectFramework(fullPath)) {
if (!hasFrameworkShim(fullPath, framework)) {
addFrameworkShim(fullPath, framework);
}
}
updateCreateProgress('Installing dependencies');
installSummary = await runViteInstall(workspaceInfo.rootDir, options.interactive, installArgs, {
silent: compactOutput,
Expand All @@ -974,6 +1022,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
silent: compactOutput,
});
} else {
if (shouldMigrateLintFmtTools) {
await installAndMigrate(fullPath);
}
updateCreateProgress('Applying Vite+ project setup');
rewriteStandaloneProject(fullPath, workspaceInfo, undefined, compactOutput);
for (const framework of detectFramework(fullPath)) {
Expand Down
Loading
Loading