From e9c6572be9cd3e57b1b6b3ae002525cfe92811bf Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Wed, 1 Apr 2026 08:15:33 +0200 Subject: [PATCH 1/4] feat(build): add vite-plus support --- .../build-info/src/build-systems/index.ts | 2 + .../src/build-systems/vite-plus.test.ts | 61 +++++++++ .../build-info/src/build-systems/vite-plus.ts | 36 +++++ .../vite_plus_setup/index.test.ts | 128 ++++++++++++++++++ .../src/plugins_core/vite_plus_setup/index.ts | 93 +++++++++++++ packages/build/src/steps/get.ts | 2 + 6 files changed, 322 insertions(+) create mode 100644 packages/build-info/src/build-systems/vite-plus.test.ts create mode 100644 packages/build-info/src/build-systems/vite-plus.ts create mode 100644 packages/build/src/plugins_core/vite_plus_setup/index.test.ts create mode 100644 packages/build/src/plugins_core/vite_plus_setup/index.ts diff --git a/packages/build-info/src/build-systems/index.ts b/packages/build-info/src/build-systems/index.ts index e8f50932bf..1f8bce9777 100644 --- a/packages/build-info/src/build-systems/index.ts +++ b/packages/build-info/src/build-systems/index.ts @@ -10,6 +10,7 @@ import { NPM, PNPM, Yarn } from './package-managers.js' import { Pants } from './pants.js' import { Rush } from './rush.js' import { Turbo } from './turbo.js' +import { VitePlus } from './vite-plus.js' export const buildSystems = [ Bazel, @@ -23,6 +24,7 @@ export const buildSystems = [ Pants, Rush, Turbo, + VitePlus, // JavaScript Package managers that offer building from a workspace PNPM, diff --git a/packages/build-info/src/build-systems/vite-plus.test.ts b/packages/build-info/src/build-systems/vite-plus.test.ts new file mode 100644 index 0000000000..646c06d675 --- /dev/null +++ b/packages/build-info/src/build-systems/vite-plus.test.ts @@ -0,0 +1,61 @@ +import { beforeEach, expect, test } from 'vitest' + +import { mockFileSystem } from '../../tests/mock-file-system.js' +import { NodeFS } from '../node/file-system.js' +import { Project } from '../project.js' + +beforeEach((ctx) => { + ctx.fs = new NodeFS() +}) + +test('detects Vite+ when vite-plus is in devDependencies', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ devDependencies: { 'vite-plus': '^1.0.0' } }), + }) + const detected = await new Project(fs, cwd).detectBuildSystem() + + expect(detected[0]?.name).toBe('Vite+') + expect(detected[0]?.version).toBe('^1.0.0') +}) + +test('detects Vite+ when vite-plus is in dependencies', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ dependencies: { 'vite-plus': '^2.0.0' } }), + }) + const detected = await new Project(fs, cwd).detectBuildSystem() + + expect(detected[0]?.name).toBe('Vite+') + expect(detected[0]?.version).toBe('^2.0.0') +}) + +test('does not detect Vite+ when vite-plus is absent', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ devDependencies: { vite: '^5.0.0' } }), + }) + const detected = await new Project(fs, cwd).detectBuildSystem() + + expect(detected.find((b) => b.name === 'Vite+')).toBeUndefined() +}) + +test('generates vp run commands from package.json scripts', async ({ fs }) => { + const cwd = mockFileSystem({ + 'package.json': JSON.stringify({ + devDependencies: { 'vite-plus': '^1.0.0' }, + scripts: { + build: 'vite build', + dev: 'vite dev', + test: 'vitest', + }, + }), + }) + const project = new Project(fs, cwd) + const detected = await project.detectBuildSystem() + const vitePlus = detected.find((b) => b.name === 'Vite+') + + const commands = await vitePlus!.getCommands!('') + expect(commands).toEqual([ + { type: 'build', command: 'vp run build' }, + { type: 'dev', command: 'vp run dev' }, + { type: 'unknown', command: 'vp run test' }, + ]) +}) diff --git a/packages/build-info/src/build-systems/vite-plus.ts b/packages/build-info/src/build-systems/vite-plus.ts new file mode 100644 index 0000000000..15d17d338c --- /dev/null +++ b/packages/build-info/src/build-systems/vite-plus.ts @@ -0,0 +1,36 @@ +import { isNpmBuildScript, isNpmDevScript } from '../get-commands.js' + +import { BaseBuildTool, type Command } from './build-system.js' + +export class VitePlus extends BaseBuildTool { + id = 'vite-plus' + name = 'Vite+' + + async detect(): Promise { + const pkgJsonPath = await this.project.fs.findUp('package.json', { + cwd: this.project.baseDirectory, + stopAt: this.project.root, + }) + if (pkgJsonPath) { + const pkg = await this.project.fs.readJSON>>(pkgJsonPath) + if (pkg.dependencies?.['vite-plus'] || pkg.devDependencies?.['vite-plus']) { + this.version = pkg.devDependencies?.['vite-plus'] || pkg.dependencies?.['vite-plus'] + return this + } + } + } + + async getCommands(packagePath: string): Promise { + const { scripts } = await this.project.fs.readJSON>>( + this.project.resolveFromPackage(packagePath, 'package.json'), + ) + + if (scripts && Object.keys(scripts).length > 0) { + return Object.entries(scripts).map(([scriptName, value]) => ({ + type: isNpmDevScript(scriptName, value) ? 'dev' : isNpmBuildScript(scriptName, value) ? 'build' : 'unknown', + command: `vp run ${scriptName}`, + })) + } + return [] + } +} diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts new file mode 100644 index 0000000000..a1df2007bd --- /dev/null +++ b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts @@ -0,0 +1,128 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' + +import { describe, expect, test, vi, beforeEach } from 'vitest' + +import { hasVitePlusPackage, getVitePlusVersion, installVitePlusCli } from './index.js' + +vi.mock('execa', () => ({ + execa: vi.fn().mockResolvedValue({ exitCode: 0 }), +})) + +vi.mock('../../utils/package.js', () => ({ + getPackageJson: vi.fn().mockResolvedValue({ packageJson: {} }), +})) + +import { execa } from 'execa' +import { getPackageJson } from '../../utils/package.js' + +const mockedExeca = vi.mocked(execa) +const mockedGetPackageJson = vi.mocked(getPackageJson) + +beforeEach(() => { + vi.clearAllMocks() +}) + +describe('hasVitePlusPackage', () => { + test('returns true when vite-plus is in dependencies', () => { + expect(hasVitePlusPackage({ dependencies: { 'vite-plus': '^1.0.0' } })).toBe(true) + }) + + test('returns true when vite-plus is in devDependencies', () => { + expect(hasVitePlusPackage({ devDependencies: { 'vite-plus': '^1.0.0' } })).toBe(true) + }) + + test('returns false when vite-plus is not present', () => { + expect(hasVitePlusPackage({ dependencies: { vue: '^3.0.0' } })).toBe(false) + }) + + test('returns false for empty package.json', () => { + expect(hasVitePlusPackage({})).toBe(false) + }) +}) + +describe('getVitePlusVersion', () => { + test('returns version from root devDependencies', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ + packageJson: { devDependencies: { 'vite-plus': '^2.0.0' } }, + }) + + expect(await getVitePlusVersion('/project')).toBe('^2.0.0') + }) + + test('returns version from root dependencies', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ + packageJson: { dependencies: { 'vite-plus': '1.5.0' } }, + }) + + expect(await getVitePlusVersion('/project')).toBe('1.5.0') + }) + + test('prefers devDependencies over dependencies', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ + packageJson: { + dependencies: { 'vite-plus': '1.0.0' }, + devDependencies: { 'vite-plus': '2.0.0' }, + }, + }) + + expect(await getVitePlusVersion('/project')).toBe('2.0.0') + }) + + test('falls back to workspace package.json', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ packageJson: {} }) + mockedGetPackageJson.mockResolvedValueOnce({ + packageJson: { devDependencies: { 'vite-plus': '3.0.0' } }, + }) + + expect(await getVitePlusVersion('/project', 'packages/app')).toBe('3.0.0') + }) + + test('returns latest when not found anywhere', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ packageJson: {} }) + + expect(await getVitePlusVersion('/project')).toBe('latest') + }) + + test('returns latest when not found in workspace either', async () => { + mockedGetPackageJson.mockResolvedValueOnce({ packageJson: {} }) + mockedGetPackageJson.mockResolvedValueOnce({ packageJson: {} }) + + expect(await getVitePlusVersion('/project', 'packages/app')).toBe('latest') + }) +}) + +describe('installVitePlusCli', () => { + test('calls curl with the install script', async () => { + await installVitePlusCli('1.0.0') + + expect(mockedExeca).toHaveBeenCalledWith('bash', ['-c', 'curl -fsSL https://vite.plus | bash'], { + env: expect.objectContaining({ + VP_VERSION: '1.0.0', + VITE_PLUS_VERSION: '1.0.0', + }), + stdio: 'pipe', + }) + }) + + test('passes latest as version env vars', async () => { + await installVitePlusCli('latest') + + expect(mockedExeca).toHaveBeenCalledWith( + 'bash', + expect.any(Array), + expect.objectContaining({ + env: expect.objectContaining({ + VP_VERSION: 'latest', + VITE_PLUS_VERSION: 'latest', + }), + }), + ) + }) + + test('returns the vite-plus bin directory', async () => { + const binDir = await installVitePlusCli('1.0.0') + + expect(binDir).toBe(join(homedir(), '.vite-plus', 'bin')) + }) +}) diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.ts b/packages/build/src/plugins_core/vite_plus_setup/index.ts new file mode 100644 index 0000000000..d7a3f47343 --- /dev/null +++ b/packages/build/src/plugins_core/vite_plus_setup/index.ts @@ -0,0 +1,93 @@ +import { homedir } from 'node:os' +import { join } from 'node:path' + +import { execa } from 'execa' + +import { log } from '../../log/logger.js' +import { THEME } from '../../log/theme.js' +import { getPackageJson, type PackageJson } from '../../utils/package.js' +import { CoreStep, CoreStepCondition, CoreStepFunction } from '../types.js' + +const NPM_PACKAGE_NAME = 'vite-plus' +const INSTALL_URL = 'https://vite.plus' + +export const hasVitePlusPackage = (packageJSON: PackageJson): boolean => { + const { dependencies = {}, devDependencies = {} } = packageJSON + + return NPM_PACKAGE_NAME in dependencies || NPM_PACKAGE_NAME in devDependencies +} + +export const getVitePlusVersion = async (buildDir: string, packagePath?: string): Promise => { + const { packageJson } = await getPackageJson(buildDir) + const version = packageJson.devDependencies?.[NPM_PACKAGE_NAME] || packageJson.dependencies?.[NPM_PACKAGE_NAME] + + if (version) { + return version + } + + if (packagePath) { + const { packageJson: workspacePackageJson } = await getPackageJson(join(buildDir, packagePath)) + return ( + workspacePackageJson.devDependencies?.[NPM_PACKAGE_NAME] || + workspacePackageJson.dependencies?.[NPM_PACKAGE_NAME] || + 'latest' + ) + } + + return 'latest' +} + +export const installVitePlusCli = async (version: string): Promise => { + await execa('bash', ['-c', `curl -fsSL ${INSTALL_URL} | bash`], { + env: { ...process.env, VP_VERSION: version, VITE_PLUS_VERSION: version }, + stdio: 'pipe', + }) + + const vitePlusBinDir = join(homedir(), '.vite-plus', 'bin') + return vitePlusBinDir +} + +const condition: CoreStepCondition = async ({ buildDir, packagePath, featureFlags }) => { + if (!featureFlags?.netlify_build_vite_plus_setup) { + return false + } + + const { packageJson } = await getPackageJson(buildDir) + + if (hasVitePlusPackage(packageJson)) { + return true + } + + if (packagePath) { + const { packageJson: workspacePackageJson } = await getPackageJson(join(buildDir, packagePath)) + + if (hasVitePlusPackage(workspacePackageJson)) { + return true + } + } + + return false +} + +const coreStep: CoreStepFunction = async ({ buildDir, packagePath, logs }) => { + const version = await getVitePlusVersion(buildDir, packagePath) + + log(logs, `Installing Vite+ CLI${version !== 'latest' ? ` (${THEME.highlightWords(version)})` : ''}`) + + const vitePlusBinDir = await installVitePlusCli(version) + const newPath = `${vitePlusBinDir}:${process.env.PATH}` + process.env.PATH = newPath + + log(logs, `Vite+ CLI installed successfully`) + + return { newEnvChanges: { PATH: newPath } } +} + +export const vitePlusSetup: CoreStep = { + event: 'onPreBuild', + coreStep, + coreStepId: 'vite_plus_setup', + coreStepName: 'Vite+ setup', + coreStepDescription: () => 'Installing Vite+ CLI', + condition, +} diff --git a/packages/build/src/steps/get.ts b/packages/build/src/steps/get.ts index 4a5ef12876..c08ec201e2 100644 --- a/packages/build/src/steps/get.ts +++ b/packages/build/src/steps/get.ts @@ -8,6 +8,7 @@ import { bundleEdgeFunctions } from '../plugins_core/edge_functions/index.js' import { applyDeployConfig } from '../plugins_core/frameworks_api/index.js' import { bundleFunctions } from '../plugins_core/functions/index.js' import { dbSetup } from '../plugins_core/db_setup/index.js' +import { vitePlusSetup } from '../plugins_core/vite_plus_setup/index.js' import { copyDbMigrations } from '../plugins_core/db_setup/migrations.js' import { preCleanup } from '../plugins_core/pre_cleanup/index.js' import { preDevCleanup } from '../plugins_core/pre_dev_cleanup/index.js' @@ -78,6 +79,7 @@ const getEventSteps = function (eventHandlers?: any[]) { const addCoreSteps = function (steps): CoreStep[] { return [ preCleanup, + vitePlusSetup, dbSetup, buildCommandCore, applyDeployConfig, From ffd274d22f0316c052ae3a80ea172eeb5785ec9c Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Wed, 1 Apr 2026 08:47:37 +0200 Subject: [PATCH 2/4] pr feedback --- .../src/build-systems/vite-plus.test.ts | 11 +++--- .../build-info/src/build-systems/vite-plus.ts | 20 ++++++----- .../vite_plus_setup/index.test.ts | 2 +- .../src/plugins_core/vite_plus_setup/index.ts | 34 +++++++++++++------ 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/packages/build-info/src/build-systems/vite-plus.test.ts b/packages/build-info/src/build-systems/vite-plus.test.ts index 646c06d675..c93c4f183d 100644 --- a/packages/build-info/src/build-systems/vite-plus.test.ts +++ b/packages/build-info/src/build-systems/vite-plus.test.ts @@ -14,8 +14,9 @@ test('detects Vite+ when vite-plus is in devDependencies', async ({ fs }) => { }) const detected = await new Project(fs, cwd).detectBuildSystem() - expect(detected[0]?.name).toBe('Vite+') - expect(detected[0]?.version).toBe('^1.0.0') + const vitePlus = detected.find((b) => b.name === 'Vite+') + expect(vitePlus).toBeDefined() + expect(vitePlus?.version).toBe('^1.0.0') }) test('detects Vite+ when vite-plus is in dependencies', async ({ fs }) => { @@ -24,8 +25,9 @@ test('detects Vite+ when vite-plus is in dependencies', async ({ fs }) => { }) const detected = await new Project(fs, cwd).detectBuildSystem() - expect(detected[0]?.name).toBe('Vite+') - expect(detected[0]?.version).toBe('^2.0.0') + const vitePlus = detected.find((b) => b.name === 'Vite+') + expect(vitePlus).toBeDefined() + expect(vitePlus?.version).toBe('^2.0.0') }) test('does not detect Vite+ when vite-plus is absent', async ({ fs }) => { @@ -52,6 +54,7 @@ test('generates vp run commands from package.json scripts', async ({ fs }) => { const detected = await project.detectBuildSystem() const vitePlus = detected.find((b) => b.name === 'Vite+') + expect(vitePlus).toBeDefined() const commands = await vitePlus!.getCommands!('') expect(commands).toEqual([ { type: 'build', command: 'vp run build' }, diff --git a/packages/build-info/src/build-systems/vite-plus.ts b/packages/build-info/src/build-systems/vite-plus.ts index 15d17d338c..2c9d7b5631 100644 --- a/packages/build-info/src/build-systems/vite-plus.ts +++ b/packages/build-info/src/build-systems/vite-plus.ts @@ -21,15 +21,19 @@ export class VitePlus extends BaseBuildTool { } async getCommands(packagePath: string): Promise { - const { scripts } = await this.project.fs.readJSON>>( - this.project.resolveFromPackage(packagePath, 'package.json'), - ) + try { + const { scripts } = await this.project.fs.readJSON>>( + this.project.resolveFromPackage(packagePath, 'package.json'), + ) - if (scripts && Object.keys(scripts).length > 0) { - return Object.entries(scripts).map(([scriptName, value]) => ({ - type: isNpmDevScript(scriptName, value) ? 'dev' : isNpmBuildScript(scriptName, value) ? 'build' : 'unknown', - command: `vp run ${scriptName}`, - })) + if (scripts && Object.keys(scripts).length > 0) { + return Object.entries(scripts).map(([scriptName, value]) => ({ + type: isNpmDevScript(scriptName, value) ? 'dev' : isNpmBuildScript(scriptName, value) ? 'build' : 'unknown', + command: `vp run ${scriptName}`, + })) + } + } catch { + // noop } return [] } diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts index a1df2007bd..ddf1a9f4c2 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts @@ -96,7 +96,7 @@ describe('installVitePlusCli', () => { test('calls curl with the install script', async () => { await installVitePlusCli('1.0.0') - expect(mockedExeca).toHaveBeenCalledWith('bash', ['-c', 'curl -fsSL https://vite.plus | bash'], { + expect(mockedExeca).toHaveBeenCalledWith('bash', ['-c', 'curl -fsSL --max-time 120 https://vite.plus | bash'], { env: expect.objectContaining({ VP_VERSION: '1.0.0', VITE_PLUS_VERSION: '1.0.0', diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.ts b/packages/build/src/plugins_core/vite_plus_setup/index.ts index d7a3f47343..5bc9c38964 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.ts @@ -38,10 +38,14 @@ export const getVitePlusVersion = async (buildDir: string, packagePath?: string) } export const installVitePlusCli = async (version: string): Promise => { - await execa('bash', ['-c', `curl -fsSL ${INSTALL_URL} | bash`], { - env: { ...process.env, VP_VERSION: version, VITE_PLUS_VERSION: version }, - stdio: 'pipe', - }) + try { + await execa('bash', ['-c', `curl -fsSL --max-time 120 ${INSTALL_URL} | bash`], { + env: { ...process.env, VP_VERSION: version, VITE_PLUS_VERSION: version }, + stdio: 'pipe', + }) + } catch (error) { + throw new Error(`Failed to install Vite+ CLI: ${error instanceof Error ? error.message : error}`) + } const vitePlusBinDir = join(homedir(), '.vite-plus', 'bin') return vitePlusBinDir @@ -52,17 +56,25 @@ const condition: CoreStepCondition = async ({ buildDir, packagePath, featureFlag return false } - const { packageJson } = await getPackageJson(buildDir) + try { + const { packageJson } = await getPackageJson(buildDir) - if (hasVitePlusPackage(packageJson)) { - return true + if (hasVitePlusPackage(packageJson)) { + return true + } + } catch { + // noop — root package.json missing or invalid } if (packagePath) { - const { packageJson: workspacePackageJson } = await getPackageJson(join(buildDir, packagePath)) - - if (hasVitePlusPackage(workspacePackageJson)) { - return true + try { + const { packageJson: workspacePackageJson } = await getPackageJson(join(buildDir, packagePath)) + + if (hasVitePlusPackage(workspacePackageJson)) { + return true + } + } catch { + // noop — workspace package.json missing or invalid } } From f69fa594486fa6d19da2f50e273d4247d650de9c Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Wed, 1 Apr 2026 08:54:18 +0200 Subject: [PATCH 3/4] fix: lint --- .../build-info/src/build-systems/vite-plus.ts | 2 +- .../vite_plus_setup/index.test.ts | 32 ++++++++++--------- .../src/plugins_core/vite_plus_setup/index.ts | 10 +++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/build-info/src/build-systems/vite-plus.ts b/packages/build-info/src/build-systems/vite-plus.ts index 2c9d7b5631..5cd4bd20b1 100644 --- a/packages/build-info/src/build-systems/vite-plus.ts +++ b/packages/build-info/src/build-systems/vite-plus.ts @@ -14,7 +14,7 @@ export class VitePlus extends BaseBuildTool { if (pkgJsonPath) { const pkg = await this.project.fs.readJSON>>(pkgJsonPath) if (pkg.dependencies?.['vite-plus'] || pkg.devDependencies?.['vite-plus']) { - this.version = pkg.devDependencies?.['vite-plus'] || pkg.dependencies?.['vite-plus'] + this.version = pkg.devDependencies?.['vite-plus'] ?? pkg.dependencies?.['vite-plus'] return this } } diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts index ddf1a9f4c2..81d41dce81 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts @@ -13,10 +13,10 @@ vi.mock('../../utils/package.js', () => ({ getPackageJson: vi.fn().mockResolvedValue({ packageJson: {} }), })) -import { execa } from 'execa' +import { execa, type Options, type ExecaChildProcess } from 'execa' import { getPackageJson } from '../../utils/package.js' -const mockedExeca = vi.mocked(execa) +const mockedExeca = vi.mocked(execa as (file: string, args?: readonly string[], options?: Options) => ExecaChildProcess) const mockedGetPackageJson = vi.mocked(getPackageJson) beforeEach(() => { @@ -96,11 +96,15 @@ describe('installVitePlusCli', () => { test('calls curl with the install script', async () => { await installVitePlusCli('1.0.0') - expect(mockedExeca).toHaveBeenCalledWith('bash', ['-c', 'curl -fsSL --max-time 120 https://vite.plus | bash'], { - env: expect.objectContaining({ + expect(mockedExeca).toHaveBeenCalledOnce() + const [cmd, args, opts] = mockedExeca.mock.calls[0] + expect(cmd).toBe('bash') + expect(args).toEqual(['-c', 'curl -fsSL --max-time 120 https://vite.plus | bash']) + expect(opts).toMatchObject({ + env: { VP_VERSION: '1.0.0', VITE_PLUS_VERSION: '1.0.0', - }), + }, stdio: 'pipe', }) }) @@ -108,16 +112,14 @@ describe('installVitePlusCli', () => { test('passes latest as version env vars', async () => { await installVitePlusCli('latest') - expect(mockedExeca).toHaveBeenCalledWith( - 'bash', - expect.any(Array), - expect.objectContaining({ - env: expect.objectContaining({ - VP_VERSION: 'latest', - VITE_PLUS_VERSION: 'latest', - }), - }), - ) + expect(mockedExeca).toHaveBeenCalledOnce() + const [, , opts] = mockedExeca.mock.calls[0] + expect(opts).toMatchObject({ + env: { + VP_VERSION: 'latest', + VITE_PLUS_VERSION: 'latest', + }, + }) }) test('returns the vite-plus bin directory', async () => { diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.ts b/packages/build/src/plugins_core/vite_plus_setup/index.ts index 5bc9c38964..ef8d02acd0 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.ts @@ -19,7 +19,7 @@ export const hasVitePlusPackage = (packageJSON: PackageJson): boolean => { export const getVitePlusVersion = async (buildDir: string, packagePath?: string): Promise => { const { packageJson } = await getPackageJson(buildDir) - const version = packageJson.devDependencies?.[NPM_PACKAGE_NAME] || packageJson.dependencies?.[NPM_PACKAGE_NAME] + const version = packageJson.devDependencies?.[NPM_PACKAGE_NAME] ?? packageJson.dependencies?.[NPM_PACKAGE_NAME] if (version) { return version @@ -28,8 +28,8 @@ export const getVitePlusVersion = async (buildDir: string, packagePath?: string) if (packagePath) { const { packageJson: workspacePackageJson } = await getPackageJson(join(buildDir, packagePath)) return ( - workspacePackageJson.devDependencies?.[NPM_PACKAGE_NAME] || - workspacePackageJson.dependencies?.[NPM_PACKAGE_NAME] || + workspacePackageJson.devDependencies?.[NPM_PACKAGE_NAME] ?? + workspacePackageJson.dependencies?.[NPM_PACKAGE_NAME] ?? 'latest' ) } @@ -44,7 +44,7 @@ export const installVitePlusCli = async (version: string): Promise => { stdio: 'pipe', }) } catch (error) { - throw new Error(`Failed to install Vite+ CLI: ${error instanceof Error ? error.message : error}`) + throw new Error(`Failed to install Vite+ CLI: ${error instanceof Error ? error.message : String(error)}`) } const vitePlusBinDir = join(homedir(), '.vite-plus', 'bin') @@ -87,7 +87,7 @@ const coreStep: CoreStepFunction = async ({ buildDir, packagePath, logs }) => { log(logs, `Installing Vite+ CLI${version !== 'latest' ? ` (${THEME.highlightWords(version)})` : ''}`) const vitePlusBinDir = await installVitePlusCli(version) - const newPath = `${vitePlusBinDir}:${process.env.PATH}` + const newPath = `${vitePlusBinDir}:${process.env.PATH ?? ''}` process.env.PATH = newPath log(logs, `Vite+ CLI installed successfully`) From 4ba9dd9f4e55363e57f5178c09faed17be8e0b51 Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Tue, 7 Apr 2026 07:42:35 +0200 Subject: [PATCH 4/4] chore: add pipefail --- packages/build/src/plugins_core/vite_plus_setup/index.test.ts | 2 +- packages/build/src/plugins_core/vite_plus_setup/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts index 81d41dce81..8afca6dd88 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.test.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.test.ts @@ -99,7 +99,7 @@ describe('installVitePlusCli', () => { expect(mockedExeca).toHaveBeenCalledOnce() const [cmd, args, opts] = mockedExeca.mock.calls[0] expect(cmd).toBe('bash') - expect(args).toEqual(['-c', 'curl -fsSL --max-time 120 https://vite.plus | bash']) + expect(args).toEqual(['-o', 'pipefail', '-c', 'curl -fsSL --max-time 120 https://vite.plus | bash']) expect(opts).toMatchObject({ env: { VP_VERSION: '1.0.0', diff --git a/packages/build/src/plugins_core/vite_plus_setup/index.ts b/packages/build/src/plugins_core/vite_plus_setup/index.ts index ef8d02acd0..18f66a5e17 100644 --- a/packages/build/src/plugins_core/vite_plus_setup/index.ts +++ b/packages/build/src/plugins_core/vite_plus_setup/index.ts @@ -39,7 +39,7 @@ export const getVitePlusVersion = async (buildDir: string, packagePath?: string) export const installVitePlusCli = async (version: string): Promise => { try { - await execa('bash', ['-c', `curl -fsSL --max-time 120 ${INSTALL_URL} | bash`], { + await execa('bash', ['-o', 'pipefail', '-c', `curl -fsSL --max-time 120 ${INSTALL_URL} | bash`], { env: { ...process.env, VP_VERSION: version, VITE_PLUS_VERSION: version }, stdio: 'pipe', })