144 lines
3.8 KiB
JavaScript
144 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
||
import fs from "node:fs/promises";
|
||
import path from "node:path";
|
||
import process from "node:process";
|
||
|
||
const CWD = process.cwd();
|
||
const DIST_ROOT = path.join(CWD, "dist");
|
||
const ALIASES_PATH = path.join(CWD, "src", "anchors", "anchor-aliases.json");
|
||
|
||
const argv = process.argv.slice(2);
|
||
const DRY_RUN = argv.includes("--dry-run");
|
||
const STRICT = argv.includes("--strict");
|
||
|
||
function escRe(s) {
|
||
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||
}
|
||
|
||
function normalizeRoute(route) {
|
||
let r = String(route || "").trim();
|
||
if (!r.startsWith("/")) r = "/" + r;
|
||
if (!r.endsWith("/")) r = r + "/";
|
||
r = r.replace(/\/{2,}/g, "/");
|
||
return r;
|
||
}
|
||
|
||
async function exists(p) {
|
||
try {
|
||
await fs.access(p);
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function hasId(html, id) {
|
||
const re = new RegExp(`\\bid=(["'])${escRe(id)}\\1`, "i");
|
||
return re.test(html);
|
||
}
|
||
|
||
function injectBeforeId(html, newId, injectHtml) {
|
||
// insère juste avant la balise qui porte id="newId"
|
||
const re = new RegExp(
|
||
`(<[^>]+\\bid=(["'])${escRe(newId)}\\2[^>]*>)`,
|
||
"i"
|
||
);
|
||
const m = html.match(re);
|
||
if (!m || m.index == null) return { html, injected: false };
|
||
const i = m.index;
|
||
const out = html.slice(0, i) + injectHtml + "\n" + html.slice(i);
|
||
return { html: out, injected: true };
|
||
}
|
||
|
||
async function main() {
|
||
if (!(await exists(ALIASES_PATH))) {
|
||
console.log("ℹ️ Aucun fichier d'aliases (src/anchors/anchor-aliases.json). Skip.");
|
||
return;
|
||
}
|
||
|
||
const raw = await fs.readFile(ALIASES_PATH, "utf-8");
|
||
/** @type {Record<string, Record<string,string>>} */
|
||
const aliases = JSON.parse(raw);
|
||
|
||
const routes = Object.keys(aliases || {});
|
||
if (routes.length === 0) {
|
||
console.log("ℹ️ Aliases vides. Rien à injecter.");
|
||
return;
|
||
}
|
||
|
||
let changedFiles = 0;
|
||
let injectedCount = 0;
|
||
let warnCount = 0;
|
||
|
||
for (const routeKey of routes) {
|
||
const route = normalizeRoute(routeKey);
|
||
const map = aliases[routeKey] || {};
|
||
const entries = Object.entries(map);
|
||
|
||
if (entries.length === 0) continue;
|
||
|
||
const rel = route.replace(/^\/+|\/+$/g, ""); // sans slash
|
||
const htmlPath = path.join(DIST_ROOT, rel, "index.html");
|
||
|
||
if (!(await exists(htmlPath))) {
|
||
const msg = `⚠️ dist introuvable pour route=${route} (${htmlPath})`;
|
||
if (STRICT) throw new Error(msg);
|
||
console.log(msg);
|
||
warnCount++;
|
||
continue;
|
||
}
|
||
|
||
let html = await fs.readFile(htmlPath, "utf-8");
|
||
let fileChanged = false;
|
||
|
||
for (const [oldId, newId] of entries) {
|
||
if (!oldId || !newId) continue;
|
||
|
||
if (hasId(html, oldId)) {
|
||
// alias déjà présent → idempotent
|
||
continue;
|
||
}
|
||
|
||
if (!hasId(html, newId)) {
|
||
const msg = `⚠️ newId introuvable: ${route} old=${oldId} -> new=${newId}`;
|
||
if (STRICT) throw new Error(msg);
|
||
console.log(msg);
|
||
warnCount++;
|
||
continue;
|
||
}
|
||
|
||
const aliasSpan = `<span id="${oldId}" class="para-alias" aria-hidden="true"></span>`;
|
||
const r = injectBeforeId(html, newId, aliasSpan);
|
||
|
||
if (!r.injected) {
|
||
const msg = `⚠️ injection impossible (pattern non trouvé) : ${route} new=${newId}`;
|
||
if (STRICT) throw new Error(msg);
|
||
console.log(msg);
|
||
warnCount++;
|
||
continue;
|
||
}
|
||
|
||
html = r.html;
|
||
fileChanged = true;
|
||
injectedCount++;
|
||
}
|
||
|
||
if (fileChanged) {
|
||
changedFiles++;
|
||
if (!DRY_RUN) await fs.writeFile(htmlPath, html, "utf-8");
|
||
}
|
||
}
|
||
|
||
console.log(
|
||
`✅ inject-anchor-aliases: files_changed=${changedFiles} aliases_injected=${injectedCount} warnings=${warnCount}` +
|
||
(DRY_RUN ? " (dry-run)" : "")
|
||
);
|
||
|
||
if (STRICT && warnCount > 0) process.exit(2);
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error("💥 inject-anchor-aliases:", e?.message || e);
|
||
process.exit(1);
|
||
});
|