// ai slop import fs from "node:fs"; import path from "node:path"; const DEV = process.argv.includes("--dev"); const escape = (str) => str.replace(/\s+/g, " ").trim().replace(/"/g, '\\"').replace(/\$/g, "\\$"); const minifyPhp = (code) => { let minified = ""; let i = 0; let inString = false; let stringChar = ""; let inComment = false; let inMultilineComment = false; while (i < code.length) { const char = code[i]; const nextChar = code[i + 1]; const nextTwoChars = code.substring(i, i + 2); // Handle strings if ((char === '"' || char === "'") && !inComment && !inMultilineComment) { if (!inString) { inString = true; stringChar = char; minified += char; } else if (char === stringChar && code[i - 1] !== "\\") { inString = false; minified += char; } else { minified += char; } i++; continue; } if (inString) { minified += char; i++; continue; } // Handle multiline comments if (nextTwoChars === "/*" && !inComment) { inMultilineComment = true; i += 2; continue; } if (nextTwoChars === "*/" && inMultilineComment) { inMultilineComment = false; i += 2; continue; } if (inMultilineComment) { i++; continue; } // Handle single-line comments if (nextTwoChars === "//" && !inComment) { inComment = true; i += 2; continue; } if ((char === "\n" || char === "\r") && inComment) { inComment = false; // Don't append the newline directly — let it fall through to the // whitespace-collapse logic below so it becomes a single space (or // nothing) instead of a literal newline. } if (inComment) { i++; continue; } // Remove unnecessary whitespace if (/\s/.test(char)) { if (minified && !/\s$/.test(minified) && !/[\{\[\(]$/.test(minified)) { const nextNonWhitespace = code.substring(i).match(/\S/); if ( nextNonWhitespace && !/[\}\]\)]/.test(nextNonWhitespace[0]) && !/[;,:]/.test(nextNonWhitespace[0]) ) { minified += " "; } } i++; continue; } minified += char; i++; } return minified.trim(); }; function build() { const data = fs.readFileSync("./script.php", "utf8"); const mountRegex = /"<.*?)\)>>"/gm; const mountedFiles = new Set(); const result = data.replace(mountRegex, (_match, file) => { let fileContent = fs.readFileSync(file.replace(/\\/g, "/"), "utf8"); mountedFiles.add(path.resolve(file)); const inlinePhpVarRegex = /"<.*?)\)>>"/g; const vars = []; let varMatch; while ((varMatch = inlinePhpVarRegex.exec(fileContent)) !== null) { vars.push({ escapedPlaceholder: escape(varMatch[0]), varName: varMatch.groups.varName, }); } fileContent = escape(fileContent); for (const { escapedPlaceholder, varName } of vars) { fileContent = fileContent.replace( escapedPlaceholder, `" . ${varName} . "`, ); } return `"${fileContent}"`; }); fs.writeFileSync("./output.php", result, "utf8"); console.log(`[${new Date().toLocaleTimeString()}] Built output.php`); // Minify PHP after @BUILDER_BEGIN_MINIFY marker try { let phpContent = fs.readFileSync("./output.php", "utf8"); const minifyMarker = "// @BUILDER_BEGIN_MINIFY"; const markerIndex = phpContent.indexOf(minifyMarker); if (markerIndex !== -1) { // Cut from the start of the marker's line so any indentation on that // line doesn't leak into the untouched "before" section. const lineStart = phpContent.lastIndexOf("\n", markerIndex) + 1; const beforeMinify = phpContent.substring(0, lineStart); const afterMarker = phpContent.substring( markerIndex + minifyMarker.length, ); const minifiedPart = minifyPhp(afterMarker); // The marker itself is a `//` comment — it must NOT be written back // into the output, otherwise everything appended right after it on // the same line gets swallowed as part of that comment. const finalContent = beforeMinify + minifiedPart; fs.writeFileSync("./output.php", finalContent, "utf8"); console.log( `[${new Date().toLocaleTimeString()}] Minified PHP (reduced by ${(100 - (minifiedPart.length / afterMarker.length) * 100).toFixed(1)}%)`, ); } } catch (err) { console.error("Minification error:", err.message); } return mountedFiles; } // Run once immediately let watchedFiles = build(); if (DEV) { const watchers = new Map(); // path → FSWatcher function watchFile(file) { if (watchers.has(file)) return; const watcher = fs.watch(file, () => { console.log( `[${new Date().toLocaleTimeString()}] Changed: ${path.relative(".", file)}`, ); rebuild(); }); watchers.set(file, watcher); } function syncWatchers(current) { // Watch any newly mounted files for (const file of current) watchFile(file); // Stop watching files that are no longer mounted for (const [file, watcher] of watchers) { if (!current.has(file) && file !== path.resolve("./script.php")) { watcher.close(); watchers.delete(file); } } } function rebuild() { try { watchedFiles = build(); syncWatchers(watchedFiles); } catch (err) { console.error("Build error:", err.message); } } // Always watch the entry file watchFile(path.resolve("./script.php")); syncWatchers(watchedFiles); console.log("Watching for changes… (Ctrl+C to stop)"); }