mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-25 09:25:50 +00:00
Fix vt test for conditional installation (#393)
This commit is contained in:
parent
5bdc7f7b1b
commit
87454cf4b2
11 changed files with 550 additions and 28 deletions
|
|
@ -66,9 +66,13 @@ final class CLIInstaller {
|
|||
// Check if it contains the correct app path reference
|
||||
if let content = try? String(contentsOfFile: path, encoding: .utf8) {
|
||||
// Verify it's our wrapper script with all expected components
|
||||
// Check for the exec command with flexible quoting and optional arguments
|
||||
// Allow for optional variables or arguments between $VIBETUNNEL_BIN and fwd
|
||||
let hasValidExecCommand = content.range(of: #"exec\s+["']?\$VIBETUNNEL_BIN["']?\s+fwd"#, options: .regularExpression) != nil
|
||||
|
||||
if content.contains("VibeTunnel CLI wrapper") &&
|
||||
content.contains("$TRY_PATH/Contents/Resources/vibetunnel") &&
|
||||
content.contains("exec \"$VIBETUNNEL_BIN\" fwd")
|
||||
hasValidExecCommand
|
||||
{
|
||||
isCorrectlyInstalled = true
|
||||
logger.info("CLIInstaller: Found valid vt script at \(path)")
|
||||
|
|
|
|||
|
|
@ -80,4 +80,11 @@ Do NOT use three separate commands (add, commit, push) as this is slow.
|
|||
- Do NOT modify `package.json` or `pnpm-lock.yaml` unless explicitly requested
|
||||
- Always ask for permission before suggesting new dependencies
|
||||
- Understand and work with the existing codebase architecture first
|
||||
- This project has custom implementations - don't assume we need standard packages
|
||||
- This project has custom implementations - don't assume we need standard packages
|
||||
|
||||
## CRITICAL: vt Command in package.json
|
||||
**IMPORTANT: DO NOT add "vt": "./bin/vt" to the bin section of package.json or package.npm.json!**
|
||||
- The vt command must NOT be registered as a global binary in package.json
|
||||
- This is because it conflicts with other tools that use 'vt' (there are many)
|
||||
- Instead, vt is conditionally installed via postinstall script only if available
|
||||
- The postinstall script checks if vt already exists before creating a symlink
|
||||
|
|
@ -28,7 +28,8 @@ pnpm run build
|
|||
**npm package**:
|
||||
- Pre-built binaries for common platforms (macOS x64/arm64, Linux x64/arm64)
|
||||
- Automatic fallback to source compilation if pre-built binaries unavailable
|
||||
- Global installation makes `vibetunnel` and `vt` commands available system-wide
|
||||
- Global installation makes `vibetunnel` command available system-wide
|
||||
- Conditional `vt` command installation (see [VT Installation Guide](docs/VT_INSTALLATION.md))
|
||||
- Includes production dependencies only
|
||||
|
||||
**Source installation**:
|
||||
|
|
|
|||
113
web/docs/VT_INSTALLATION.md
Normal file
113
web/docs/VT_INSTALLATION.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# VT Command Installation Guide
|
||||
|
||||
The `vt` command is VibeTunnel's convenient wrapper that allows you to run any command with terminal sharing enabled. This guide explains how the installation works and how to manage it.
|
||||
|
||||
## Installation Behavior
|
||||
|
||||
When you install VibeTunnel via npm, the `vt` command installation follows these rules:
|
||||
|
||||
### Global Installation (`npm install -g vibetunnel`)
|
||||
- **Checks for existing `vt` command** to avoid conflicts with other tools
|
||||
- If no `vt` command exists, creates it globally
|
||||
- If `vt` already exists, skips installation and shows a warning
|
||||
- You can still use `npx vt` or `vibetunnel fwd` as alternatives
|
||||
|
||||
### Local Installation (`npm install vibetunnel`)
|
||||
- Configures `vt` for local use only
|
||||
- Access via `npx vt` within your project
|
||||
|
||||
## Platform Support
|
||||
|
||||
### macOS and Linux
|
||||
- Creates a symlink to the `vt` script
|
||||
- Falls back to copying if symlink creation fails
|
||||
- Script is made executable automatically
|
||||
|
||||
### Windows
|
||||
- Creates a `.cmd` wrapper for proper command execution
|
||||
- Copies the actual script alongside the wrapper
|
||||
- Works with Command Prompt, PowerShell, and Git Bash
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Existing VT Command
|
||||
If you already have a `vt` command from another tool:
|
||||
```bash
|
||||
# You'll see this warning during installation:
|
||||
⚠️ A "vt" command already exists in your system
|
||||
VibeTunnel's vt wrapper was not installed to avoid conflicts
|
||||
You can still use "npx vt" or the full path to run VibeTunnel's vt
|
||||
```
|
||||
|
||||
**Alternatives:**
|
||||
- Use `npx vt` (works globally if installed with -g)
|
||||
- Use `vibetunnel fwd` directly
|
||||
- Manually install to a different name (see below)
|
||||
|
||||
### Manual Installation
|
||||
If automatic installation fails or you want to customize:
|
||||
|
||||
```bash
|
||||
# Find where npm installs global packages
|
||||
npm config get prefix
|
||||
|
||||
# On macOS/Linux, create symlink manually
|
||||
ln -s $(npm root -g)/vibetunnel/bin/vt /usr/local/bin/vt
|
||||
|
||||
# Or copy and rename to avoid conflicts
|
||||
cp $(npm root -g)/vibetunnel/bin/vt /usr/local/bin/vibetunnel-vt
|
||||
chmod +x /usr/local/bin/vibetunnel-vt
|
||||
```
|
||||
|
||||
### Force Reinstallation
|
||||
To force VibeTunnel to overwrite an existing `vt` command:
|
||||
|
||||
```bash
|
||||
# Remove existing vt first
|
||||
rm -f $(which vt)
|
||||
|
||||
# Then reinstall VibeTunnel
|
||||
npm install -g vibetunnel
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Denied
|
||||
If you get permission errors during global installation:
|
||||
```bash
|
||||
# Option 1: Use a Node version manager (recommended)
|
||||
# With nvm: https://github.com/nvm-sh/nvm
|
||||
# With fnm: https://github.com/Schniz/fnm
|
||||
|
||||
# Option 2: Change npm's default directory
|
||||
# See: https://docs.npmjs.com/resolving-eacces-permissions-errors
|
||||
```
|
||||
|
||||
### Command Not Found
|
||||
If `vt` is installed but not found:
|
||||
```bash
|
||||
# Check if npm bin directory is in PATH
|
||||
echo $PATH
|
||||
npm config get prefix
|
||||
|
||||
# Add to your shell profile (.bashrc, .zshrc, etc.)
|
||||
export PATH="$(npm config get prefix)/bin:$PATH"
|
||||
```
|
||||
|
||||
### Windows Specific Issues
|
||||
- Ensure Node.js is in your system PATH
|
||||
- Restart your terminal after installation
|
||||
- Try using `vt.cmd` explicitly if `vt` doesn't work
|
||||
|
||||
## Uninstallation
|
||||
|
||||
The `vt` command is removed automatically when you uninstall VibeTunnel:
|
||||
```bash
|
||||
npm uninstall -g vibetunnel
|
||||
```
|
||||
|
||||
If it persists, remove manually:
|
||||
```bash
|
||||
rm -f $(which vt)
|
||||
# On Windows: del "%APPDATA%\npm\vt.cmd"
|
||||
```
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
"description": "Terminal sharing server with web interface - supports macOS, Linux, and headless environments",
|
||||
"main": "lib/cli.js",
|
||||
"bin": {
|
||||
"vibetunnel": "./bin/vibetunnel",
|
||||
"vt": "./bin/vt"
|
||||
"vibetunnel": "./bin/vibetunnel"
|
||||
},
|
||||
"files": [
|
||||
"lib/",
|
||||
|
|
|
|||
|
|
@ -600,7 +600,8 @@ async function main() {
|
|||
|
||||
// Scripts
|
||||
{ src: 'scripts/postinstall-npm.js', dest: 'scripts/postinstall.js' },
|
||||
{ src: 'scripts/node-pty-plugin.js', dest: 'scripts/node-pty-plugin.js' }
|
||||
{ src: 'scripts/node-pty-plugin.js', dest: 'scripts/node-pty-plugin.js' },
|
||||
{ src: 'scripts/install-vt-command.js', dest: 'scripts/install-vt-command.js' }
|
||||
];
|
||||
|
||||
function copyRecursive(src, dest) {
|
||||
|
|
|
|||
121
web/scripts/install-vt-command.js
Normal file
121
web/scripts/install-vt-command.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Helper function to detect global installation
|
||||
const detectGlobalInstall = () => {
|
||||
if (process.env.npm_config_global === 'true') return true;
|
||||
if (process.env.npm_config_global === 'false') return false;
|
||||
|
||||
try {
|
||||
const globalPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
|
||||
const globalModules = path.join(globalPrefix, process.platform === 'win32' ? 'node_modules' : 'lib/node_modules');
|
||||
const packagePath = path.resolve(__dirname, '..');
|
||||
return packagePath.startsWith(globalModules);
|
||||
} catch {
|
||||
return false; // Default to local install
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get npm global bin directory
|
||||
const getNpmBinDir = () => {
|
||||
try {
|
||||
// Try npm config first (more reliable)
|
||||
const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
|
||||
return path.join(npmPrefix, 'bin');
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Could not determine npm global bin directory');
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to install vt globally
|
||||
const installGlobalVt = (vtSource, npmBinDir) => {
|
||||
const vtTarget = path.join(npmBinDir, 'vt');
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Check if vt already exists
|
||||
if (fs.existsSync(vtTarget) || (isWindows && fs.existsSync(vtTarget + '.cmd'))) {
|
||||
console.log('⚠️ A "vt" command already exists in your system');
|
||||
console.log(' VibeTunnel\'s vt wrapper was not installed to avoid conflicts');
|
||||
console.log(' You can still use "npx vt" or the full path to run VibeTunnel\'s vt');
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (isWindows) {
|
||||
// On Windows, create a .cmd wrapper
|
||||
const cmdContent = `@echo off\r\nnode "%~dp0\\vt" %*\r\n`;
|
||||
fs.writeFileSync(vtTarget + '.cmd', cmdContent);
|
||||
// Also copy the actual script
|
||||
fs.copyFileSync(vtSource, vtTarget);
|
||||
console.log('✓ vt command installed globally (Windows)');
|
||||
} else {
|
||||
// On Unix-like systems, create symlink
|
||||
fs.symlinkSync(vtSource, vtTarget);
|
||||
console.log('✓ vt command installed globally');
|
||||
}
|
||||
console.log(' You can now use "vt" to wrap commands with VibeTunnel');
|
||||
return true;
|
||||
} catch (symlinkError) {
|
||||
// If symlink fails on Unix, try copying the file
|
||||
if (!isWindows) {
|
||||
try {
|
||||
fs.copyFileSync(vtSource, vtTarget);
|
||||
fs.chmodSync(vtTarget, '755');
|
||||
console.log('✓ vt command installed globally (copied)');
|
||||
console.log(' You can now use "vt" to wrap commands with VibeTunnel');
|
||||
return true;
|
||||
} catch (copyError) {
|
||||
console.warn('⚠️ Could not install vt command globally:', copyError.message);
|
||||
console.log(' Use "npx vt" or "vibetunnel fwd" instead');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Could not install vt command on Windows:', symlinkError.message);
|
||||
console.log(' Use "npx vt" or "vibetunnel fwd" instead');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Install vt command handler
|
||||
const installVtCommand = (vtSource, isGlobalInstall) => {
|
||||
if (!fs.existsSync(vtSource)) {
|
||||
console.warn('⚠️ vt command script not found in package');
|
||||
console.log(' Use "vibetunnel" command instead');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Make vt script executable (Unix-like systems only)
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(vtSource, '755');
|
||||
}
|
||||
|
||||
if (!isGlobalInstall) {
|
||||
console.log('✓ vt command configured for local use');
|
||||
console.log(' Use "npx vt" to run the vt wrapper');
|
||||
return true;
|
||||
}
|
||||
|
||||
const npmBinDir = getNpmBinDir();
|
||||
if (!npmBinDir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return installGlobalVt(vtSource, npmBinDir);
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not configure vt command:', error.message);
|
||||
console.log(' Use "vibetunnel" command instead');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
detectGlobalInstall,
|
||||
getNpmBinDir,
|
||||
installGlobalVt,
|
||||
installVtCommand
|
||||
};
|
||||
|
|
@ -232,27 +232,19 @@ for (const module of modules) {
|
|||
}
|
||||
}
|
||||
|
||||
// Import vt installation functions
|
||||
const { detectGlobalInstall, installVtCommand } = require('./install-vt-command');
|
||||
|
||||
// Install vt symlink/wrapper
|
||||
if (!hasErrors && !isDevelopment) {
|
||||
console.log('\nSetting up vt command...');
|
||||
|
||||
const vtSource = path.join(__dirname, '..', 'bin', 'vt');
|
||||
|
||||
// Check if vt script exists
|
||||
if (!fs.existsSync(vtSource)) {
|
||||
console.warn('⚠️ vt command script not found in package');
|
||||
console.log(' Use "vibetunnel" command instead');
|
||||
} else {
|
||||
try {
|
||||
// Make vt script executable
|
||||
fs.chmodSync(vtSource, '755');
|
||||
console.log('✓ vt command configured');
|
||||
console.log(' Note: The vt command is available through npm/npx');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not configure vt command:', error.message);
|
||||
console.log(' Use "vibetunnel" command instead');
|
||||
}
|
||||
}
|
||||
// Use the improved global install detection
|
||||
const isGlobalInstall = detectGlobalInstall();
|
||||
console.log(` Detected ${isGlobalInstall ? 'global' : 'local'} installation`);
|
||||
installVtCommand(vtSource, isGlobalInstall);
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
|
|
|
|||
94
web/scripts/test-vt-install.js
Executable file
94
web/scripts/test-vt-install.js
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test script for vt installation functionality
|
||||
* This tests the install-vt-command module without relying on command substitution
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
let { execSync } = require('child_process');
|
||||
|
||||
// Import the module we're testing
|
||||
const { detectGlobalInstall, installVtCommand } = require('./install-vt-command');
|
||||
|
||||
// Create a test directory
|
||||
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vt-install-test-'));
|
||||
const testBinDir = path.join(testDir, 'bin');
|
||||
const vtPath = path.join(testBinDir, 'vt');
|
||||
|
||||
// Create test vt script
|
||||
fs.mkdirSync(testBinDir, { recursive: true });
|
||||
fs.writeFileSync(vtPath, '#!/bin/bash\necho "test vt script"', { mode: 0o755 });
|
||||
|
||||
console.log('Running vt installation tests...\n');
|
||||
|
||||
// Test 1: Local installation
|
||||
console.log('Test 1: Local installation');
|
||||
process.env.npm_config_global = 'false';
|
||||
const localResult = installVtCommand(vtPath, false);
|
||||
console.log(` Result: ${localResult ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Expected: vt configured for local use\n`);
|
||||
|
||||
// Test 2: Global installation detection
|
||||
console.log('Test 2: Global installation detection');
|
||||
process.env.npm_config_global = 'true';
|
||||
const isGlobal = detectGlobalInstall();
|
||||
console.log(` Detected as global: ${isGlobal}`);
|
||||
console.log(` Result: ${isGlobal === true ? 'PASS' : 'FAIL'}\n`);
|
||||
|
||||
// Test 3: Global installation with existing vt
|
||||
console.log('Test 3: Global installation with existing vt');
|
||||
// Mock the existence check
|
||||
const originalExistsSync = fs.existsSync;
|
||||
const originalSymlinkSync = fs.symlinkSync;
|
||||
let existsCheckCalled = false;
|
||||
let symlinkCalled = false;
|
||||
|
||||
fs.existsSync = (path) => {
|
||||
if (path.endsWith('/vt')) {
|
||||
existsCheckCalled = true;
|
||||
return true; // Simulate existing vt
|
||||
}
|
||||
return originalExistsSync(path);
|
||||
};
|
||||
|
||||
fs.symlinkSync = (target, path) => {
|
||||
symlinkCalled = true;
|
||||
return originalSymlinkSync(target, path);
|
||||
};
|
||||
|
||||
// Create a mock npm bin directory
|
||||
const mockNpmBinDir = path.join(testDir, 'npm-bin');
|
||||
fs.mkdirSync(mockNpmBinDir, { recursive: true });
|
||||
|
||||
// Mock execSync to return our test directory
|
||||
const originalExecSync = require('child_process').execSync;
|
||||
require('child_process').execSync = (cmd, opts) => {
|
||||
if (cmd.includes('npm config get prefix')) {
|
||||
return testDir;
|
||||
}
|
||||
return originalExecSync(cmd, opts);
|
||||
};
|
||||
|
||||
process.env.npm_config_global = 'true';
|
||||
const globalResult = installVtCommand(vtPath, true);
|
||||
console.log(` Existing vt check called: ${existsCheckCalled}`);
|
||||
console.log(` Symlink attempted: ${symlinkCalled}`);
|
||||
console.log(` Result: ${globalResult && existsCheckCalled && !symlinkCalled ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Expected: Should detect existing vt and skip installation\n`);
|
||||
|
||||
// Restore original functions
|
||||
fs.existsSync = originalExistsSync;
|
||||
fs.symlinkSync = originalSymlinkSync;
|
||||
require('child_process').execSync = originalExecSync;
|
||||
|
||||
// Test 4: Missing vt script
|
||||
console.log('Test 4: Missing vt script');
|
||||
const missingResult = installVtCommand(path.join(testDir, 'nonexistent'), false);
|
||||
console.log(` Result: ${!missingResult ? 'PASS' : 'FAIL'}`);
|
||||
console.log(` Expected: Should return false for missing script\n`);
|
||||
|
||||
// Cleanup
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
|
||||
console.log('All tests completed.');
|
||||
|
|
@ -53,20 +53,22 @@ if awk '/if.*then/{start=NR; in_if=1; has_content=0} in_if && !/^[[:space:]]*#/
|
|||
fi
|
||||
echo "✅ vt script has no empty if statements"
|
||||
|
||||
# Test 6: Check if package.json includes vt in bin section
|
||||
# Test 6: Check that package.json does NOT include vt in bin section
|
||||
# (vt is installed conditionally via postinstall script to avoid conflicts)
|
||||
PACKAGE_JSON="$PROJECT_ROOT/package.json"
|
||||
if [ -f "$PACKAGE_JSON" ]; then
|
||||
if ! grep -q '"vt".*:.*"./bin/vt"' "$PACKAGE_JSON"; then
|
||||
echo "❌ ERROR: package.json missing vt in bin section"
|
||||
if grep -q '"vt".*:.*"./bin/vt"' "$PACKAGE_JSON"; then
|
||||
echo "❌ ERROR: package.json should NOT include vt in bin section"
|
||||
echo " vt must be installed conditionally via postinstall to avoid conflicts"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ package.json includes vt in bin section"
|
||||
echo "✅ package.json correctly omits vt from bin section (installed conditionally)"
|
||||
fi
|
||||
|
||||
# Test 7: Basic functionality test (help command)
|
||||
# Skip this test if we're already inside a VibeTunnel session
|
||||
# Skip if already inside a VibeTunnel session (recursive sessions not supported)
|
||||
if [ -n "$VIBETUNNEL_SESSION_ID" ]; then
|
||||
echo "✅ vt script detected we're inside a VibeTunnel session (expected behavior)"
|
||||
echo "⚠️ Skipping vt --help test (already inside VibeTunnel session)"
|
||||
else
|
||||
# Use gtimeout if available, otherwise skip timeout
|
||||
if command -v gtimeout >/dev/null 2>&1; then
|
||||
|
|
@ -81,7 +83,7 @@ else
|
|||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "✅ vt --help command works"
|
||||
fi
|
||||
echo "✅ vt --help command works"
|
||||
|
||||
echo "🎉 All vt command tests passed!"
|
||||
188
web/src/test/unit/postinstall-vt.test.ts
Normal file
188
web/src/test/unit/postinstall-vt.test.ts
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
describe('postinstall vt installation', () => {
|
||||
let testDir: string;
|
||||
let originalEnv: NodeJS.ProcessEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a temporary directory for testing
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vt-test-'));
|
||||
originalEnv = { ...process.env };
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up
|
||||
process.env = originalEnv;
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('installVtCommand - local installation', () => {
|
||||
it('should configure vt for local use when not global install', () => {
|
||||
const vtSource = path.join(testDir, 'vt');
|
||||
fs.writeFileSync(vtSource, '#!/bin/bash\necho "test vt"');
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
const { installVtCommand } = require('../../../scripts/install-vt-command');
|
||||
const result = installVtCommand(vtSource, false);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(consoleSpy).toHaveBeenCalledWith('✓ vt command configured for local use');
|
||||
expect(consoleSpy).toHaveBeenCalledWith(' Use "npx vt" to run the vt wrapper');
|
||||
|
||||
// Check file is executable on Unix
|
||||
if (process.platform !== 'win32') {
|
||||
const stats = fs.statSync(vtSource);
|
||||
expect(stats.mode & 0o111).toBeTruthy(); // Check execute bit
|
||||
}
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle missing vt script gracefully', () => {
|
||||
const vtSource = path.join(testDir, 'nonexistent');
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
const { installVtCommand } = require('../../../scripts/install-vt-command');
|
||||
const result = installVtCommand(vtSource, false);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith('⚠️ vt command script not found in package');
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(' Use "vibetunnel" command instead');
|
||||
|
||||
consoleWarnSpy.mockRestore();
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectGlobalInstall - environment variables', () => {
|
||||
it('should detect global install when npm_config_global is true', () => {
|
||||
process.env.npm_config_global = 'true';
|
||||
|
||||
const { detectGlobalInstall } = require('../../../scripts/install-vt-command');
|
||||
const result = detectGlobalInstall();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect local install when npm_config_global is false', () => {
|
||||
process.env.npm_config_global = 'false';
|
||||
|
||||
const { detectGlobalInstall } = require('../../../scripts/install-vt-command');
|
||||
const result = detectGlobalInstall();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Installation helpers', () => {
|
||||
it('should check for existing vt command', () => {
|
||||
const mockBinDir = path.join(testDir, 'bin');
|
||||
fs.mkdirSync(mockBinDir);
|
||||
|
||||
// Test when vt doesn't exist
|
||||
const vtPath = path.join(mockBinDir, 'vt');
|
||||
expect(fs.existsSync(vtPath)).toBe(false);
|
||||
|
||||
// Create vt and test it exists
|
||||
fs.writeFileSync(vtPath, '#!/bin/bash\necho "test"');
|
||||
expect(fs.existsSync(vtPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle Windows .cmd files', () => {
|
||||
if (process.platform !== 'win32') {
|
||||
// Skip on non-Windows
|
||||
return;
|
||||
}
|
||||
|
||||
const mockBinDir = path.join(testDir, 'bin');
|
||||
fs.mkdirSync(mockBinDir);
|
||||
|
||||
const cmdPath = path.join(mockBinDir, 'vt.cmd');
|
||||
const cmdContent = '@echo off\r\nnode "%~dp0\\vt" %*\r\n';
|
||||
|
||||
fs.writeFileSync(cmdPath, cmdContent);
|
||||
|
||||
const content = fs.readFileSync(cmdPath, 'utf8');
|
||||
expect(content).toContain('@echo off');
|
||||
expect(content).toContain('node "%~dp0\\vt" %*');
|
||||
});
|
||||
|
||||
it('should handle symlink creation', () => {
|
||||
if (process.platform === 'win32') {
|
||||
// Skip on Windows
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceFile = path.join(testDir, 'source');
|
||||
const targetLink = path.join(testDir, 'target');
|
||||
|
||||
fs.writeFileSync(sourceFile, '#!/bin/bash\necho "test"');
|
||||
|
||||
// Create symlink
|
||||
fs.symlinkSync(sourceFile, targetLink);
|
||||
|
||||
// Verify symlink exists and points to correct file
|
||||
expect(fs.existsSync(targetLink)).toBe(true);
|
||||
expect(fs.lstatSync(targetLink).isSymbolicLink()).toBe(true);
|
||||
expect(fs.readlinkSync(targetLink)).toBe(sourceFile);
|
||||
});
|
||||
|
||||
it('should handle file copying as fallback', () => {
|
||||
const sourceFile = path.join(testDir, 'source');
|
||||
const targetFile = path.join(testDir, 'target');
|
||||
|
||||
const content = '#!/bin/bash\necho "test"';
|
||||
fs.writeFileSync(sourceFile, content);
|
||||
|
||||
// Copy file
|
||||
fs.copyFileSync(sourceFile, targetFile);
|
||||
|
||||
// Make executable on Unix
|
||||
if (process.platform !== 'win32') {
|
||||
fs.chmodSync(targetFile, '755');
|
||||
}
|
||||
|
||||
// Verify copy
|
||||
expect(fs.existsSync(targetFile)).toBe(true);
|
||||
expect(fs.readFileSync(targetFile, 'utf8')).toBe(content);
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
const stats = fs.statSync(targetFile);
|
||||
expect(stats.mode & 0o111).toBeTruthy(); // Check execute bit
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle permission errors gracefully', () => {
|
||||
const vtSource = path.join(testDir, 'vt');
|
||||
fs.writeFileSync(vtSource, '#!/bin/bash\necho "test vt"');
|
||||
|
||||
// Create a read-only directory to trigger permission error
|
||||
const readOnlyDir = path.join(testDir, 'readonly');
|
||||
fs.mkdirSync(readOnlyDir);
|
||||
|
||||
// This would normally fail with permission denied if we made it truly read-only
|
||||
// but that's hard to test cross-platform, so we just verify the setup
|
||||
expect(fs.existsSync(readOnlyDir)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle path with spaces', () => {
|
||||
const dirWithSpaces = path.join(testDir, 'dir with spaces');
|
||||
fs.mkdirSync(dirWithSpaces);
|
||||
|
||||
const vtSource = path.join(dirWithSpaces, 'vt');
|
||||
fs.writeFileSync(vtSource, '#!/bin/bash\necho "test vt"');
|
||||
|
||||
expect(fs.existsSync(vtSource)).toBe(true);
|
||||
expect(fs.readFileSync(vtSource, 'utf8')).toContain('test vt');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue