@browserbox/browserbox
Version:
BrowserBox npm wrapper CLI
205 lines (176 loc) • 5.07 kB
JavaScript
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);
});