@dbcube/core
Version:
578 lines (575 loc) • 19.6 kB
JavaScript
// src/lib/Arquitecture.ts
import * as os from "os";
var Arquitecture = class {
systemInfo;
constructor() {
this.systemInfo = this.detectSystemInfo();
}
/**
* Detecta información completa del sistema
*/
detectSystemInfo() {
return {
platform: os.platform(),
arch: os.arch(),
release: os.release(),
type: os.type(),
endianness: os.endianness(),
cpus: os.cpus().length
};
}
/**
* Obtiene la plataforma normalizada
*/
getPlatform() {
const platform2 = this.systemInfo.platform;
switch (platform2) {
case "win32":
return "windows";
case "darwin":
return "macos";
case "linux":
return "linux";
case "freebsd":
return "freebsd";
case "openbsd":
return "openbsd";
case "sunos":
return "solaris";
default:
return platform2;
}
}
/**
* Obtiene la arquitectura normalizada
*/
getArchitecture() {
const arch2 = this.systemInfo.arch;
switch (arch2) {
case "x64":
return "x86_64";
case "x32":
case "ia32":
return "i686";
case "arm64":
return "aarch64";
case "arm":
return "armv7";
case "ppc64":
return "powerpc64";
case "ppc":
return "powerpc";
case "s390x":
return "s390x";
case "mips":
return "mips";
case "mipsel":
return "mipsel";
default:
return arch2;
}
}
/**
* Genera el nombre del binario basado en la arquitectura
*/
getBinaryName(baseName) {
const platform2 = this.getPlatform();
const arch2 = this.getArchitecture();
const extension = platform2 === "windows" ? ".exe" : "";
return `${baseName}-${platform2}-${arch2}${extension}`;
}
/**
* Obtiene información completa del sistema
*/
getSystemInfo() {
return { ...this.systemInfo };
}
/**
* Obtiene el triple de destino (target triple) para Rust
*/
getRustTargetTriple() {
const platform2 = this.getPlatform();
const arch2 = this.getArchitecture();
const targetMap = {
"linux-x86_64": "x86_64-unknown-linux-gnu",
"linux-i686": "i686-unknown-linux-gnu",
"linux-aarch64": "aarch64-unknown-linux-gnu",
"linux-armv7": "armv7-unknown-linux-gnueabihf",
"macos-x86_64": "x86_64-apple-darwin",
"macos-aarch64": "aarch64-apple-darwin",
"windows-x86_64": "x86_64-pc-windows-msvc",
"windows-i686": "i686-pc-windows-msvc",
"windows-aarch64": "aarch64-pc-windows-msvc",
"freebsd-x86_64": "x86_64-unknown-freebsd"
};
const key = `${platform2}-${arch2}`;
return targetMap[key] || `${arch2}-unknown-${platform2}`;
}
/**
* Muestra información detallada del sistema
*/
printSystemInfo() {
console.log("\u{1F5A5}\uFE0F System Information:");
console.log("\u251C\u2500 Platform:", this.getPlatform());
console.log("\u251C\u2500 Arquitecture:", this.getArchitecture());
console.log("\u251C\u2500 OS Type:", this.systemInfo.type);
console.log("\u251C\u2500 OS Release:", this.systemInfo.release);
console.log("\u251C\u2500 Endianness:", this.systemInfo.endianness);
console.log("\u251C\u2500 CPUs:", this.systemInfo.cpus);
console.log("\u2514\u2500 Rust Target:", this.getRustTargetTriple());
}
};
// src/lib/Donwloader.ts
import * as fs from "fs";
import * as path from "path";
import * as os2 from "os";
import followRedirects from "follow-redirects";
import * as unzipper from "unzipper";
import ora from "ora";
import chalk from "chalk";
import { fileURLToPath } from "url";
import { dirname } from "path";
var { https } = followRedirects;
var Downloader = class {
static mainSpinner = null;
static currentSpinner = null;
static VERSION_URLS = {
query: "https://raw.githubusercontent.com/Dbcube/binaries/main/query-engines.json",
schema: "https://raw.githubusercontent.com/Dbcube/binaries/main/schema-engines.json",
sqlite: "https://raw.githubusercontent.com/Dbcube/binaries/main/sqlite-engines.json"
};
/**
* Fetch latest version from GitHub
*/
static async fetchLatestVersion(prefix) {
const url = this.VERSION_URLS[prefix];
return new Promise((resolve2, reject) => {
https.get(url, (response) => {
let data = "";
response.on("data", (chunk) => {
data += chunk;
});
response.on("end", () => {
try {
const versions = JSON.parse(data);
if (versions && versions.length > 0) {
resolve2(versions[0].version);
} else {
reject(new Error("No versions found"));
}
} catch (error) {
reject(error);
}
});
response.on("error", reject);
}).on("error", reject);
});
}
/**
* Extract version from binary filename
* Example: schema-engine-v1.0.0-windows-x64.exe -> v1.0.0
*/
static extractVersionFromFilename(filename) {
const match = filename.match(/-(v\d+\.\d+\.\d+)-/);
return match ? match[1] : null;
}
/**
* Get local version of installed binary
*/
static getLocalVersion(binDir, prefix) {
try {
const files = fs.readdirSync(binDir);
const binaryPattern = new RegExp(`^${prefix}-engine-v`);
const binaryFile = files.find((f) => binaryPattern.test(f));
if (binaryFile) {
return this.extractVersionFromFilename(binaryFile);
}
} catch (error) {
}
return null;
}
/**
* Compare versions (returns true if remote is newer)
*/
static isNewerVersion(localVersion, remoteVersion) {
if (!localVersion) return true;
const cleanLocal = localVersion.replace(/^v/, "");
const cleanRemote = remoteVersion.replace(/^v/, "");
const localParts = cleanLocal.split(".").map(Number);
const remoteParts = cleanRemote.split(".").map(Number);
for (let i = 0; i < 3; i++) {
if (remoteParts[i] > localParts[i]) return true;
if (remoteParts[i] < localParts[i]) return false;
}
return false;
}
/**
* Delete old binary files
*/
static deleteOldBinary(binDir, prefix) {
try {
const files = fs.readdirSync(binDir);
const binaryPattern = new RegExp(`^${prefix}-engine-`);
files.forEach((file) => {
if (binaryPattern.test(file)) {
const filePath = path.join(binDir, file);
try {
fs.unlinkSync(filePath);
console.log(`\u{1F5D1}\uFE0F Deleted old binary: ${file}`);
} catch (err) {
console.warn(`\u26A0\uFE0F Could not delete: ${file}`);
}
}
});
} catch (error) {
}
}
static get(prefix) {
const arch2 = new Arquitecture();
const platform2 = arch2.getPlatform();
const architecture = arch2.getArchitecture();
const platformMap = {
windows: "windows",
linux: "linux",
darwin: "macos"
};
const archMap = {
x86_64: "x64",
aarch64: "arm64"
};
const plat = platformMap[platform2];
const archSuffix = archMap[architecture];
if (plat && archSuffix) {
const baseName = `${prefix}-engine-${plat}-${archSuffix}`;
const binaryName = platform2 === "windows" ? `${baseName}.exe` : baseName;
const url = `https://github.com/Dbcube/binaries/releases/download/${prefix}-engine/${prefix}-engine-latest-${plat}-${archSuffix}.zip`;
return {
name: binaryName,
url,
version: "latest",
query_engine: binaryName,
schema_engine: `${prefix}-engine-${plat}-${archSuffix}${platform2 === "windows" ? ".exe" : ""}`
};
}
return {
name: "",
url: "",
version: "",
query_engine: "",
schema_engine: ""
};
}
/**
* Get binary info with actual version from GitHub
*/
static async getWithVersion(prefix) {
const arch2 = new Arquitecture();
const platform2 = arch2.getPlatform();
const architecture = arch2.getArchitecture();
const platformMap = {
windows: "windows",
linux: "linux",
darwin: "macos"
};
const archMap = {
x86_64: "x64",
aarch64: "arm64"
};
const plat = platformMap[platform2];
const archSuffix = archMap[architecture];
if (plat && archSuffix) {
const version = await this.fetchLatestVersion(prefix);
const baseName = `${prefix}-engine-${version}-${plat}-${archSuffix}`;
const binaryName = platform2 === "windows" ? `${baseName}.exe` : baseName;
const url = `https://github.com/Dbcube/binaries/releases/download/${prefix}-engine/${prefix}-engine-${version}-${plat}-${archSuffix}.zip`;
return {
name: binaryName,
url,
version,
query_engine: binaryName,
schema_engine: binaryName
};
}
return {
name: "",
url: "",
version: "",
query_engine: "",
schema_engine: ""
};
}
static async download(targetDir) {
const binDir = targetDir || this.getDefaultBinDir();
fs.mkdirSync(binDir, { recursive: true });
this.mainSpinner = ora({
text: chalk.blue("Verificando versiones de binarios..."),
spinner: "dots12"
}).start();
const binariesToProcess = [];
for (const prefix of ["query", "schema", "sqlite"]) {
try {
const localVersion = this.getLocalVersion(binDir, prefix);
const remoteVersion = await this.fetchLatestVersion(prefix);
const needsUpdate = this.isNewerVersion(localVersion, remoteVersion);
binariesToProcess.push({
prefix,
needsUpdate,
localVersion,
remoteVersion
});
if (needsUpdate) {
console.log(`
\u{1F4E6} ${prefix}-engine: ${localVersion || "not installed"} \u2192 ${remoteVersion}`);
} else if (localVersion) {
console.log(`
\u2705 ${prefix}-engine: ${localVersion} (up to date)`);
}
} catch (error) {
console.warn(`\u26A0\uFE0F Could not check version for ${prefix}-engine, will attempt download`);
binariesToProcess.push({
prefix,
needsUpdate: true,
localVersion: null,
remoteVersion: "latest"
});
}
}
const binariesToDownload = binariesToProcess.filter((b) => b.needsUpdate);
if (binariesToDownload.length === 0) {
this.mainSpinner.succeed(chalk.green("All binaries are up to date"));
return;
}
this.mainSpinner.text = chalk.blue(`Updating ${binariesToDownload.length} binary(ies)...`);
try {
await Promise.all(binariesToDownload.map(async (binary) => {
const maxRetries = 3;
let attempt = 0;
while (attempt <= maxRetries) {
try {
const binaryInfo = await this.getWithVersion(binary.prefix);
if (!binaryInfo.name || !binaryInfo.url) {
throw new Error(`Platform or architecture not supported for ${binary.prefix}`);
}
const tempZipPath = path.join(os2.tmpdir(), `dbcube-${binary.prefix}-${Date.now()}.zip`);
const finalBinaryPath = path.join(binDir, binaryInfo.name);
this.deleteOldBinary(binDir, binary.prefix);
await this.downloadFileWithProgress(binaryInfo.url, tempZipPath, binary.prefix);
await this.extractBinary(tempZipPath, finalBinaryPath, binary.prefix);
console.log(`\u2705 ${binary.prefix}-engine updated to ${binary.remoteVersion}`);
break;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
if (attempt < maxRetries && (errorMessage.includes("ECONNRESET") || errorMessage.includes("timeout") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("ENOTFOUND"))) {
attempt++;
console.log(`\u{1F504} Retrying ${binary.prefix}-engine (${attempt}/${maxRetries})...`);
await new Promise((resolve2) => setTimeout(resolve2, 1e3 + Math.random() * 1e3));
} else {
throw new Error(`Error downloading ${binary.prefix}: ${errorMessage}`);
}
}
}
}));
this.mainSpinner.succeed(chalk.green("Binaries updated successfully"));
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
this.mainSpinner.fail(chalk.red(`Error updating binaries: ${errorMessage}`));
throw error;
}
}
static updateMainProgress(binary, current, total, status, attempt) {
const progressBar = this.createProgressBar(current, total);
const statusEmojis = {
downloading: "\u{1F4E5}",
extracting: "\u{1F4E6}",
completed: "\u2705",
exists: "\u2705",
retrying: "\u{1F504}"
};
const statusMessages = {
downloading: chalk.blue("descargando"),
extracting: chalk.yellow("extrayendo"),
completed: chalk.green("completado"),
exists: chalk.gray("existe"),
retrying: chalk.yellow(`reintentando (${attempt}/${3})`)
};
const emoji = statusEmojis[status] || "\u{1F4E5}";
const message = statusMessages[status] || status;
this.mainSpinner.text = `${progressBar} ${emoji} ${chalk.bold(binary)} - ${message}`;
}
static createProgressBar(current, total, width = 20) {
const filled = Math.round(current / total * width);
const empty = width - filled;
const filledBar = chalk.green("\u2588".repeat(filled));
const emptyBar = chalk.gray("\u2591".repeat(empty));
const percentage = chalk.bold(`${current}/${total}`);
return `[${filledBar}${emptyBar}] ${percentage}`;
}
static downloadFileWithProgress(url, outputPath, prefix) {
return new Promise((resolve2, reject) => {
const request = https.get(url, { timeout: 0 }, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
const redirectUrl = response.headers.location;
if (redirectUrl) {
return this.downloadFileWithProgress(redirectUrl, outputPath, prefix).then(resolve2).catch(reject);
}
}
if (response.statusCode !== 200) {
reject(new Error(`HTTP ${response.statusCode} para ${prefix}`));
return;
}
const file = fs.createWriteStream(outputPath);
const totalBytes = parseInt(response.headers["content-length"] || "0", 10);
let downloadedBytes = 0;
response.on("data", (chunk) => {
downloadedBytes += chunk.length;
file.write(chunk);
if (totalBytes > 0) {
const progress = {
downloaded: downloadedBytes,
total: totalBytes,
percentage: downloadedBytes / totalBytes * 100
};
this.updateDownloadProgress(prefix, progress);
}
});
response.on("end", () => {
file.end();
resolve2();
});
response.on("error", (err) => {
file.close();
this.cleanupFile(outputPath);
reject(err);
});
file.on("error", (err) => {
file.close();
this.cleanupFile(outputPath);
reject(err);
});
});
request.on("error", reject);
request.on("timeout", () => {
request.destroy();
reject(new Error(`Timeout descargando ${prefix}`));
});
});
}
static updateDownloadProgress(binary, progress) {
const percentage = progress.percentage.toFixed(1);
const downloaded = (progress.downloaded / 1024 / 1024).toFixed(1);
const total = (progress.total / 1024 / 1024).toFixed(1);
const barWidth = 15;
const filled = Math.round(progress.percentage / 100 * barWidth);
const empty = barWidth - filled;
const progressBar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
const progressText = `[${progressBar}] ${percentage}% (${downloaded}/${total}MB)`;
this.mainSpinner.text = `\u{1F4E5} ${chalk.bold(binary)} - ${progressText}`;
}
static extractBinary(zipPath, outputPath, prefix) {
return new Promise((resolve2, reject) => {
let extracted = false;
fs.createReadStream(zipPath).pipe(unzipper.Parse()).on("entry", (entry) => {
if (entry.type === "File" && !extracted) {
extracted = true;
const writeStream = fs.createWriteStream(outputPath);
entry.pipe(writeStream);
writeStream.on("finish", () => {
if (process.platform !== "win32") {
fs.chmodSync(outputPath, 493);
}
this.cleanupFile(zipPath);
resolve2();
});
writeStream.on("error", (err) => {
this.cleanupFile(zipPath);
reject(err);
});
} else {
entry.autodrain();
}
}).on("error", (err) => {
this.cleanupFile(zipPath);
reject(err);
}).on("close", () => {
if (!extracted) {
this.cleanupFile(zipPath);
reject(new Error(`No se encontr\xF3 archivo v\xE1lido en el ZIP para ${prefix}`));
}
});
});
}
static cleanupFile(filePath) {
try {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
} catch {
}
}
static getDefaultBinDir() {
const __filename = typeof import.meta !== "undefined" && import.meta.url ? fileURLToPath(import.meta.url) : "";
const __dirname = __filename ? dirname(__filename) : process.cwd();
const possibleDirs = [
path.resolve(process.cwd(), ".dbcube", "bin"),
path.resolve(process.cwd(), "node_modules", ".dbcube", "bin"),
path.resolve(__dirname, "..", "bin")
];
for (const dir of possibleDirs) {
try {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const testFile = path.join(dir, ".test");
fs.writeFileSync(testFile, "test");
fs.unlinkSync(testFile);
return dir;
} catch {
continue;
}
}
const tempDir = path.join(os2.tmpdir(), ".dbcube", "bin");
fs.mkdirSync(tempDir, { recursive: true });
return tempDir;
}
};
// src/bin.ts
async function main() {
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case "install":
case "download":
console.log("\u{1F680} Iniciando descarga de binarios Dbcube...");
try {
await Downloader.download();
console.log("\u2705 \xA1Todos los binarios descargados exitosamente!");
} catch (error) {
console.error("\u274C Error descargando binarios:", error);
}
break;
case "check":
console.log("\u{1F50D} Verificando binarios...");
const binaries = ["schema", "query", "sqlite"];
for (const prefix of binaries) {
const binary = Downloader.get(prefix);
console.log(`${prefix}-engine: ${binary.name}`);
console.log(` URL: ${binary.url}`);
}
break;
default:
console.log("Dbcube Binary Manager");
console.log("");
console.log("Comandos disponibles:");
console.log(" install|download - Descargar todos los binarios");
console.log(" check - Verificar informaci\xF3n de binarios");
console.log("");
console.log("Uso:");
console.log(" npx @dbcube/core install");
console.log(" npx @dbcube/core check");
}
}
main().catch((error) => {
console.error("Error:", error);
});
//# sourceMappingURL=bin.js.map