UNPKG

@browserbox/browserbox

Version:

BrowserBox npm wrapper CLI

205 lines (176 loc) 5.07 kB
#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawn } = require('child_process'); const platform = os.platform(); const userHome = os.homedir(); const argv = process.argv.slice(2); function realPathOrNull(filePath) { try { return fs.realpathSync(filePath); } catch { return null; } } function fileExists(filePath) { try { fs.accessSync(filePath, fs.constants.F_OK); return true; } catch { return false; } } function toPathList(envPath) { if (!envPath) { return []; } return envPath.split(path.delimiter).filter(Boolean); } function getSelfPaths() { const selfPaths = new Set(); const localPaths = [__filename, process.argv[1]]; for (const localPath of localPaths) { if (!localPath) { continue; } selfPaths.add(path.resolve(localPath)); const resolved = realPathOrNull(localPath); if (resolved) { selfPaths.add(resolved); } } return selfPaths; } function isSelfCandidate(candidatePath, selfPaths) { const candidateResolvedPath = path.resolve(candidatePath); if (selfPaths.has(candidateResolvedPath)) { return true; } const candidateRealPath = realPathOrNull(candidatePath); return Boolean(candidateRealPath && selfPaths.has(candidateRealPath)); } function isNpmShimForWrapper(candidatePath) { if (!fileExists(candidatePath)) { return false; } try { const stat = fs.statSync(candidatePath); if (!stat.isFile() || stat.size > 8192) { return false; } const content = fs.readFileSync(candidatePath, 'utf8'); if (!content.includes('node_modules')) { return false; } return content.includes('branch-bbx.cjs') || content.includes('/bin/bbx.cjs') || content.includes('\\bin\\bbx.cjs') || content.includes('@browserbox/browserbox'); } catch { return false; } } function getKnownCandidates() { if (platform === 'win32') { const userProfile = process.env.USERPROFILE || userHome; const localAppData = process.env.LOCALAPPDATA || path.join(userProfile, 'AppData', 'Local'); return [ path.join(userProfile, 'bin', 'bbx.ps1'), path.join(userProfile, 'Scripts', 'bbx.ps1'), path.join(localAppData, 'Microsoft', 'WindowsApps', 'bbx.ps1'), path.join(localAppData, 'browserbox', 'bin', 'browserbox.exe'), ]; } return [ '/usr/local/bin/bbx', '/usr/bin/bbx', path.join(userHome, '.local', 'bin', 'bbx'), ]; } function getPathCandidates() { const searchDirs = toPathList(process.env.PATH); const names = platform === 'win32' ? ['bbx.ps1', 'browserbox.exe'] : ['bbx']; const candidates = []; for (const dir of searchDirs) { for (const name of names) { candidates.push(path.join(dir, name)); } } return candidates; } function findInstalledCliTarget() { const selfPaths = getSelfPaths(); const candidateSet = new Set([...getKnownCandidates(), ...getPathCandidates()]); for (const candidate of candidateSet) { if (!candidate || !fileExists(candidate)) { continue; } if (isSelfCandidate(candidate, selfPaths)) { continue; } if (isNpmShimForWrapper(candidate)) { continue; } return candidate; } return null; } function runChild(command, args) { return new Promise((resolve, reject) => { const child = spawn(command, args, { stdio: 'inherit' }); child.on('error', reject); child.on('close', (code, signal) => { if (signal) { reject(new Error(`Process was terminated by signal ${signal}`)); return; } resolve(code ?? 1); }); }); } function runInstallCommand() { if (platform === 'win32') { return runChild('powershell.exe', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'irm https://browserbox.io/install.ps1 | iex', ]); } return runChild('/bin/bash', ['-c', 'curl -fsSL https://browserbox.io/install.sh | bash']); } function runDelegatedCli(targetPath, args) { if (platform === 'win32' && targetPath.toLowerCase().endsWith('.ps1')) { return runChild('powershell.exe', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', targetPath, ...args, ]); } return runChild(targetPath, args); } async function ensureAndDelegate() { let target = findInstalledCliTarget(); if (!target) { console.log('BrowserBox CLI not found. Running platform installer...'); const installExitCode = await runInstallCommand(); if (installExitCode !== 0) { throw new Error(`BrowserBox installer failed with exit code ${installExitCode}`); } target = findInstalledCliTarget(); } if (!target) { throw new Error('BrowserBox install completed but no bbx command was found. Open a new shell and try again.'); } const exitCode = await runDelegatedCli(target, argv); process.exit(exitCode); } ensureAndDelegate().catch((error) => { console.error(`BrowserBox wrapper failed: ${error.message}`); process.exit(1); });