Skip unchanged files and remove obsolete outputs

Avoid unnecessary file rewrites and prune stale outputs. Add writeIfChanged to worker.js to skip writing identical content (new 'skipped' / '*-skip' result types), update task.js to count skipped/deleted files and report stats, and introduce syncDeleteOldFiles/getAllFilesInDir/deleteEmptyDirs to remove obsolete files from dbs_min before processing. Also restore MAX_PARALLEL to 16 and simplify worker resolution/exit handling. Finally, comment out the unconditional rd command in dbs_min_generate.cmd so the output folder is preserved.
This commit is contained in:
DosX 2026-02-14 19:24:00 +03:00
commit 455967700f
3 changed files with 139 additions and 36 deletions

View file

@ -4,13 +4,15 @@ const { Worker } = require("worker_threads");
const inputDirs = ["db", "db_custom", "db_extra"];
const outputDir = "dbs_min";
const MAX_PARALLEL = 6; // Reduced from 16 to prevent resource exhaustion
const MAX_PARALLEL = 16;
var stats = {
const stats = {
total: 0,
minified: 0,
copied: 0,
failed: 0,
skipped: 0,
deleted: 0,
};
const failedFiles = [];
@ -26,53 +28,55 @@ function processFile(srcFile, dstFile) {
}
});
let isResolved = false;
const cleanup = () => {
if (!isResolved) {
isResolved = true;
worker.terminate().catch(() => { });
resolve();
}
};
worker.on('message', (result) => {
stats.total++;
if (result.type === 'minified') {
stats.minified++;
console.log("[MINIFIED] " + result.srcFile);
} else if (result.type === 'skipped') {
stats.minified++;
stats.skipped++;
console.log("[SKIP] " + result.srcFile);
} else if (result.type === 'copied') {
stats.copied++;
copiedFiles.push(result.srcFile);
console.log("[COPIED] " + result.srcFile);
} else if (result.type === 'copied-skip') {
stats.copied++;
stats.skipped++;
console.log("[SKIP] " + result.srcFile);
} else if (result.type === 'failed') {
stats.failed++;
failedFiles.push({ file: result.srcFile, reason: result.error });
console.warn("[FAILED] " + result.srcFile + " — " + result.error);
} else if (result.type === 'failed-skip') {
stats.failed++;
stats.skipped++;
console.log("[SKIP/FAIL] " + result.srcFile);
} else {
stats.failed++;
failedFiles.push({ file: result.srcFile, reason: "Read error: " + result.error });
console.warn("[ERROR/READ] " + result.srcFile + " — " + result.error);
}
cleanup();
resolve();
});
worker.on('error', (err) => {
stats.failed++;
failedFiles.push({ file: srcFile, reason: err.message });
console.warn("[ERROR] " + srcFile + " — " + err.message);
cleanup();
resolve();
});
worker.on('exit', (code) => {
if (code !== 0 && !isResolved) {
if (code !== 0) {
stats.failed++;
failedFiles.push({ file: srcFile, reason: `Worker stopped with exit code ${code}` });
console.warn("[ERROR] " + srcFile + " — Worker stopped with exit code " + code);
resolve();
}
cleanup();
});
});
}
@ -103,8 +107,8 @@ function collectFiles(srcDir, relBase, dstBase, fileList = []) {
const items = fs.readdirSync(srcDir);
for (const item of items) {
const srcPath = path.join(srcDir, item);
const stat = fs.statSync(srcPath);
if (stat.isDirectory()) {
collectFiles(srcPath, relBase, dstBase, fileList);
} else {
@ -117,6 +121,63 @@ function collectFiles(srcDir, relBase, dstBase, fileList = []) {
return fileList;
}
function getAllFilesInDir(dir, fileList = []) {
if (!fs.existsSync(dir)) return fileList;
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
getAllFilesInDir(fullPath, fileList);
} else {
fileList.push(fullPath);
}
}
return fileList;
}
function syncDeleteOldFiles(expectedFiles) {
const expectedSet = new Set(expectedFiles.map(f => path.normalize(f.dst)));
const existingFiles = getAllFilesInDir(outputDir);
let deletedCount = 0;
for (const existingFile of existingFiles) {
const normalized = path.normalize(existingFile);
if (!expectedSet.has(normalized)) {
try {
fs.unlinkSync(existingFile);
console.log("[DELETED] " + path.relative(process.cwd(), existingFile));
deletedCount++;
} catch (e) {
console.warn("[DELETE FAILED] " + existingFile + " — " + e.message);
}
}
}
deleteEmptyDirs(outputDir);
return deletedCount;
}
function deleteEmptyDirs(dir) {
if (!fs.existsSync(dir)) return;
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
if (fs.statSync(fullPath).isDirectory()) {
deleteEmptyDirs(fullPath);
}
}
if (fs.readdirSync(dir).length === 0 && dir !== outputDir) {
fs.rmdirSync(dir);
}
}
(async () => {
console.log(`[i] Processing with ${MAX_PARALLEL} parallel workers...\n`);
@ -133,13 +194,20 @@ function collectFiles(srcDir, relBase, dstBase, fileList = []) {
console.log(`[i] Found ${allFiles.length} files to process\n`);
stats.deleted = syncDeleteOldFiles(allFiles);
if (stats.deleted > 0) {
console.log(`\n[i] Deleted ${stats.deleted} obsolete files\n`);
}
await processFilesInParallel(allFiles);
let report = "\n[V] Done!\n" +
`— Total: ${stats.total}\n` +
`— Minified: ${stats.minified}\n` +
`— Copied: ${stats.copied}\n` +
`— Failed: ${stats.failed}\n`;
`— Failed: ${stats.failed}\n` +
`— Skipped: ${stats.skipped}\n` +
`— Deleted: ${stats.deleted}\n`;
if (copiedFiles.length > 0) {
report += "\n[I] Copied (unsupported extension):\n" + copiedFiles.map((f) => " • " + f).join("\n") + "\n";

View file

@ -3,6 +3,19 @@ const fs = require("fs");
const path = require("path");
const UglifyJS = require("uglify-js");
function writeIfChanged(filePath, newContent) {
if (fs.existsSync(filePath)) {
try {
const existingContent = fs.readFileSync(filePath, "utf8");
if (existingContent === newContent) {
return false;
}
} catch (e) { }
}
fs.writeFileSync(filePath, newContent, "utf8");
return true;
}
function shouldMinify(filePath) {
const ext = path.extname(filePath).toLowerCase();
return ext === ".sg" || ext === "";
@ -156,28 +169,24 @@ function replaceLetWithVarSafe(text) {
function replaceArrowFunctions(text) {
// Simple direct replacement without complex parsing
// UglifyJS output doesn't have regex/string issues with arrow functions
// 1. ()=>{...} -> function(){...}
text = text.replace(/\(\)\s*=>\s*\{/g, 'function(){');
// 2. (args)=>{...} -> function(args){...}
// Match balanced parentheses
text = text.replace(/\(([^()]*)\)\s*=>\s*\{/g, 'function($1){');
// 3. Single arg with block: arg=>{...} -> function(arg){...}
text = text.replace(/\b([a-zA-Z_$][\w$]*)\s*=>\s*\{/g, 'function($1){');
// 4. Concise forms (no braces) - need to find expression end
// ()=>expr -> function(){return expr}
// This is complex, skip for now as UglifyJS typically uses braces
return text;
}
/**
* Fix delete statements for strict mode
* Applied BEFORE minification
*/
function fixDeleteStatements(text) {
return parseJSCodeSafe(text, (fragment) => {
const match = fragment.match(/^delete\s+([a-zA-Z_$][\w$]*)(\s*;?)/);
@ -208,7 +217,19 @@ function fixDeleteStatements(text) {
}
/**
* Replace bDetected=!0 and bDetected=!1
* Safely replaces the value of bDetected variable by toggling its boolean state.
*
* @param {string} text - The JavaScript code text to parse and process
* @returns {string} The text with bDetected values toggled (0 becomes 1, 1 becomes 0)
*
* @description
* Parses the provided text using parseJSCodeSafe and searches for patterns matching
* "bDetected = !0" or "bDetected = !1". When found, toggles the numeric value and
* returns the modified text with the replacement applied.
*
* @example
* replaceBDetectedSafe('bDetected = !0'); // Returns: 'bDetected=1'
* replaceBDetectedSafe('bDetected = !1'); // Returns: 'bDetected=0'
*/
function replaceBDetectedSafe(text) {
return parseJSCodeSafe(text, (fragment) => {
@ -227,7 +248,18 @@ function replaceBDetectedSafe(text) {
}
/**
* Replace empty constructors with literals
* Replaces common constructor calls with their simplified equivalents in JavaScript code.
*
* Safely transforms:
* - `String()` `""`
* - `Boolean()` `!1`
* - `Number()` `0`
*
* Only replaces constructors that are not preceded by a dot (.) or identifier character,
* ensuring that property accesses and method calls are not affected.
*
* @param {string} text - The JavaScript code text to process
* @returns {string} The text with constructor calls replaced by their simplified forms
*/
function replaceConstructorsSafe(text) {
return parseJSCodeSafe(text, (fragment, index, fullText) => {
@ -314,21 +346,24 @@ try {
);
fs.mkdirSync(path.dirname(dstFile), { recursive: true });
fs.writeFileSync(dstFile, legacyCompatibleCode, "utf8");
const wasWritten = writeIfChanged(dstFile, legacyCompatibleCode);
result.success = true;
result.type = 'minified';
result.type = wasWritten ? 'minified' : 'skipped';
} catch (e) {
fs.mkdirSync(path.dirname(dstFile), { recursive: true });
fs.writeFileSync(dstFile, text, "utf8");
const wasWritten = writeIfChanged(dstFile, text);
result.success = false;
result.type = 'failed';
result.type = wasWritten ? 'failed' : 'failed-skip';
result.error = e.message;
}
} else {
fs.mkdirSync(path.dirname(dstFile), { recursive: true });
fs.writeFileSync(dstFile, text, "utf8");
const wasWritten = writeIfChanged(dstFile, text);
result.success = true;
result.type = 'copied';
result.type = wasWritten ? 'copied' : 'copied-skip';
}
} catch (e) {
result.success = false;

View file

@ -1,5 +1,5 @@
@echo off
rd dbs_min /q /s
:: rd dbs_min /q /s
node autotools\dbcompiler\task.js
echo Generated: %DATE%>dbs_min\timestamp.log
call db_compress