From 59ca1b985b42b0aa658f5892d2f5f5d1e8ff4b23 Mon Sep 17 00:00:00 2001 From: Liang Date: Tue, 10 Nov 2026 23:06:40 +0800 Subject: [PATCH 1/3] feat(create): skip migration for templates that already use Vite+ --- .../mock-manifest.json | 21 +++++++++ .../snapshots.toml | 10 +++++ .../create_remote_vite_plus_template.md | 41 ++++++++++++++++++ .../create-vp-test-vite-plus-1.0.0.tgz | Bin 0 -> 850 bytes docs/guide/create.md | 2 + packages/cli/src/create/bin.ts | 35 +++++++++++---- 6 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/mock-manifest.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots/create_remote_vite_plus_template.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/tarballs/create-vp-test-vite-plus-1.0.0.tgz diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/mock-manifest.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/mock-manifest.json new file mode 100644 index 0000000000..d0f621feb5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/mock-manifest.json @@ -0,0 +1,21 @@ +{ + "create-vp-test-vite-plus": { + "name": "create-vp-test-vite-plus", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "create-vp-test-vite-plus", + "version": "1.0.0", + "bin": { + "create-vp-test-vite-plus": "index.cjs" + }, + "dist": { + "tarball": "{REGISTRY}/create-vp-test-vite-plus/-/create-vp-test-vite-plus-1.0.0.tgz", + "integrity": "sha512-xvpZCYhS9b6ee0uUQC7FlICwKIR/W6nkIQfWZ1UAfd8b+gzgUjT5kR8JILcITLstgv0prAnOeAVmsgyQExbD7A==" + } + } + } + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots.toml new file mode 100644 index 0000000000..1ce15ee705 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots.toml @@ -0,0 +1,10 @@ +# The fake `create-vp-test-vite-plus` CLI writes a project that already uses +# Vite+, so `vp create` must leave its setup unchanged. +[[case]] +name = "create_remote_vite_plus_template" +vp = "local" +steps = [ + { argv = ["vp", "create", "vp-test-vite-plus", "--no-interactive", "--no-agent", "--no-editor", "--no-git", "--no-hooks", "--", "my-app"], comment = "create from a template that already generates a Vite+ project", snapshot = false, continue-on-failure = true }, + { argv = ["vpt", "print-file", "my-app/package.json"], comment = "dependencies and scripts are not rewritten", continue-on-failure = true }, + { argv = ["vpt", "stat-file", "my-app/pnpm-workspace.yaml", "--assert-not", "file"], comment = "no Vite+ overrides are added", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots/create_remote_vite_plus_template.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots/create_remote_vite_plus_template.md new file mode 100644 index 0000000000..29f82ffa4a --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/snapshots/create_remote_vite_plus_template.md @@ -0,0 +1,41 @@ +# create_remote_vite_plus_template + +## `vp create vp-test-vite-plus --no-interactive --no-agent --no-editor --no-git --no-hooks -- my-app` + +create from a template that already generates a Vite+ project + + +## `vpt print-file my-app/package.json` + +dependencies and scripts are not rewritten + +``` +{ + "name": "my-app", + "private": true, + "type": "module", + "scripts": { + "dev": "vp dev", + "lint": "eslint ." + }, + "devDependencies": { + "eslint": "^9.0.0", + "vite-plus": "latest" + }, + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "", + "onFail": "download" + } + } +} +``` + +## `vpt stat-file my-app/pnpm-workspace.yaml --assert-not file` + +no Vite+ overrides are added + +``` +my-app/pnpm-workspace.yaml: missing +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/tarballs/create-vp-test-vite-plus-1.0.0.tgz b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_vite_plus_template/tarballs/create-vp-test-vite-plus-1.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d037e32f0db14f4e604c3c18a84394015a0def56 GIT binary patch literal 850 zcmV-Y1FifYiwFR%G4pBw1MOJdP8&xQrn&S}oVbb>1g!VpV8jKbjc|lo652>nNR-L? z*o?ED-OS7`Muf{gPG2uqsnj#OHc&&MNQNk3zYEXK%=z0h{^lHekBxVb^-#7l7Jpy^ zT$n!`$7zQln2|6$I*#W$;D%m1hT2$9v&(t ztZ&_=kDK^!CjaKY7e>VY@GgUz;(_yjdj5N}b-*5~RLsW*B-LTqzIKA*xv1eK4cmOC{WUh0tUqY zA;ub5LPEV`$Q2g@cuB67D}(QOX^4v+j_r7;nvK)?cDOTKuK#C=pPP@{|Ne{oz^m*3 zecLBJE)# zP6ZY5zJg7V_%Y`a7cH~V=__k#W3rtweRxU0EEVTt6b78E&6;wiKDd9;y6ns z9U}Hwo@jXeW`peg&oeLqPt9pnokEk3N`odleJ*gj+=f%=%XD<{9m&sunDKs5W$}c* c3J0QDzT4L(P>mWjYJ8*k2fRoZ%>Wz#0H$BMhyVZp literal 0 HcmV?d00001 diff --git a/docs/guide/create.md b/docs/guide/create.md index 1e3c258f74..1c36b98261 100644 --- a/docs/guide/create.md +++ b/docs/guide/create.md @@ -74,6 +74,8 @@ Vite+ ships with these built-in templates: Run `vp create --list` to see the built-in templates and the common shorthand templates Vite+ recognizes. +After a third-party template runs, `vp create` migrates the generated project to Vite+. When a standalone project already depends on `vite-plus`, `vp create` skips this migration and keeps the template's tooling choices. + ## Options - `--directory ` writes the generated project into a specific target directory. Only built-in templates and bundled `@org` templates support this option. diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index 30bd7261f5..87c1c3ada9 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -38,11 +38,13 @@ import { resolveApproveBuildTargets, } from '../utils/approve-builds.ts'; import { unwrapCliParseOutcome } from '../utils/cli-parse.ts'; +import { isForceOverrideMode } from '../utils/constants.ts'; import { documentationUrl } from '../utils/documentation.ts'; import { detectExistingEditors, selectEditors, writeEditorConfigs } from '../utils/editor.ts'; import { findGitRoot, initGitRepository } from '../utils/git.ts'; import { renderCliDoc } from '../utils/help.ts'; import { readJsonFile } from '../utils/json.ts'; +import { hasVitePlusDependency } from '../utils/package.ts'; import { displayRelative } from '../utils/path.ts'; import { cancelAndExit, @@ -1149,6 +1151,18 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h resumeCreateProgress(); } + // A `create-xxx` CLI that already generates a Vite+ project made its own + // toolchain choices (e.g. keeping ESLint), so skip migration like `vp migrate` + // does for Vite+ projects. Monorepo packages still need the rewrite to align + // with the workspace catalog. + const packageJsonPath = path.join(fullPath, 'package.json'); + const isVitePlusTemplate = + !isMonorepo && + templateInfo.type === TemplateType.remote && + !isForceOverrideMode() && + fs.existsSync(packageJsonPath) && + hasVitePlusDependency(readJsonFile(packageJsonPath)); + // 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, @@ -1156,9 +1170,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h // 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 || - detectTsupProject(fullPath).hasDependency; + !isVitePlusTemplate && + (detectEslintProject(fullPath).hasDependency || + detectPrettierProject(fullPath).hasDependency || + detectTsupProject(fullPath).hasDependency); let installSummary: CommandRunSummary | undefined; @@ -1338,12 +1353,14 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h await installAndMigrate(fullPath); } updateCreateProgress('Applying Vite+ project setup'); - const skipStagedMigration = shouldSkipStagedMigrationForHooks( - fullPath, - shouldSetupHooks, - workspaceInfo.packageManager, - ); - rewriteStandaloneProject(fullPath, workspaceInfo, skipStagedMigration, compactOutput); + if (!isVitePlusTemplate) { + const skipStagedMigration = shouldSkipStagedMigrationForHooks( + fullPath, + shouldSetupHooks, + workspaceInfo.packageManager, + ); + rewriteStandaloneProject(fullPath, workspaceInfo, skipStagedMigration, compactOutput); + } setPackageManager(fullPath, workspaceInfo.downloadPackageManager); for (const framework of detectFramework(fullPath)) { if (!hasFrameworkShim(fullPath, framework)) { From 5a8f911db20863a2416dcf9d08b130a715304641 Mon Sep 17 00:00:00 2001 From: Liang Date: Wed, 11 Nov 2026 22:53:03 +0800 Subject: [PATCH 2/3] feat(create): ask setup questions after the create-xxx CLI runs --- .../mock-manifest.json | 21 +++++ .../snapshots.toml | 19 ++++ .../create_remote_generated_setup.md | 53 +++++++++++ .../create-vp-test-generated-setup-1.0.0.tgz | Bin 0 -> 727 bytes docs/guide/create.md | 2 + packages/cli/src/create/bin.ts | 87 +++++++++++------- 6 files changed, 150 insertions(+), 32 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/mock-manifest.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots/create_remote_generated_setup.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/tarballs/create-vp-test-generated-setup-1.0.0.tgz diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/mock-manifest.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/mock-manifest.json new file mode 100644 index 0000000000..1c59d6b94e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/mock-manifest.json @@ -0,0 +1,21 @@ +{ + "create-vp-test-generated-setup": { + "name": "create-vp-test-generated-setup", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "name": "create-vp-test-generated-setup", + "version": "1.0.0", + "bin": { + "create-vp-test-generated-setup": "index.cjs" + }, + "dist": { + "tarball": "{REGISTRY}/create-vp-test-generated-setup/-/create-vp-test-generated-setup-1.0.0.tgz", + "integrity": "sha512-eg95ZfZTfRmqgWms3s+wI5sd8Jl9lMMYWcHsfWtdKKP2hEeIpWUlHAuELDWc/3vyUi8KZDB6edDGwZtIZ2w+Zw==" + } + } + } + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots.toml new file mode 100644 index 0000000000..8fe01842bf --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots.toml @@ -0,0 +1,19 @@ +# The fake `create-vp-test-generated-setup` CLI writes AGENTS.md and +# .vscode/settings.json and runs `git init`, so the setup prompts that follow +# it must react to those files. +[[case]] +name = "create_remote_generated_setup" +vp = "local" +steps = [ + { argv = ["vp", "create", "vp-test-generated-setup", "--package-manager", "pnpm", "--", "my-app"], comment = "after the create CLI: no git prompt, a hooks prompt, then append/skip for the generated AGENTS.md", snapshot = false, continue-on-failure = true, interactions = [ + { "expect-milestone" = "confirm:confirm:yes" }, + { "write-key" = "enter" }, + { "expect-milestone" = "select:select:1" }, + { "write-key" = "up" }, + { "expect-milestone" = "select:select:0" }, + { "write-key" = "enter" }, + ] }, + { argv = ["vpt", "grep-file", "my-app/AGENTS.md", ""], comment = "Vite+ instructions are appended to the generated AGENTS.md", continue-on-failure = true }, + { argv = ["vpt", "print-file", "my-app/.vscode/settings.json"], comment = "generated editor config is merged without a prompt", continue-on-failure = true }, + { argv = ["vpt", "stat-file", "my-app/.vite-hooks/pre-commit", "--assert", "file"], comment = "hooks are set up in the repository the create CLI initialized", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots/create_remote_generated_setup.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots/create_remote_generated_setup.md new file mode 100644 index 0000000000..5ef56cd9ea --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/snapshots/create_remote_generated_setup.md @@ -0,0 +1,53 @@ +# create_remote_generated_setup + +## `vp create vp-test-generated-setup --package-manager pnpm -- my-app` + +after the create CLI: no git prompt, a hooks prompt, then append/skip for the generated AGENTS.md + + +## `vpt grep-file my-app/AGENTS.md '<'\!'--VITE PLUS START-->'` + +Vite+ instructions are appended to the generated AGENTS.md + +``` +my-app/AGENTS.md: found "" +``` + +## `vpt print-file my-app/.vscode/settings.json` + +generated editor config is merged without a prompt + +``` +{ + "editor.tabSize": 2, + "npm.scriptRunner": "vp", + "editor.defaultFormatter": "oxc.oxc-vscode", + "[javascript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "oxc.oxc-vscode" + }, + "oxc.disableNestedConfig": true, + "oxc.fmt.disableNestedConfig": true, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "editor.codeActionsOnSave": { + "source.fixAll.oxc": "explicit" + } +} +``` + +## `vpt stat-file my-app/.vite-hooks/pre-commit --assert file` + +hooks are set up in the repository the create CLI initialized + +``` +my-app/.vite-hooks/pre-commit: file +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/tarballs/create-vp-test-generated-setup-1.0.0.tgz b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_remote_generated_setup/tarballs/create-vp-test-generated-setup-1.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..185e902a5f31fbd1122db021cbd107ad9755993d GIT binary patch literal 727 zcmV;|0x10-iwFP!00002|Ls;$PunmM?sI>|txR&OwW$kjky7z6u?=mS*h6`vXqfmK z4pQ5*od7HS?~{`j6&Mnm7}BJE53zIp?tFIcKE;USQl!`=CGk2+E?n*9!8zX@4B&Tm z&UtUQ*N589V90yD;gI*C#(O=!JA@jqy#jLQg{^V^0*B4;^dq%`Q2EZpmVG>TKY}hBj$8%*$99B zh!Bi{vgAmzum{V?k+5lTynO<adp&Y9pGUQQ?*HS;k06{b{*i$_}gLR zyd{;=^d|TK%*!W$5;Kge7-zy^cnxt%zW`&Y5Pq4%%II$l#=vG;<{7e;)g_jUcQMRC zVi5x?OqeiOtQZoD4`@(HR0(0pWCeNbm@IcJ29^nr&fj?~2bE`8HQNqD3*G^nDAuk& z^G3oJQKHq9(#X5&P8u{b+WIorDJk49mtg@@t7q$3jV5ZM@VX>RoQgd2_gtFuMQb!s zjo)YUFqQWQ9}kbl(M*PK%(mbNXC@pPNI7rw#FJKTqS!M}wknu5Fo)igO5M}neGUvI zd2J&v&c^f;Sq$5-Xjk_O+xPBuy`h>cCC}P$%t(=EC!so%D;YzXg!7Vg42)8xEwZvs zLvQE|qfDpG(}Ptbz}vYbyEtu)>VMP!%fjxi7x+KW|6#xXl>YM_K76GAgZ^Nn|F3}{ zS&bl2jTjn cancelAndExit(), - }); - selectedAgentTargetPaths = agentSelection.targetPaths; - shouldWriteCopilotSetupWorkflow = agentSelection.selectedAgents.some( - (agent) => agent.id === COPILOT_AGENT_ID, - ); - } - const shouldConfigureEditors = shouldConfigureEditorsForCreate({ editor: options.editor, isMonorepo, }); - if (shouldConfigureEditors) { - const existingEditors = - options.editor || !options.interactive + + // Agent, editor, git and hook choices. Direct scaffolds ask up front. A + // `create-xxx` CLI asks after it ran (`generatedProjectPath`), so its own + // prompts come first and the answers reflect the files it generated. + let shouldSetupGit = false; + const resolveProjectSetup = async (generatedProjectPath?: string) => { + const agentInstructionsRoot = + generatedProjectPath && !isMonorepo ? generatedProjectPath : workspaceInfoOptional.rootDir; + const existingAgentTargetPaths = + options.agent !== undefined || !options.interactive ? undefined - : detectExistingEditors(workspaceInfoOptional.rootDir); - selectedEditors = - existingEditors ?? - (await selectEditors({ + : detectExistingAgentTargetPaths(agentInstructionsRoot); + if (existingAgentTargetPaths !== undefined) { + selectedAgentTargetPaths = existingAgentTargetPaths; + } else { + const agentSelection = await selectAgentTargets({ interactive: options.interactive, - editor: options.editor, + agent: options.agent, onCancel: () => cancelAndExit(), - })); - } + }); + selectedAgentTargetPaths = agentSelection.targetPaths; + shouldWriteCopilotSetupWorkflow = agentSelection.selectedAgents.some( + (agent) => agent.id === COPILOT_AGENT_ID, + ); + } + + if (shouldConfigureEditors) { + const existingEditors = + options.editor || !options.interactive + ? undefined + : detectExistingEditors(generatedProjectPath ?? workspaceInfoOptional.rootDir); + selectedEditors = + existingEditors ?? + (await selectEditors({ + interactive: options.interactive, + editor: options.editor, + onCancel: () => cancelAndExit(), + })); + } - const shouldSetupGit = await resolveGitInit(options, isMonorepo); - if (!isMonorepo && (!options.interactive || shouldSetupGit || options.hooks === true)) { - shouldSetupHooks = await promptGitHooks(options); + // A `create-xxx` CLI may have initialized the repository itself. + const hasGitRepository = + generatedProjectPath !== undefined && fs.existsSync(path.join(generatedProjectPath, '.git')); + shouldSetupGit = !hasGitRepository && (await resolveGitInit(options, isMonorepo)); + if ( + !isMonorepo && + (!options.interactive || hasGitRepository || shouldSetupGit || options.hooks === true) + ) { + shouldSetupHooks = await promptGitHooks(options); + } + }; + + if (isDirectScaffoldTemplate) { + await resolveProjectSetup(); } const createProgress = @@ -1079,6 +1098,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h } const fullPath = path.join(workspaceInfo.rootDir, projectDir); + if (!isDirectScaffoldTemplate) { + await resolveProjectSetup(fullPath); + } // Register a scaffolded generator in `create.templates` so it appears in the // `vp create` picker (and resolves by name) without a manual config edit. @@ -1140,7 +1162,8 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h await writeEditorConfigs({ projectRoot: fullPath, editorId: selectedEditors, - interactive: options.interactive, + // Editor configs a `create-xxx` CLI generated are merged without asking per file. + interactive: options.interactive && isDirectScaffoldTemplate, silent: compactOutput, extraVsCodeSettings: { 'npm.scriptRunner': 'vp' }, packageManager, From 9f763e841d4c8a6ddb30fedb88a8de2a0d614514 Mon Sep 17 00:00:00 2001 From: Liang Date: Thu, 12 Nov 2026 06:27:17 +0800 Subject: [PATCH 3/3] wip --- docs/guide/create.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/guide/create.md b/docs/guide/create.md index 9441169754..1c36b98261 100644 --- a/docs/guide/create.md +++ b/docs/guide/create.md @@ -74,8 +74,6 @@ Vite+ ships with these built-in templates: Run `vp create --list` to see the built-in templates and the common shorthand templates Vite+ recognizes. -A third-party template runs right after the package manager is chosen. `vp create` asks about agent instructions, editor configs, git, and commit hooks once the template finishes, based on the files it generated: an existing agent instructions file can be appended to or kept, existing editor configs are merged, and an existing git repository is reused. - After a third-party template runs, `vp create` migrates the generated project to Vite+. When a standalone project already depends on `vite-plus`, `vp create` skips this migration and keeps the template's tooling choices. ## Options