feat: Enhance CSV cleaning service with new endpoints for CSV analysis and preview functionality. Introduce support for job management linked to extraction batches, and update the cleaning rules structure. Add new utility functions for handling CSV data and improve documentation for clarity on usage and available endpoints.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 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=06/07/2026 (formato DATA.txt DD/MM/YYYY; default: hoy o version.json)
|
||||
*/
|
||||
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 displayFromIso(iso) {
|
||||
const m = String(iso).trim().match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||
if (!m) return null;
|
||||
const [, yyyy, mm, dd] = m;
|
||||
return `${dd}/${mm}/${yyyy}`;
|
||||
}
|
||||
|
||||
function resolveReleaseDateDisplay() {
|
||||
if (process.env.RELEASE_DATE) return String(process.env.RELEASE_DATE).trim();
|
||||
try {
|
||||
const meta = JSON.parse(readFileSync(join(root, "src/shared/version.json"), "utf8"));
|
||||
if (meta.releaseDate) {
|
||||
return displayFromIso(meta.releaseDate) || todayDisplay();
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user