From 40b8370da40906cc4d8e748b16d554c3c0bfa537 Mon Sep 17 00:00:00 2001 From: Liang Date: Tue, 13 Oct 2026 21:30:53 +0800 Subject: [PATCH 1/6] feat(create): use the package manager that launched vp create When vp create runs through a package manager (`pnpm create vite-plus`, `npx vite-plus create`), read `npm_config_user_agent` to pick that package manager instead of prompting, and suggest running vp through it in the next command, since the global CLI may not be installed. --- .../snapshots.toml | 9 ++++++++ ...ommand_application_from_package_manager.md | 14 +++++++++++++ .../cli/src/create/__tests__/utils.spec.ts | 19 ++++++++++++++++- packages/cli/src/create/bin.ts | 14 ++++++++++--- packages/cli/src/create/utils.ts | 21 +++++++++++++++++++ 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots/create_next_command_application_from_package_manager.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots.toml index 27c73be16d..4581abc19a 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots.toml @@ -5,6 +5,15 @@ steps = [ { argv = ["vp", "create", "vite:application", "--no-interactive"], comment = "verify next command suggests vp run", continue-on-failure = true }, ] +[[case]] +name = "create_next_command_application_from_package_manager" +vp = "local" +comment = "`npm create vite-plus` stamps npm's user agent: create with npm and run vp through it, since the global CLI may be missing." +env = { npm_config_user_agent = "npm/11.0.0 node/v24.0.0 linux x64 workspaces/false" } +steps = [ + { argv = ["vp", "create", "vite:application", "--no-interactive"], comment = "verify npm is selected and the next command uses npx", continue-on-failure = true }, +] + [[case]] name = "create_git_command_application" vp = "global" diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots/create_next_command_application_from_package_manager.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots/create_next_command_application_from_package_manager.md new file mode 100644 index 0000000000..3bc1f29655 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_next_command_application/snapshots/create_next_command_application_from_package_manager.md @@ -0,0 +1,14 @@ +# create_next_command_application_from_package_manager + +`npm create vite-plus` stamps npm's user agent: create with npm and run vp through it, since the global CLI may be missing. + +## `vp create vite:application --no-interactive` + +verify npm is selected and the next command uses npx + +``` +● Using default package name: vite-plus-application +◇ Scaffolded vite-plus-application with Vite application +• Node npm +→ Next: cd vite-plus-application && npx vp run +``` diff --git a/packages/cli/src/create/__tests__/utils.spec.ts b/packages/cli/src/create/__tests__/utils.spec.ts index d935aa240a..fb48cec86c 100644 --- a/packages/cli/src/create/__tests__/utils.spec.ts +++ b/packages/cli/src/create/__tests__/utils.spec.ts @@ -2,10 +2,11 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { deriveDefaultPackageName, + detectLauncherPackageManager, ensureDefaultGitignoreEntries, ensureGitignoreVsCodeEditorConfigs, formatTargetDir, @@ -41,6 +42,22 @@ describe('editor configuration policy', () => { }); }); +describe('detectLauncherPackageManager', () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it('detects the package manager that launched the process, but not vp', () => { + vi.stubEnv('npm_config_user_agent', 'pnpm/11.0.0 npm/? node/v24.0.0 darwin arm64'); + expect(detectLauncherPackageManager()).toBe('pnpm'); + // `vp run` and `vp exec` prefix the agent with vp, where the global CLI is already available. + vi.stubEnv('npm_config_user_agent', 'vp/1.1.0 npm/11.0.0 node/v24.0.0 darwin arm64'); + expect(detectLauncherPackageManager()).toBeUndefined(); + vi.stubEnv('npm_config_user_agent', undefined); + expect(detectLauncherPackageManager()).toBeUndefined(); + }); +}); + describe('formatTargetDir', () => { it('should format "." as current directory with empty package name', () => { expect(formatTargetDir('.')).toEqual({ diff --git a/packages/cli/src/create/bin.ts b/packages/cli/src/create/bin.ts index 6271fc3ae1..355f7838e9 100644 --- a/packages/cli/src/create/bin.ts +++ b/packages/cli/src/create/bin.ts @@ -88,9 +88,11 @@ import { import { BuiltinTemplate, TemplateType } from './templates/types.ts'; import { deriveDefaultPackageName, + detectLauncherPackageManager, ensurePnpmCreateCatalogEntries, ensureDefaultGitignoreEntries, ensureGitignoreVsCodeEditorConfigs, + formatLocalVpCommand, formatTargetDir, shouldConfigureEditorsForCreate, } from './utils.ts'; @@ -672,13 +674,15 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h } } - // Resolve package manager: existing monorepo > CLI flag > ambient detection > prompt/default + // Resolve package manager: existing monorepo > CLI flag > ambient detection > launcher > prompt/default const requestedPackageManager = options.packageManager; const detectedPackageManager = workspaceInfoOptional.packageManager; + const launcherPackageManager = detectLauncherPackageManager(); const packageManager = (isMonorepo ? (detectedPackageManager ?? requestedPackageManager) : (requestedPackageManager ?? detectedPackageManager)) ?? + launcherPackageManager ?? (await selectPackageManager(options.interactive, compactOutput)); const packageManagerVersion = packageManager === detectedPackageManager @@ -699,6 +703,10 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h packageManager, downloadPackageManager: downloadResult, }; + // Only the global CLI puts `vp` on PATH; launches through a package manager reach it through the project's package manager. + const vpRunCommand = launcherPackageManager + ? formatLocalVpCommand(packageManager, 'vp run') + : 'vp run'; const existingAgentTargetPaths = options.agent !== undefined || !options.interactive @@ -975,7 +983,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h description: describeScaffold(selectedTemplateName, selectedTemplateArgs), gitInitialized, installSummary, - nextCommand: getNextCommand(projectDir, 'vp run'), + nextCommand: getNextCommand(projectDir, vpRunCommand), packageManager: workspaceInfo.packageManager, packageManagerVersion: workspaceInfo.downloadPackageManager.version, projectDir, @@ -1333,7 +1341,7 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h description: describeScaffold(selectedTemplateName, selectedTemplateArgs), gitInitialized, installSummary, - nextCommand: getNextCommand(projectDir, 'vp run'), + nextCommand: getNextCommand(projectDir, vpRunCommand), packageManager: workspaceInfo.packageManager, packageManagerVersion: workspaceInfo.downloadPackageManager.version, projectDir, diff --git a/packages/cli/src/create/utils.ts b/packages/cli/src/create/utils.ts index 85f69c320a..83fbddb9e2 100644 --- a/packages/cli/src/create/utils.ts +++ b/packages/cli/src/create/utils.ts @@ -4,6 +4,7 @@ import path from 'node:path'; import validateNpmPackageName from 'validate-npm-package-name'; import { parseDocument } from 'yaml'; +import { PackageManager } from '../types/index.ts'; import { VITE_PLUS_NAME, VITE_PLUS_OVERRIDE_PACKAGES, @@ -76,6 +77,26 @@ export function shouldConfigureEditorsForCreate({ return hasExplicitEditorOptIn(editor); } +// Package managers stamp `npm_config_user_agent` (`pnpm/11.0.0 npm/? node/v24.0.0 ...`) on what they launch, e.g. `pnpm create vite-plus`; `vp run` prefixes it with `vp/`, so it never matches. +export function detectLauncherPackageManager(): PackageManager | undefined { + const name = process.env.npm_config_user_agent?.split('/')[0]; + return Object.values(PackageManager).find((packageManager) => packageManager === name); +} + +export function formatLocalVpCommand(packageManager: PackageManager, command: string) { + switch (packageManager) { + case PackageManager.pnpm: + return `pnpm exec ${command}`; + case PackageManager.yarn: + return `yarn exec ${command}`; + case PackageManager.bun: + return `bunx ${command}`; + case PackageManager.npm: + default: + return `npx ${command}`; + } +} + // Helper functions for file operations export function copy(src: string, dest: string) { const stat = fs.statSync(src); From 52cb0565e523a9ea19c4080d8e0a0697ca2628dc Mon Sep 17 00:00:00 2001 From: Liang Date: Tue, 13 Oct 2026 21:30:54 +0800 Subject: [PATCH 2/6] feat: add create-vite-plus package A thin wrapper that runs `vp create` from the vite-plus package, so `pnpm create vite-plus`, `npm create vite-plus`, `yarn create vite-plus`, and `bun create vite-plus` work without the global CLI. Refs #2602 --- packages/create-vite-plus/LICENSE | 22 +++++++++++++ packages/create-vite-plus/README.md | 19 +++++++++++ .../create-vite-plus/bin/create-vite-plus | 11 +++++++ packages/create-vite-plus/package.json | 32 +++++++++++++++++++ pnpm-lock.yaml | 6 ++++ 5 files changed, 90 insertions(+) create mode 100644 packages/create-vite-plus/LICENSE create mode 100644 packages/create-vite-plus/README.md create mode 100755 packages/create-vite-plus/bin/create-vite-plus create mode 100644 packages/create-vite-plus/package.json diff --git a/packages/create-vite-plus/LICENSE b/packages/create-vite-plus/LICENSE new file mode 100644 index 0000000000..2efd4ba7eb --- /dev/null +++ b/packages/create-vite-plus/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026-present, VoidZero Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/packages/create-vite-plus/README.md b/packages/create-vite-plus/README.md new file mode 100644 index 0000000000..7650dc4737 --- /dev/null +++ b/packages/create-vite-plus/README.md @@ -0,0 +1,19 @@ +# create-vite-plus + +Create a new [Vite+](https://viteplus.dev) project without installing the global `vp` CLI. + +```bash +pnpm create vite-plus +npm create vite-plus@latest +yarn create vite-plus +bun create vite-plus +``` + +This runs [`vp create`](https://viteplus.dev/guide/create) from the `vite-plus` package and uses the package manager that ran it, unless you pass `--package-manager` or create the project inside an existing workspace. Arguments are forwarded unchanged, so the first argument is the template: + +```bash +pnpm create vite-plus vite:application --directory my-app +pnpm create vite-plus vite -- --template react-ts +``` + +npm needs `--` before options meant for `vp create`, for example `npm create vite-plus@latest -- vite:library --no-interactive`. diff --git a/packages/create-vite-plus/bin/create-vite-plus b/packages/create-vite-plus/bin/create-vite-plus new file mode 100755 index 0000000000..c7c924bec6 --- /dev/null +++ b/packages/create-vite-plus/bin/create-vite-plus @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +import module from 'node:module'; +if (module.enableCompileCache) { + module.enableCompileCache(); +} +// `pnpm create vite-plus ` runs this bin with ``; the CLI entry +// dispatches on the first argument, so this becomes `vp create `. +process.argv.splice(2, 0, 'create'); +// Dynamic import so it runs AFTER enableCompileCache and the argv rewrite. +await import('vite-plus/bin'); diff --git a/packages/create-vite-plus/package.json b/packages/create-vite-plus/package.json new file mode 100644 index 0000000000..09d88985fe --- /dev/null +++ b/packages/create-vite-plus/package.json @@ -0,0 +1,32 @@ +{ + "name": "create-vite-plus", + "version": "1.1.0", + "description": "Create a new Vite+ project", + "keywords": [ + "vite-plus" + ], + "homepage": "https://viteplus.dev/guide/create", + "bugs": { + "url": "https://github.com/voidzero-dev/vite-plus/issues" + }, + "license": "MIT", + "author": "VoidZero Inc.", + "repository": { + "type": "git", + "url": "git+https://github.com/voidzero-dev/vite-plus.git", + "directory": "packages/create-vite-plus" + }, + "bin": { + "create-vite-plus": "./bin/create-vite-plus" + }, + "files": [ + "bin" + ], + "type": "module", + "dependencies": { + "vite-plus": "workspace:*" + }, + "engines": { + "node": "^22.18.0 || ^24.11.0 || >=26.0.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6979c760d8..436935e272 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -758,6 +758,12 @@ importers: specifier: ~2.3.3 version: 2.3.3 + packages/create-vite-plus: + dependencies: + vite-plus: + specifier: workspace:vite-plus@* + version: link:../cli + packages/prompts: dependencies: '@clack/core': From 9e769606ce2014930832b6acdd13f9ebdeae8827 Mon Sep 17 00:00:00 2001 From: Liang Date: Tue, 13 Oct 2026 21:30:54 +0800 Subject: [PATCH 3/6] docs: document create-vite-plus --- docs/guide/create.md | 34 ++++++++++++++++++++++++++++++++++ docs/guide/local-cli.md | 8 ++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/docs/guide/create.md b/docs/guide/create.md index 202294c691..30f60f7592 100644 --- a/docs/guide/create.md +++ b/docs/guide/create.md @@ -20,6 +20,40 @@ vp create