/** * Publica el instalador NSIS en MinIO y actualiza latest.json + DATA.txt. * * Uso: * set MINIO_ACCESS_KEY=... * set MINIO_SECRET_KEY=... * npm run publish:minio * * Opcional: * MINIO_ENDPOINT=https://s3.builker.xyz * MINIO_BUCKET=apps-versions * MINIO_PREFIX=IPSExtractor * RELEASE_DATE=11/07/2026 (formato DATA.txt DD/MM/YYYY; default: fecha de hoy) */ import { createHash } from "node:crypto"; import { createReadStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, ".."); /** Carga KEY=VALUE desde un archivo local (sin dependencia dotenv). */ function loadEnvFile(filePath) { if (!existsSync(filePath)) return; const text = readFileSync(filePath, "utf8"); for (const line of text.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const eq = trimmed.indexOf("="); if (eq <= 0) continue; const key = trimmed.slice(0, eq).trim(); let value = trimmed.slice(eq + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } if (!(key in process.env) || process.env[key] === "") { process.env[key] = value; } } } loadEnvFile(join(root, ".env.minio")); loadEnvFile(join(root, ".env")); const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); const version = pkg.version; const endpoint = (process.env.MINIO_ENDPOINT || "https://s3.builker.xyz").replace(/\/$/, ""); const bucket = process.env.MINIO_BUCKET || "apps-versions"; const prefix = (process.env.MINIO_PREFIX || "IPSExtractor").replace(/^\/|\/$/g, ""); const accessKey = process.env.MINIO_ACCESS_KEY || process.env.AWS_ACCESS_KEY_ID; const secretKey = process.env.MINIO_SECRET_KEY || process.env.AWS_SECRET_ACCESS_KEY; if (!accessKey || !secretKey) { console.error( "Faltan credenciales. Define MINIO_ACCESS_KEY y MINIO_SECRET_KEY (o AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY).", ); process.exit(1); } const setupName = `IPSExtractor-Desktop-${version}-Setup.exe`; const setupPath = join(root, "release", setupName); if (!existsSync(setupPath)) { console.error(`No se encontró ${setupPath}. Ejecuta antes: npm run dist`); process.exit(1); } function todayDisplay() { const d = new Date(); const dd = String(d.getDate()).padStart(2, "0"); const mm = String(d.getMonth() + 1).padStart(2, "0"); const yyyy = d.getFullYear(); return `${dd}/${mm}/${yyyy}`; } function isoFromDisplay(display) { // DD/MM/YYYY → YYYY-MM-DD const m = String(display).trim().match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); if (!m) return null; const [, dd, mm, yyyy] = m; return `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}`; } function resolveReleaseDateDisplay() { // Prioridad: RELEASE_DATE explícita → fecha de hoy al publicar. if (process.env.RELEASE_DATE) return String(process.env.RELEASE_DATE).trim(); return todayDisplay(); } async function sha256File(filePath) { return new Promise((resolve, reject) => { const hash = createHash("sha256"); const stream = createReadStream(filePath); stream.on("data", (chunk) => hash.update(chunk)); stream.on("error", reject); stream.on("end", () => resolve(hash.digest("hex"))); }); } async function upload(client, key, body, contentType) { await client.send( new PutObjectCommand({ Bucket: bucket, Key: key, Body: body, ContentType: contentType, }), ); console.log(` ↑ s3://${bucket}/${key}`); } async function main() { const releaseDateDisplay = resolveReleaseDateDisplay(); const releaseDateIso = isoFromDisplay(releaseDateDisplay) || new Date().toISOString().slice(0, 10); const sha256 = await sha256File(setupPath); const { size } = statSync(setupPath); const publicBase = `${endpoint}/${bucket}/${prefix}`; const objectKeyExe = `${prefix}/${version}/${setupName}`; const objectKeyData = `${prefix}/${version}/DATA.txt`; const objectKeyLatest = `${prefix}/latest.json`; const publicUrl = `${publicBase}/${version}/${setupName}`; const latest = { version, releaseDate: releaseDateIso, releaseDateDisplay, fileName: setupName, url: publicUrl, sha256, size, }; // Actualiza version.json local para alinear la UI con la fecha publicada writeFileSync( join(root, "src/shared/version.json"), `${JSON.stringify({ releaseDate: releaseDateIso }, null, 2)}\n`, "utf8", ); const client = new S3Client({ region: process.env.MINIO_REGION || "us-east-1", endpoint, forcePathStyle: true, credentials: { accessKeyId: accessKey, secretAccessKey: secretKey, }, }); console.log(`Publicando v${version} → ${endpoint}/${bucket}/${prefix}/`); await upload(client, objectKeyExe, createReadStream(setupPath), "application/octet-stream"); await upload(client, objectKeyData, `${releaseDateDisplay}\n`, "text/plain; charset=utf-8"); await upload( client, objectKeyLatest, Buffer.from(`${JSON.stringify(latest, null, 2)}\n`, "utf8"), "application/json", ); console.log("\nListo."); console.log(` latest.json: ${publicBase}/latest.json`); console.log(` DATA.txt: ${publicBase}/${version}/DATA.txt → ${releaseDateDisplay}`); console.log(` Setup: ${publicUrl}`); } main().catch((err) => { console.error(err); process.exit(1); });