Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .claude/skills/release-manager/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ Auto-merge being enabled is not a completed merge. Confirm `mergedAt` and the me
-q '.[] | "\(.environment.name) can_approve=\(.current_user_can_approve) reviewers=\([.reviewers[]?.reviewer.login] | join(","))"'
```

4. `Release`: publishes the NAPI bindings (`@voidzero-dev/vite-plus-<platform>`) and standalone CLI packages (`@voidzero-dev/vite-plus-cli-<platform>`, via `packages/cli/publish-native-addons.ts`), then `@voidzero-dev/vite-plus-core` and `vite-plus` to npm (`--tag latest`). Each dependency tier waits for npm propagation before publication advances. It then creates the `vX.Y.Z` GitHub release (draft, with installer/binary assets, then undrafted). The generated body has only Published Packages and Installation sections.
4. `Release`: publishes the NAPI bindings (`@voidzero-dev/vite-plus-<platform>`) and standalone CLI packages (`@voidzero-dev/vite-plus-cli-<platform>`, via `packages/cli/publish-native-addons.ts`), then `@voidzero-dev/vite-plus-core`, `vite-plus`, and `create-vite-plus` to npm (`--tag latest`). Each dependency tier waits for npm propagation before publication advances. It then creates the `vX.Y.Z` GitHub release (draft, with installer/binary assets, then undrafted). The generated body has only Published Packages and Installation sections.
5. `publish-docker`: multi-arch toolchain image to `ghcr.io/voidzero-dev/vite-plus`, after npm publish (the image installs vp from npm).
6. `deploy-docs`: deploys the production docs after a stable release is published. It is skipped for prereleases. When a prerelease is published to `latest` and its notes or CLI messages link to docs that production does not serve yet, ask the release manager whether to run `gh workflow run deploy-docs.yml --ref <ref>` once the `Release` job is publishing. Use the `vX.Y.Z` tag, or `main` while it still points at the release commit, and confirm the run's head SHA. The `vp` version that builds the docs does not need to match the release: the site and its install scripts come from the checked-out commit. The job authenticates with the `VOID_TOKEN` repository secret. When that token has expired, the job fails with `` `VOID_TOKEN` is invalid or expired `` and `discord-notify` is skipped. Ask someone with access to rotate the secret, then run `gh run rerun <run-id> --failed` to rerun the docs deployment and the Discord notification.
7. `discord-notify`: announces to Discord after Docker publishing and docs deployment succeed (docs are skipped for prereleases).
Expand Down Expand Up @@ -446,6 +446,7 @@ The full package document can update before npm's separately cached installation
```bash
npm view vite-plus version # X.Y.Z
npm view @voidzero-dev/vite-plus-core version # X.Y.Z
npm view create-vite-plus version # X.Y.Z
npm view @voidzero-dev/vite-plus-cli-darwin-arm64 version # X.Y.Z, spot-check a native platform package
npm view vite-plus dist-tags.latest # X.Y.Z
docker run --rm ghcr.io/voidzero-dev/vite-plus:X.Y.Z vp --version
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/prepare_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
update_json packages/cli/package.json
update_json packages/core/package.json
update_json packages/prompts/package.json
update_json packages/create-vite-plus/package.json
update_toml packages/cli/binding/Cargo.toml
update_toml crates/vp_global_cli/Cargo.toml

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/publish-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ jobs:
packages/cli
packages/core
packages/prompts
packages/create-vite-plus
# Must match the upload step in publish-preview-register.yml, which
# validates the same batch from main.
workspace-packages: vite-plus,create-vite-plus,@voidzero-dev/vite-plus-*

- name: Upload packed packages
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ jobs:
"vite-plus@${VERSION}"
"@voidzero-dev/vite-plus-prompts@${VERSION}"

# create-vite-plus pins vite-plus via workspace:* (rewritten to this exact
# version), so it publishes only after vite-plus is installable.
- name: Publish create-vite-plus
run: >-
node ./.github/scripts/publish-npm-package.ts
"create-vite-plus@${VERSION}"
--
pnpm publish --filter=./packages/create-vite-plus --tag latest --access public --no-git-checks
Comment thread
fengmk2 marked this conversation as resolved.

