All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m27s
178 lines
4.6 KiB
TypeScript
178 lines
4.6 KiB
TypeScript
import fs from "node:fs";
|
|
import fsp from "node:fs/promises";
|
|
import path from "node:path";
|
|
import archiver from "archiver";
|
|
import { createLogger } from "../logger/logger.controller.js";
|
|
import { emitBuildLog } from "./build.utils.js";
|
|
import { updateAppStats } from "./updateAppStats.utils.js";
|
|
|
|
const log = createLogger({ module: "utils", subModule: "zip" });
|
|
|
|
const exists = async (target: string) => {
|
|
try {
|
|
await fsp.access(target);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const getNextBuildNumber = async (buildNumberFile: string) => {
|
|
if (!(await exists(buildNumberFile))) {
|
|
await fsp.writeFile(buildNumberFile, "1", "utf8");
|
|
return 1;
|
|
}
|
|
|
|
const raw = await fsp.readFile(buildNumberFile, "utf8");
|
|
const current = Number.parseInt(raw.trim(), 10);
|
|
|
|
if (Number.isNaN(current) || current < 1) {
|
|
await fsp.writeFile(buildNumberFile, "1", "utf8");
|
|
return 1;
|
|
}
|
|
|
|
const next = current + 1;
|
|
|
|
await fsp.writeFile(buildNumberFile, String(next), "utf8");
|
|
|
|
// update the server with the next build number
|
|
|
|
await updateAppStats({
|
|
currentBuild: next,
|
|
lastBuildAt: new Date(),
|
|
building: true,
|
|
});
|
|
|
|
return next;
|
|
};
|
|
|
|
const cleanupOldBuilds = async (buildFolder: string, maxBuilds: number) => {
|
|
const entries = await fsp.readdir(buildFolder, { withFileTypes: true });
|
|
|
|
const zipFiles: { fullPath: string; name: string; mtimeMs: number }[] = [];
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isFile()) continue;
|
|
if (!/^LSTV3-\d+\.zip$/i.test(entry.name)) continue;
|
|
|
|
const fullPath = path.join(buildFolder, entry.name);
|
|
const stat = await fsp.stat(fullPath);
|
|
|
|
zipFiles.push({
|
|
fullPath,
|
|
name: entry.name,
|
|
mtimeMs: stat.mtimeMs,
|
|
});
|
|
}
|
|
|
|
zipFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
|
|
const toRemove = zipFiles.slice(maxBuilds);
|
|
|
|
for (const file of toRemove) {
|
|
await fsp.rm(file.fullPath, { force: true });
|
|
emitBuildLog(`Removed old build: ${file.name}`);
|
|
}
|
|
};
|
|
|
|
export const zipBuild = async () => {
|
|
const appDir = process.env.DEV_DIR ?? "";
|
|
const maxBuilds = Number(process.env.MAX_BUILDS ?? 5);
|
|
|
|
if (!appDir) {
|
|
log.error({ notify: true }, "Forgot to add in the dev dir into the env");
|
|
return;
|
|
}
|
|
|
|
const includesFile = path.join(appDir, ".includes");
|
|
const buildNumberFile = path.join(appDir, ".buildNumber");
|
|
const buildFolder = path.join(appDir, "builds");
|
|
const tempFolder = path.join(appDir, "temp", "zip-temp");
|
|
if (!(await exists(includesFile))) {
|
|
log.error({ notify: true }, "Missing .includes file common");
|
|
return;
|
|
}
|
|
|
|
await fsp.mkdir(buildFolder, { recursive: true });
|
|
|
|
const buildNumber = await getNextBuildNumber(buildNumberFile);
|
|
const zipFileName = `LSTV3-${buildNumber}.zip`;
|
|
const zipFile = path.join(buildFolder, zipFileName);
|
|
// make the folders in case they are not created already
|
|
emitBuildLog(`Using build number: ${buildNumber}`);
|
|
|
|
if (await exists(tempFolder)) {
|
|
await fsp.rm(tempFolder, { recursive: true, force: true });
|
|
}
|
|
|
|
await fsp.mkdir(tempFolder, { recursive: true });
|
|
|
|
const includes = (await fsp.readFile(includesFile, "utf8"))
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter(Boolean);
|
|
|
|
emitBuildLog(`Preparing zip from ${includes.length} include entries`);
|
|
|
|
for (const relPath of includes) {
|
|
const source = path.join(appDir, relPath);
|
|
const dest = path.join(tempFolder, relPath);
|
|
|
|
if (!(await exists(source))) {
|
|
emitBuildLog(`Skipping missing path: ${relPath}`, "error");
|
|
continue;
|
|
}
|
|
|
|
const stat = await fsp.stat(source);
|
|
await fsp.mkdir(path.dirname(dest), { recursive: true });
|
|
|
|
if (stat.isDirectory()) {
|
|
emitBuildLog(`Copying folder: ${relPath}`);
|
|
await fsp.cp(source, dest, { recursive: true });
|
|
} else {
|
|
emitBuildLog(`Copying file: ${relPath}`);
|
|
await fsp.copyFile(source, dest);
|
|
}
|
|
}
|
|
|
|
// if something crazy happens and we get the same build lets just reuse it
|
|
// if (await exists(zipFile)) {
|
|
// await fsp.rm(zipFile, { force: true });
|
|
// }
|
|
|
|
emitBuildLog(`Creating zip: ${zipFile}`);
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const output = fs.createWriteStream(zipFile);
|
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
|
|
output.on("close", () => resolve());
|
|
output.on("error", reject);
|
|
archive.on("error", reject);
|
|
|
|
archive.pipe(output);
|
|
|
|
// zip contents of temp folder, not temp folder itself
|
|
archive.directory(tempFolder, false);
|
|
archive.finalize();
|
|
});
|
|
|
|
await fsp.rm(tempFolder, { recursive: true, force: true });
|
|
|
|
emitBuildLog(`Zip completed successfully: ${zipFile}`);
|
|
|
|
await cleanupOldBuilds(buildFolder, maxBuilds);
|
|
|
|
await updateAppStats({
|
|
lastUpdated: new Date(),
|
|
building: false,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
buildNumber,
|
|
zipFile,
|
|
zipFileName,
|
|
};
|
|
};
|