|
| 1 | +import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; |
| 2 | +import { tmpdir } from 'node:os'; |
| 3 | +import { join } from 'node:path'; |
| 4 | +import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'; |
| 5 | +import { installProjectDependencies } from '../../src/cli/init.ts'; |
| 6 | + |
| 7 | +let root: string; |
| 8 | +let bin: string; |
| 9 | +let callsFile: string; |
| 10 | + |
| 11 | +function executable(name: string, status = 0): void { |
| 12 | + const script = join(bin, `${name}.cjs`); |
| 13 | + writeFileSync( |
| 14 | + script, |
| 15 | + `const { appendFileSync } = require('node:fs'); |
| 16 | +appendFileSync(${JSON.stringify(callsFile)}, JSON.stringify({ command: ${JSON.stringify(name)}, args: process.argv.slice(2), cwd: process.cwd() }) + '\\n'); |
| 17 | +process.exitCode = process.argv.includes('--version') ? 0 : ${status}; |
| 18 | +`, |
| 19 | + ); |
| 20 | + if (process.platform === 'win32') { |
| 21 | + writeFileSync(join(bin, `${name}.cmd`), `@"${process.execPath}" "${script}" %*\r\n`); |
| 22 | + } else { |
| 23 | + writeFileSync(join(bin, name), `#!${process.execPath}\nrequire(${JSON.stringify(script)});\n`, { |
| 24 | + mode: 0o755, |
| 25 | + }); |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +function calls(): Array<{ command: string; args: Array<string>; cwd: string }> { |
| 30 | + return readFileSync(callsFile, 'utf8') |
| 31 | + .trim() |
| 32 | + .split('\n') |
| 33 | + .map((line) => JSON.parse(line)); |
| 34 | +} |
| 35 | + |
| 36 | +beforeEach(() => { |
| 37 | + root = realpathSync(mkdtempSync(join(tmpdir(), 'void-init-install-'))); |
| 38 | + bin = join(root, 'bin'); |
| 39 | + callsFile = join(root, 'calls.jsonl'); |
| 40 | + mkdirSync(bin); |
| 41 | + writeFileSync( |
| 42 | + join(root, 'package.json'), |
| 43 | + JSON.stringify({ |
| 44 | + private: true, |
| 45 | + packageManager: 'pnpm@12.4.1', |
| 46 | + devDependencies: { 'vite-plus': '^0.1.21' }, |
| 47 | + }), |
| 48 | + ); |
| 49 | + // The host's tools must not mask a missing vp or supply a newer pnpm. |
| 50 | + vi.stubEnv('PATH', bin); |
| 51 | +}); |
| 52 | + |
| 53 | +afterEach(() => { |
| 54 | + vi.unstubAllEnvs(); |
| 55 | + rmSync(root, { recursive: true, force: true }); |
| 56 | +}); |
| 57 | + |
| 58 | +describe('init dependency installation (real subprocesses)', () => { |
| 59 | + it('uses Vite+ instead of delegating a pnpm 12 pin to an older global pnpm', () => { |
| 60 | + executable('vp'); |
| 61 | + executable('pnpm', 1); |
| 62 | + |
| 63 | + installProjectDependencies(root, 'pnpm'); |
| 64 | + |
| 65 | + expect(calls()).toEqual([ |
| 66 | + { command: 'vp', args: ['--version'], cwd: root }, |
| 67 | + { command: 'vp', args: ['install', '--no-frozen-lockfile'], cwd: root }, |
| 68 | + ]); |
| 69 | + expect(JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).packageManager).toBe( |
| 70 | + 'pnpm@12.4.1', |
| 71 | + ); |
| 72 | + }); |
| 73 | + |
| 74 | + it('bootstraps Vite+ when neither a local nor a global vp is installed', () => { |
| 75 | + executable('npx'); |
| 76 | + executable('pnpm', 1); |
| 77 | + |
| 78 | + installProjectDependencies(root, 'pnpm'); |
| 79 | + |
| 80 | + expect(calls()).toEqual([ |
| 81 | + { |
| 82 | + command: 'npx', |
| 83 | + args: ['--yes', '--package=vite-plus@^0.1.21', 'vp', 'install', '--no-frozen-lockfile'], |
| 84 | + cwd: root, |
| 85 | + }, |
| 86 | + ]); |
| 87 | + }); |
| 88 | + |
| 89 | + it('reports a Vite+ install failure without retrying through another installer', () => { |
| 90 | + executable('vp', 7); |
| 91 | + executable('npx'); |
| 92 | + executable('pnpm', 1); |
| 93 | + |
| 94 | + expect(() => installProjectDependencies(root, 'pnpm')).toThrow( |
| 95 | + 'vp install failed with exit code 7', |
| 96 | + ); |
| 97 | + expect(calls()).toEqual([ |
| 98 | + { command: 'vp', args: ['--version'], cwd: root }, |
| 99 | + { command: 'vp', args: ['install', '--no-frozen-lockfile'], cwd: root }, |
| 100 | + ]); |
| 101 | + }); |
| 102 | + |
| 103 | + it('uses an installed project-local Vite+ without requiring tools on PATH', () => { |
| 104 | + const vitePlus = join(root, 'node_modules', 'vite-plus'); |
| 105 | + mkdirSync(join(vitePlus, 'bin'), { recursive: true }); |
| 106 | + writeFileSync(join(vitePlus, 'package.json'), JSON.stringify({ name: 'vite-plus' })); |
| 107 | + executable('local-vp'); |
| 108 | + writeFileSync(join(vitePlus, 'bin', 'vp'), readFileSync(join(bin, 'local-vp.cjs'), 'utf8')); |
| 109 | + |
| 110 | + installProjectDependencies(root, 'pnpm'); |
| 111 | + |
| 112 | + expect(calls()).toEqual([ |
| 113 | + { command: 'local-vp', args: ['install', '--no-frozen-lockfile'], cwd: root }, |
| 114 | + ]); |
| 115 | + }); |
| 116 | + |
| 117 | + it('keeps plain Vite projects on their selected package manager', () => { |
| 118 | + writeFileSync(join(root, 'package.json'), JSON.stringify({ private: true })); |
| 119 | + executable('vp', 1); |
| 120 | + executable('pnpm'); |
| 121 | + |
| 122 | + installProjectDependencies(root, 'pnpm'); |
| 123 | + |
| 124 | + expect(calls()).toEqual([ |
| 125 | + { command: 'pnpm', args: ['install', '--no-frozen-lockfile'], cwd: root }, |
| 126 | + ]); |
| 127 | + }); |
| 128 | +}); |
0 commit comments