const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const os = require('os');
const zlib = require('zlib');
const REPO = 'neul-labs/stout';
const BINARY_NAME = 'stout';
const INSTALLED_BINARY_NAME = 'stout-bin';
function getPlatform() {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'darwin';
case 'linux': return 'linux';
default:
throw new Error(`Unsupported platform: ${platform}`);
}
}
function getArch() {
const arch = os.arch();
switch (arch) {
case 'x64': return 'x86_64';
case 'arm64': return 'aarch64';
default:
throw new Error(`Unsupported architecture: ${arch}`);
}
}
function getTarget(platform, arch) {
const targets = {
'darwin-x86_64': 'x86_64-apple-darwin',
'darwin-aarch64': 'aarch64-apple-darwin',
'linux-x86_64': 'x86_64-unknown-linux-gnu',
'linux-aarch64': 'aarch64-unknown-linux-gnu',
};
const key = `${platform}-${arch}`;
const target = targets[key];
if (!target) {
throw new Error(`Unsupported platform/arch combination: ${key}`);
}
return target;
}
function getLatestVersion() {
return new Promise((resolve, reject) => {
const url = `https://api.github.com/repos/${REPO}/releases/latest`;
const options = {
headers: {
'User-Agent': 'stout-npm-installer',
'Accept': 'application/vnd.github.v3+json'
}
};
https.get(url, options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const release = JSON.parse(data);
resolve(release.tag_name);
} catch (e) {
reject(new Error('Failed to parse release info'));
}
});
}).on('error', reject);
});
}
function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const options = {
headers: {
'User-Agent': 'stout-npm-installer'
}
};
const request = (url) => {
https.get(url, options, (res) => {
if (res.statusCode === 302 || res.statusCode === 301) {
request(res.headers.location);
return;
}
if (res.statusCode !== 200) {
reject(new Error(`Download failed with status ${res.statusCode}`));
return;
}
const file = fs.createWriteStream(dest);
res.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', reject);
};
request(url);
});
}
function extractTarGz(tarPath, destDir) {
execSync(`tar -xzf "${tarPath}" -C "${destDir}"`, { stdio: 'inherit' });
}
async function main() {
console.log('Installing stout...');
try {
const platform = getPlatform();
const arch = getArch();
const target = getTarget(platform, arch);
console.log(`Platform: ${platform}-${arch} (${target})`);
const packageJson = require('../package.json');
let version = `v${packageJson.version}`;
try {
const latestVersion = await getLatestVersion();
if (latestVersion) {
version = latestVersion;
}
} catch (e) {
console.log(`Using package version: ${version}`);
}
console.log(`Downloading stout ${version}...`);
const archiveName = `stout-${target}.tar.gz`;
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${archiveName}`;
const binDir = path.join(__dirname, '..', 'bin');
const tmpDir = os.tmpdir();
const archivePath = path.join(tmpDir, archiveName);
if (!fs.existsSync(binDir)) {
fs.mkdirSync(binDir, { recursive: true });
}
await downloadFile(downloadUrl, archivePath);
console.log('Extracting...');
const extractDir = path.join(tmpDir, 'stout-extract');
if (!fs.existsSync(extractDir)) {
fs.mkdirSync(extractDir, { recursive: true });
}
extractTarGz(archivePath, extractDir);
const extractedBinary = path.join(extractDir, BINARY_NAME);
const binaryPath = path.join(binDir, INSTALLED_BINARY_NAME);
if (fs.existsSync(binaryPath)) {
fs.unlinkSync(binaryPath);
}
fs.copyFileSync(extractedBinary, binaryPath);
fs.chmodSync(binaryPath, 0o755);
fs.unlinkSync(archivePath);
fs.rmSync(extractDir, { recursive: true, force: true });
console.log(`stout installed successfully to ${binaryPath}`);
try {
const versionOutput = execSync(`"${binaryPath}" --version`, { encoding: 'utf8' });
console.log(`Installed: ${versionOutput.trim()}`);
} catch (e) {
console.log('Binary installed. Run "stout --version" to verify.');
}
} catch (error) {
console.error('Installation failed:', error.message);
console.error('You can install stout manually from: https://github.com/neul-labs/stout/releases');
process.exit(1);
}
}
main();