seo-generator-server/tools/pack-lib.cjs
2025-09-03 15:29:19 +08:00

156 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const args = process.argv.slice(2);
// ---- CLI options -----------------------------------------------------------
function getArg(name, def) {
const i = args.findIndex(a => a === `--${name}` || a.startsWith(`--${name}=`));
if (i === -1) return def;
const v = args[i].includes('=') ? args[i].split('=').slice(1).join('=') : args[i + 1];
return v ?? true;
}
const ROOT = process.cwd();
const DIR = path.resolve(ROOT, getArg('dir', 'lib'));
const OUT = path.resolve(ROOT, getArg('out', 'code.js'));
const ENTRY= getArg('entry', 'lib/test-manual.js');
const ORDER= getArg('order', 'topo'); // 'topo' | 'alpha' | 'entry-first'
// ---- Helpers ---------------------------------------------------------------
function readFileSafe(p) {
try { return fs.readFileSync(p, 'utf8'); }
catch (e) { return null; }
}
function* walk(dir) {
const list = fs.readdirSync(dir, { withFileTypes: true });
for (const d of list) {
const p = path.join(dir, d.name);
if (d.isDirectory()) yield* walk(p);
else if (d.isFile() && d.name.endsWith('.js')) yield p;
}
}
// Normalise un chemin importé (./x, ../x, sans extension)
function resolveImport(fromFile, spec) {
if (!spec) return null;
if (spec.startsWith('node:')) return null; // builtin
if (!spec.startsWith('./') && !spec.startsWith('../')) return null; // externe: ignorer
let target = path.resolve(path.dirname(fromFile), spec);
if (!/\.js$/i.test(target)) {
const withJs = `${target}.js`;
if (fs.existsSync(withJs)) target = withJs;
else if (fs.existsSync(path.join(target, 'index.js'))) target = path.join(target, 'index.js');
}
return target;
}
// Très simple parseur dimports/require (suffisant pour 95 % des cas)
const RE_IMPORT_1 = /\bimport\s+[^'"]*\s+from\s+['"]([^'"]+)['"]/g; // import X from '...'
const RE_IMPORT_2 = /\bimport\s+['"]([^'"]+)['"]/g; // import '...'
const RE_REQUIRE = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g; // require('...')
function extractDeps(absPath, code) {
const specs = new Set();
let m;
for (RE_IMPORT_1.lastIndex = 0; (m = RE_IMPORT_1.exec(code)); ) specs.add(m[1]);
for (RE_IMPORT_2.lastIndex = 0; (m = RE_IMPORT_2.exec(code)); ) specs.add(m[1]);
for (RE_REQUIRE.lastIndex = 0; (m = RE_REQUIRE.exec(code)); ) specs.add(m[1]);
const deps = [];
for (const s of specs) {
const r = resolveImport(absPath, s);
if (r) deps.push(r);
}
return deps;
}
function asciiBox(title, width = 70) {
const clean = title.length > width - 6 ? title.slice(0, width - 9) + '…' : title;
const line = '─'.repeat(width - 2);
const pad = width - 6 - clean.length;
return [
`/*`,
`${line}`,
`${clean}${' '.repeat(Math.max(0, pad))}`,
`${line}`,
`*/`
].join('\n');
}
// ---- Collecte des fichiers -------------------------------------------------
if (!fs.existsSync(DIR) || !fs.statSync(DIR).isDirectory()) {
console.error(`[pack-lib] Dossier introuvable: ${DIR}`);
process.exit(1);
}
const allFiles = Array.from(walk(DIR)).map(p => path.resolve(p));
// ---- Graphe de dépendances -------------------------------------------------
/**
* graph: Map<file, { deps: string[], code: string }>
*/
const graph = new Map();
for (const f of allFiles) {
const code = readFileSafe(f) ?? '';
const deps = extractDeps(f, code).filter(d => allFiles.includes(d));
graph.set(f, { deps, code });
}
// ---- Ordonnancement --------------------------------------------------------
function topoSort(graph, entryAbs) {
const visited = new Set();
const temp = new Set();
const order = [];
const visit = (n) => {
if (visited.has(n)) return;
if (temp.has(n)) return; // cycle: on ignore pour éviter boucle
temp.add(n);
const { deps = [] } = graph.get(n) || {};
for (const d of deps) if (graph.has(d)) visit(d);
temp.delete(n);
visited.add(n);
order.push(n);
};
if (entryAbs && graph.has(entryAbs)) visit(entryAbs);
for (const n of graph.keys()) visit(n);
return order;
}
let order;
const entryAbs = path.resolve(ROOT, ENTRY);
if (ORDER === 'alpha') {
order = [...graph.keys()].sort((a, b) => a.localeCompare(b));
} else if (ORDER === 'entry-first') {
order = [entryAbs, ...[...graph.keys()].filter(f => f !== entryAbs).sort((a,b)=>a.localeCompare(b))]
.filter(Boolean);
} else {
order = topoSort(graph, entryAbs);
}
// ---- Écriture du bundle ----------------------------------------------------
const header = `/*
code.js — bundle concaténé
Généré: ${new Date().toISOString()}
Source: ${path.relative(ROOT, DIR)}
Fichiers: ${order.length}
Ordre: ${ORDER}
*/\n\n`;
let out = header;
for (const file of order) {
const rel = path.relative(ROOT, file).replace(/\\/g, '/');
const box = asciiBox(`File: ${rel}`);
const code = (graph.get(file)?.code) ?? '';
out += `${box}\n\n${code}\n\n`;
}
// Sassure que le dossier cible existe
fs.writeFileSync(OUT, out, 'utf8');
const relOut = path.relative(ROOT, OUT).replace(/\\/g, '/');
console.log(`[pack-lib] OK -> ${relOut} (${order.length} fichiers)`);