Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// bundled tsdown loaded them as external top-level packages, so `vp pack --exe`
// (and CSS bundling) failed with `Failed to import module "@tsdown/exe"`.
//
// They are now bundled into core, so this loads the bundled extension chunks
// They now ship in the CLI's private pack runtime, so this loads the bundled extension chunks
// directly to prove they resolve `tsdown/internal` against the bundled tsdown.
// `vp pack --exe` itself needs Node >= 25.7 (SEA), so it cannot run end-to-end
// in CI; this check is Node-version independent.
Expand All @@ -13,7 +13,8 @@ import path from 'node:path';
import { pathToFileURL } from 'node:url';

const require = createRequire(import.meta.url);
const packEntry = require.resolve('@voidzero-dev/vite-plus-core/pack');
const cliRequire = createRequire(require.resolve('vite-plus/package.json'));
const packEntry = cliRequire.resolve('#pack');
const tsdownDir = path.dirname(packEntry);

for (const chunk of ['tsdown-exe.js', 'tsdown-css.js']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The global flag reads the manifest paired with the global binary.
Vite+ toolchain (global)

vite-plus@<version>
├── bundles tsdown@<version>
├── bundles rolldown@<version>
│ ├── compiles oxc@<version>
│ └── compiles oxc-resolver@<version>
├── depends on @voidzero-dev/vite-plus-core@<version>
│ ├── bundles vite@<version>
│ │ └── uses rolldown@<version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The local CLI reads the manifest shipped in its vite-plus package.
Vite+ toolchain (local)

vite-plus@<version>
├── bundles tsdown@<version>
├── bundles rolldown@<version>
│ ├── compiles oxc@<version>
│ └── compiles oxc-resolver@<version>
├── depends on @voidzero-dev/vite-plus-core@<version>
│ ├── bundles vite@<version>
│ │ └── uses rolldown@<version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ a package without a runnable local CLI uses the global toolchain
Vite+ toolchain (global)

vite-plus@<version>
├── bundles tsdown@<version>
├── bundles rolldown@<version>
│ ├── compiles oxc@<version>
│ └── compiles oxc-resolver@<version>
├── depends on @voidzero-dev/vite-plus-core@<version>
│ ├── bundles vite@<version>
│ │ └── uses rolldown@<version>
Expand Down Expand Up @@ -74,6 +78,10 @@ Run `vp toolchain vite` to show these versions and relationships.
Vite+ toolchain (global)

vite-plus@<version>
├── bundles tsdown@<version>
├── bundles rolldown@<version>
│ ├── compiles oxc@<version>
│ └── compiles oxc-resolver@<version>
├── depends on @voidzero-dev/vite-plus-core@<version>
│ ├── bundles vite@<version>
│ │ └── uses rolldown@<version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,12 @@ error: Failed to resolve vite command: GenericFailure, Expected @voidzero-dev/vi

## `vp -C . pack packages/app/src/index.ts`

**Exit code:** 1

```
VITE+ - The Unified Toolchain for the Web

error: Failed to resolve pack command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found vite@8.2.2 at <workspace>/node_modules/.pnpm/vite@8.2.2/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
ℹ entry: packages/app/src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ error: Failed to resolve vite command: GenericFailure, Expected @voidzero-dev/vi

## `vp -C . pack packages/app/src/index.ts`

**Exit code:** 1

```
error: Failed to resolve pack command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found vite@8.2.2 at <workspace>/node_modules/.pnpm/vite@8.2.2/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
ℹ entry: packages/app/src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import { build } from 'vite-plus/pack';
const require = createRequire(import.meta.url);
const cliRequire = createRequire(require.resolve('vite-plus/package.json'));
assert.equal(createServer, cliRequire('vite').createServer);
assert.equal(build, cliRequire('vite/pack').build);
assert.equal(build, cliRequire('#pack').build);
assert.equal(require('./package.json').devDependencies.vite, undefined);
console.log('Bundled APIs resolve without a project Vite dependency');
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,28 @@ const cliRequire = createRequire(require.resolve('vite-plus/package.json'));
const project = require('./package.json');
assert.equal(project.overrides, undefined);
assert.equal(project.devDependencies.vite, 'npm:@voidzero-dev/vite-plus-core@latest');
// npm 11.11 accepts a peer's upstream Vite as satisfying the tagged alias.
assert.equal(require('vite/package.json').name, 'vite');
// npm may satisfy a tagged alias with an upstream Vite peer. Check the
// command against the installed package instead of depending on that choice.
const projectVite = require('vite/package.json');
assert.equal(cliRequire('vite/package.json').name, '@voidzero-dev/vite-plus-core');
assert.notEqual(require.resolve('vite/package.json'), cliRequire.resolve('vite/package.json'));

for (const args of [['dev'], ['pack', 'entry.js']]) {
const result = spawnSync('vp', args, { encoding: 'utf8', timeout: 30_000 });
assert.ifError(result.error);
const output = result.stdout + result.stderr;
assert.equal(result.status, 1, output);
const dev = spawnSync('vp', ['dev'], { encoding: 'utf8', timeout: 30_000 });
assert.ifError(dev.error);
const output = dev.stdout + dev.stderr;
if (projectVite.name === 'vite') {
assert.notEqual(require.resolve('vite/package.json'), cliRequire.resolve('vite/package.json'));
assert.equal(dev.status, 1, output);
assert.match(output, /Expected @voidzero-dev\/vite-plus-core@/);
assert.match(output, /but found vite@/);
assert.match(output, /Run `vp migrate` to align the Vite alias, then run `vp install`\./);
console.log(`vp ${args[0]} rejects the replaced npm alias and reports the repair command`);
} else {
assert.equal(projectVite.name, '@voidzero-dev/vite-plus-core');
assert.equal(projectVite.version, cliRequire('vite-plus/package.json').version);
assert.equal(dev.status, 0, output);
assert.match(output, /SSR environment identity and HTTP response passed/);
}
console.log('vp dev validates the installed package behind the tagged npm alias');

const pack = spawnSync('vp', ['pack', 'entry.js'], { encoding: 'utf8', timeout: 30_000 });
assert.ifError(pack.error);
assert.equal(pack.status, 0, pack.stdout + pack.stderr);
console.log('vp pack uses its private runtime with the tagged npm alias');
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ local-registry = true
comment = "CI can stamp the package version after building the CLI. The resolver must compare installed manifests rather than an inlined build-time version."
steps = [
{ argv = ["vp", "install", "--ignore-scripts"], snapshot = false },
{ argv = ["vpt", "json-edit", "node_modules/vite-plus/package.json", "version", "0.0.0"], snapshot = false },
{ argv = ["vpt", "json-edit", "node_modules/vite-plus/package.json", "dependencies.vite", "npm:@voidzero-dev/vite-plus-core@0.0.0"], snapshot = false },
{ argv = ["vpt", "json-edit", "node_modules/vite/package.json", "version", "0.0.0"], snapshot = false },
{ argv = ["node", "stamp-version.mjs"], snapshot = false },
["node", "check-api.mjs"],
["vp", "dev"],
["vp", "build"],
Expand Down Expand Up @@ -156,7 +154,7 @@ steps = [
]

[[case]]
name = "core_module_identity_npm_tagged_alias_rejected"
name = "core_module_identity_npm_tagged_alias"
vp = "global"
local-registry = true
steps = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# core_module_identity_npm_tagged_alias_rejected
# core_module_identity_npm_tagged_alias

## `vpt write-file package.json '{"name":"core-module-identity","private":true,"type":"module","packageManager":"npm@11.11.0","devDependencies":{"vite-plus":"latest","vite":"npm:@voidzero-dev/vite-plus-core@latest"}}
'`
Expand All @@ -10,6 +10,6 @@
## `node check-tagged-alias.mjs`

```
vp dev rejects the replaced npm alias and reports the repair command
vp pack rejects the replaced npm alias and reports the repair command
vp dev validates the installed package behind the tagged npm alias
vp pack uses its private runtime with the tagged npm alias
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ CI can stamp the package version after building the CLI. The resolver must compa
## `vp install --ignore-scripts`


## `vpt json-edit node_modules/vite-plus/package.json version 0.0.0`


## `vpt json-edit node_modules/vite-plus/package.json dependencies.vite npm:@voidzero-dev/vite-plus-core@0.0.0`


## `vpt json-edit node_modules/vite/package.json version 0.0.0`
## `node stamp-version.mjs`


## `node check-api.mjs`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const cliManifest = require.resolve('vite-plus/package.json');
const cliRequire = createRequire(cliManifest);
for (const manifest of new Set([
cliManifest,
require.resolve('vite/package.json'),
cliRequire.resolve('vite/package.json'),
])) {
const pkg = JSON.parse(readFileSync(manifest, 'utf8'));
pkg.version = '0.0.0';
if (pkg.name === 'vite-plus') {
pkg.dependencies.vite = 'npm:@voidzero-dev/vite-plus-core@0.0.0';
}
writeFileSync(manifest, JSON.stringify(pkg));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const value = 42;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ vp = "local"
steps = [
{ argv = ["vpt", "write-file", "node_modules/vite/package.json", "{\"name\":\"vite\",\"version\":\"8.2.2\"}\n"], snapshot = false },
{ argv = ["vp", "dev"], continue-on-failure = true },
["vp", "pack", "index.ts"],
["vp", "pack", "--no-config", "index.ts"],
]

[[case]]
Expand All @@ -13,5 +13,5 @@ vp = "local"
steps = [
{ argv = ["vpt", "write-file", "node_modules/vite/package.json", "{\"name\":\"@voidzero-dev/vite-plus-core\",\"version\":\"0.0.0-stale\"}\n"], snapshot = false },
{ argv = ["vp", "dev"], continue-on-failure = true },
["vp", "pack", "index.ts"],
["vp", "pack", "--no-config", "index.ts"],
]
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
error: Failed to resolve vite command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found @voidzero-dev/vite-plus-core@<version> at <workspace>/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
```

## `vp pack index.ts`

**Exit code:** 1
## `vp pack --no-config index.ts`

```
error: Failed to resolve pack command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found @voidzero-dev/vite-plus-core@<version> at <workspace>/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
ℹ entry: index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
error: Failed to resolve vite command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found vite@8.2.2 at <workspace>/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
```

## `vp pack index.ts`

**Exit code:** 1
## `vp pack --no-config index.ts`

```
error: Failed to resolve pack command: GenericFailure, Expected @voidzero-dev/vite-plus-core@<version>, but found vite@8.2.2 at <workspace>/node_modules/vite/package.json. Run `vp migrate` to align the Vite alias, then run `vp install`.
ℹ entry: index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import * as vite from 'vite';
import { ModuleRunner } from 'vite/module-runner';
import * as vp from 'vite-plus';
import { ModuleRunner as VpModuleRunner } from 'vite-plus/module-runner';
import { build as pack } from 'vite-plus/pack';

const require = createRequire(import.meta.url);
const cliRequire = createRequire(require.resolve('vite-plus/package.json'));
const vitestRequire = createRequire(cliRequire.resolve('vitest/package.json'));
const cliPkg = cliRequire('vite-plus/package.json');
assert.equal(cliPkg.dependencies['@voidzero-dev/vite-plus-core'], undefined);
assert.throws(() => cliRequire.resolve('vite/pack'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' });
assert.equal(pack, (await import(pathToFileURL(cliRequire.resolve('#pack')).href)).build);
assert.equal(vite.version, '8.3.4');
for (const name of [
'createServer',
'build',
'resolveConfig',
'DevEnvironment',
'isRunnableDevEnvironment',
'isFetchableDevEnvironment',
]) {
assert.equal(vite[name], vp[name], name);
assert.equal(require('vite-plus')[name], vite[name], name);
}
assert.equal(ModuleRunner, VpModuleRunner);
assert.equal(cliRequire.resolve('vite'), require.resolve('vite'));
assert.equal(vitestRequire.resolve('vite'), require.resolve('vite'));
if (process.env.LOCAL_VITE === '1') {
assert.equal(vp.ecosystemCiMarker, 'local-vite');
}
console.log('API exports and bundled Vitest use upstream Vite; pack uses its private bundled runtime');
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { cpSync, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';

const require = createRequire(import.meta.url);
const cliRoot = dirname(require.resolve('vite-plus/package.json'));
const upstream = dirname(require.resolve('vite/package.json'));
// Also cover the default strict behavior with real upstream Vite installed.
for (const command of ['dev', 'build', 'preview']) {
const local = process.argv[2] === 'local';
const args = local ? [join(cliRoot, 'bin/vp'), command] : [command];
const result = spawnSync(local ? process.execPath : 'vp', args, {
encoding: 'utf8', timeout: 30_000,
env: { ...process.env, VP_USE_PROJECT_VITE: '0' },
});
assert.ifError(result.error);
assert.equal(result.status, 1, result.stdout + result.stderr);
assert.match(result.stdout + result.stderr, /but found vite@8\.3\.4/);
}
console.log('Upstream Vite rejected without the opt-in before dev/build/preview');

const cliModules = join(cliRoot, 'node_modules');
const cliVite = join(cliModules, 'vite');
const temporary = mkdtempSync(join(cliRoot, '.vite-identity-'));
const original = join(temporary, 'original-vite');
mkdirSync(cliModules, { recursive: true });
const hadVite = existsSync(cliVite);
if (hadVite) {
renameSync(cliVite, original);
}

function checkRejected() {
for (const command of ['dev', 'build', 'preview']) {
const local = process.argv[2] === 'local';
const args = local ? [join(cliRoot, 'bin/vp'), command] : [command];
const result = spawnSync(local ? process.execPath : 'vp', args, {
encoding: 'utf8',
timeout: 30_000,
});
assert.ifError(result.error);
const output = result.stdout + result.stderr;
assert.equal(result.status, 1, output);
assert.match(output, /requires the project and vite-plus to resolve the same Vite package/);
assert.match(output, /Apply the same override to all `vite` dependencies/);
}
}

try {
// A CLI alias left on core must fail before its runtime is loaded.
mkdirSync(cliVite);
writeFileSync(join(cliVite, 'package.json'), JSON.stringify({
name: '@voidzero-dev/vite-plus-core',
version: require('vite-plus/package.json').version,
exports: { './package.json': './package.json' },
}));
checkRejected();
console.log('Partial Vite override rejected before dev/build/preview');
rmSync(cliVite, { recursive: true });

// A second physical copy has the same version and exports but different
// constructor identities. Version checks alone must not accept it.
cpSync(upstream, cliVite, { recursive: true });
checkRejected();
console.log('Separate Vite copies with the same version rejected before dev/build/preview');
} finally {
rmSync(cliVite, { recursive: true, force: true });
if (hadVite) {
renameSync(original, cliVite);
}
rmSync(temporary, { recursive: true, force: true });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import './style.css';

export const message: string = 'Packed with the private runtime';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1>Upstream Vite</h1>
<script type="module" src="./entry.ts"></script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vite-plus/test';
import * as vite from 'vite';
import * as vp from 'vite-plus';

test('uses the replacement Vite through public API exports', async () => {
expect(vp.version).toBe('8.3.4');
expect(vp.createServer).toBe(vite.createServer);
if (process.env.LOCAL_VITE === '1') {
expect(vp.ecosystemCiMarker).toBe('local-vite');
}
const server = await vp.createServer({
configFile: false,
server: { middlewareMode: true, ws: false },
});
try {
expect(vite.isRunnableDevEnvironment(server.environments.ssr)).toBe(true);
expect(vp.isRunnableDevEnvironment(server.environments.ssr)).toBe(true);
} finally {
await server.close();
}
});
Loading
Loading