# Vite+ Package Manager Integration: How It Works with pnpm, npm, and Yarn

> Discover how Vite+ seamlessly integrates with pnpm, npm, and Yarn. Learn how Vite+ detects your package manager and leverages its Rust core for efficient command execution.

- Repository: [VoidZero/vite-plus](https://github.com/voidzero-dev/vite-plus)
- Tags: how-to-guide
- Published: 2026-03-16

---

**Vite+ abstracts pnpm, npm, and Yarn behind a unified interface by detecting the active manager from configuration files, prompting users when uncertain, and delegating all CLI commands to a Rust core that invokes the appropriate native binary.**

Vite+ (vite-plus) from the voidzero-dev/vite-plus repository treats package managers as interchangeable backends while preserving each tool's native workspace capabilities. The integration architecture in [`packages/cli/src/utils/workspace.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/utils/workspace.ts) ensures that commands like `vp install` work identically whether your monorepo uses pnpm workspaces, npm workspaces, or Yarn's plug-and-play configuration.

## Automatic Package Manager Detection

Vite+ detects the active package manager by reading the filesystem via a Rust NAPI binding before any user interaction occurs. In [`packages/cli/src/utils/workspace.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/utils/workspace.ts) (lines 30-55), the `detectWorkspace()` function calls `detectWorkspaceBinding()` to scan for manager-specific configuration files.

The detection hierarchy follows this priority:

- **pnpm**: Presence of [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml)
- **Yarn**: Presence of [`.yarnrc.yml`](https://github.com/voidzero-dev/vite-plus/blob/main/.yarnrc.yml) or Yarn-specific workspace metadata
- **npm**: Presence of `workspaces` array in [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) (fallback when neither pnpm nor Yarn is detected)

```typescript
export async function detectWorkspace(rootDir: string): Promise<WorkspaceInfoOptional> {
  const bindingResult = await detectWorkspaceBinding(rootDir);   // ← Rust side reads the FS
  const result: WorkspaceInfoOptional = { … };

  // The binding tells us which manager is present
  if (bindingResult.packageManagerName) {
    result.packageManager = bindingResult.packageManagerName as PackageManager;
  }
  …
}

```

The resulting `WorkspaceInfo` object is cached and used throughout the CLI session to determine which configuration files to read and which binaries to invoke.

## Interactive Prompts and Default Behavior

When detection fails or when initializing a new project, Vite+ prompts the user to select a package manager. The `selectPackageManager()` function in [`packages/cli/src/utils/prompts.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/utils/prompts.ts) (lines 20-45) presents pnpm as the recommended option, followed by Yarn and npm.

```typescript
export async function selectPackageManager(interactive?: boolean, silent = false) {
  if (interactive) {
    const selected = await prompts.select({
      message: 'Which package manager would you like to use?',
      options: [
        { value: PackageManager.pnpm, hint: 'recommended' },
        { value: PackageManager.yarn },
        { value: PackageManager.npm },
      ],
      initialValue: PackageManager.pnpm,
    });
    …
    return selected;
  } else {
    // CI / non‑interactive → pnpm is the default
    return PackageManager.pnpm;
  }
}

```

For non-interactive environments such as CI pipelines, Vite+ defaults to **pnpm** without prompting. Once selected, the specified manager is downloaded via `downloadPackageManagerBinding()` if not already present on the system.

## Workspace Configuration Management

Vite+ normalizes workspace mutations across all three package managers while respecting their native file formats. The utilities `updateWorkspaceConfig()` and `updatePackageJsonWithDeps()` in [`workspace.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.ts) (lines 48-73) handle the specifics:

**For pnpm**, Vite+ edits [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) directly:

```typescript
if (workspaceInfo.packageManager === PackageManager.pnpm) {
  editYamlFile(path.join(workspaceInfo.rootDir, 'pnpm-workspace.yaml'), doc => {
    let packages = doc.getIn(['packages']) as YAMLSeq<Scalar<string>>;
    packages?.add(new Scalar(pattern));
  });
}

```

**For npm and Yarn**, Vite+ modifies the `workspaces` array in [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json):

```typescript
editJsonFile<{ workspaces?: string[] }>(path.join(workspaceInfo.rootDir, 'package.json'), pkg => {
  pkg.workspaces = [...(pkg.workspaces || []), pattern];
});

```

When adding internal dependencies, Vite+ writes the `workspace:*` protocol to ensure local resolution:

```typescript
for (const dep of dependencies) {
  pkg[dependencyType][dep] = 'workspace:*';
}

```

## Project Scaffolding and Migration

The template system in [`packages/cli/src/create/templates/monorepo.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/create/templates/monorepo.ts) (lines 69-84) adapts generated files to the detected package manager. When scaffolding with Yarn, Vite+ removes stray [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) files and creates [`.yarnrc.yml`](https://github.com/voidzero-dev/vite-plus/blob/main/.yarnrc.yml) with appropriate settings. Conversely, pnpm-based projects receive the YAML workspace configuration while npm projects rely solely on [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) workspaces.

The migration engine ([`migrator.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/migrator.ts)) performs similar rewrites when converting existing projects, handling manager-specific features like pnpm's `catalog:` protocol or npm's `overrides`.

## Command Execution Architecture

All CLI commands (`vp install`, `vp run`, etc.) ultimately delegate to the Rust core via NAPI bindings. The `runCommandWithFspy()` function in [`packages/cli/src/create/command.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/create/command.ts) (lines 6-11) forwards requests to the native implementation, which then invokes the correct underlying binary:

- `pnpm install` for pnpm projects
- `npm install` for npm projects  
- `yarn install` for Yarn projects

This architecture ensures that Vite+ CLI syntax remains constant while leveraging each package manager's native performance characteristics and lockfile behavior.

## Summary

- **Detection**: `detectWorkspace()` in [`workspace.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.ts) identifies the active manager by scanning for [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml), [`.yarnrc.yml`](https://github.com/voidzero-dev/vite-plus/blob/main/.yarnrc.yml), or [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) workspaces via Rust bindings.
- **Selection**: `selectPackageManager()` in [`prompts.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/prompts.ts) defaults to pnpm in CI or prompts users interactively.
- **Configuration**: `updateWorkspaceConfig()` manages `workspace:*` dependencies and updates the correct configuration file for each manager.
- **Templates**: [`monorepo.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/monorepo.ts) generates manager-specific files and removes conflicting configurations during project creation.
- **Execution**: `runCommandWithFspy()` delegates to the Rust core, which executes the native package manager binary.

## Frequently Asked Questions

### How does Vite+ detect which package manager a project uses?

Vite+ calls the Rust binding `detectWorkspaceBinding()` from [`packages/cli/src/utils/workspace.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/utils/workspace.ts) to scan the repository root. It checks for [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) (pnpm), [`.yarnrc.yml`](https://github.com/voidzero-dev/vite-plus/blob/main/.yarnrc.yml) (Yarn), or the `workspaces` field in [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) (npm), storing the result in a cached `WorkspaceInfo` object.

### What happens if Vite+ cannot detect a package manager automatically?

The `selectPackageManager()` function in [`packages/cli/src/utils/prompts.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/utils/prompts.ts) prompts the user to choose pnpm, Yarn, or npm, with pnpm marked as recommended. In non-interactive environments like CI, it silently defaults to pnpm and downloads the binary if necessary.

### How does Vite+ handle workspace dependencies across different managers?

When adding internal packages, Vite+ calls `updatePackageJsonWithDeps()` to write `workspace:*` references in [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json), then calls `updateWorkspaceConfig()` to register the package in the appropriate workspace file—[`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) for pnpm, or the `workspaces` array for npm and Yarn.

### Which component actually executes package manager commands like install?

The `runCommandWithFspy()` function in [`packages/cli/src/create/command.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/src/create/command.ts) delegates all commands to the Rust core via NAPI bindings. The Rust layer then invokes the native CLI of the detected package manager, ensuring `vp install` translates to `pnpm install`, `npm install`, or `yarn install` as appropriate.