- name: Create release body
env:
REPOSITORY: ${{ github.repository }}
Expand All @@ -219,6 +228,7 @@ jobs:

- \`@voidzero-dev/vite-plus-core@${VERSION}\`
- \`vite-plus@${VERSION}\`
- \`create-vite-plus@${VERSION}\`

### Installation

Expand Down Expand Up @@ -375,6 +385,7 @@ jobs:
**Published Packages:**
• @voidzero-dev/vite-plus-core@${{ env.VERSION }}
• vite-plus@${{ env.VERSION }}
• create-vite-plus@${{ env.VERSION }}

**Docker (GHCR):**
• `docker pull ${{ env.IMAGE }}:${{ env.VERSION }}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <version> npm <version>
→ Next: cd vite-plus-application && npx vp run
```
34 changes: 34 additions & 0 deletions docs/guide/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,40 @@ vp create <template>
vp create <template> -- <template-options>
```

### Without the Global CLI

If you haven't installed [the global `vp`](/guide/global-cli) yet. Vite+ provides `create-vite-plus` package running `vp create` through your package manager.

::: code-group

```bash [pnpm]
pnpm create vite-plus
```

```bash [npm]
npm create vite-plus@latest
```

```bash [Yarn]
yarn create vite-plus
```

```bash [Bun]
bun create vite-plus
```

:::

The new project uses the package manager that ran the command, unless you pass `--package-manager` or create it inside an existing workspace. Arguments are forwarded to `vp create` unchanged, so the first argument is the template, not the target directory:

```bash
pnpm create vite-plus vite:application --directory my-app
```

If you are using npm, there is a `--` needed before options meant for `vp create`, for example `npm create vite-plus@latest -- vite:application --directory my-app`.
Comment thread
fengmk2 marked this conversation as resolved.

After creating the project, run `vp` through your package manager, for example `pnpm exec vp run`.

## Built-in Templates

Vite+ ships with these built-in templates:
Expand Down
8 changes: 4 additions & 4 deletions docs/guide/local-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ For most of use cases, we recommend to use Vite+ cli to install in a project or
::: code-group

```bash [pnpm]
pnpm dlx --package=vite-plus vp create
pnpm create vite-plus
```

```bash [npm]
npx --package=vite-plus vp create
npm create vite-plus@latest
```

```bash [Yarn]
yarn dlx --package vite-plus vp create
yarn create vite-plus
```

```bash [Bun]
bunx --package vite-plus vp create
bun create vite-plus
```

:::
Expand Down
19 changes: 18 additions & 1 deletion packages/cli/src/create/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/create/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ import {
import { BuiltinTemplate, TemplateType } from './templates/types.ts';
import {
deriveDefaultPackageName,
detectLauncherPackageManager,
ensurePnpmCreateCatalogEntries,
ensureDefaultGitignoreEntries,
ensureGitignoreVsCodeEditorConfigs,
formatLocalVpCommand,
formatTargetDir,
shouldConfigureEditorsForCreate,
} from './utils.ts';
Expand Down Expand Up @@ -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
Expand All @@ -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')
Comment thread
fengmk2 marked this conversation as resolved.
: 'vp run';

const existingAgentTargetPaths =
options.agent !== undefined || !options.interactive
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions packages/cli/src/create/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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/<version>`, 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);
Expand Down
22 changes: 22 additions & 0 deletions packages/create-vite-plus/LICENSE
Original file line number Diff line number Diff line change
@@ -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.

19 changes: 19 additions & 0 deletions packages/create-vite-plus/README.md
Original file line number Diff line number Diff line change
@@ -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`.
11 changes: 11 additions & 0 deletions packages/create-vite-plus/bin/create-vite-plus
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env node

import module from 'node:module';
if (module.enableCompileCache) {
module.enableCompileCache();
}
// `pnpm create vite-plus <args>` runs this bin with `<args>`; the CLI entry
// dispatches on the first argument, so this becomes `vp create <args>`.
process.argv.splice(2, 0, 'create');
// Dynamic import so it runs AFTER enableCompileCache and the argv rewrite.
await import('vite-plus/bin');
32 changes: 32 additions & 0 deletions packages/create-vite-plus/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading