docs: introduce project and keep local reports out of source delivery

This commit is contained in:
cieanyau committed 2026-11-06 00:29:13 +08:00
1 parent db5762abd4
commit d37ce57d09
45 files changed
+232 -14820

No files matched your search

+24
View File
@@ -1,4 +1,9 @@
/** One-time source reconstruction. Input is explicit; generated app has no runtime link to it. */
/**
* 一次性重建留档,不参与日常开发、构建或发布。
* 输入必须是可信的自有 Nuxt 发布目录:脚本会 import 其中的元数据,因此不是安全的第三方解包器。
* 只在空工程执行;已有 app/pages 时直接拒绝,避免覆盖后续维护的源码和注释。
*/
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
@@ -12,6 +17,7 @@ import { format } from "prettier";
const generate = generator.default;
const traverse = traverser.default;
const input = path.resolve(process.argv[2] || "MISSING_INPUT");
// 所有输出定位到本工程;不向输入发布目录回写,也不改变生产发布软链接。
const root = path.resolve(import.meta.dirname, "..");
if (!fs.existsSync(path.join(input, "server/chunks/virtual/precomputed.mjs")))
throw Error("Pass the active .output directory");
@@ -19,6 +25,7 @@ if (fs.existsSync(path.join(root, "app/pages")))
throw Error(
"Refusing to overwrite editable source; this importer is only for a new empty project",
);
// 这一步会执行输入模块,调用方必须先验证输入可信性。
const pc = (
await import(
pathToFileURL(path.join(input, "server/chunks/virtual/precomputed.mjs"))
@@ -26,6 +33,7 @@ const pc = (
).default;
const chunkDir = path.join(input, "public/_nuxt");
const asts = new Map();
// 先解析元数据声明的脚本,再按导入图选择业务模块,不直接复制旧框架 bundle。
const files = [
...new Set(
Object.values(pc.modules)
@@ -46,6 +54,7 @@ const textValue = (n) =>
? n.quasis[0].value.cooked
: n?.value;
function resolve(file, exported, seen = new Set()) {
// 顺着重导出定位真正的定义;seen 检测循环,缺失导出则终止而非猜测模块归属。
if (seen.has(key(file, exported)))
throw Error(`Cyclic export ${file}:${exported}`);
seen.add(key(file, exported));
@@ -84,6 +93,7 @@ function resolve(file, exported, seen = new Set()) {
const replacements = new Map();
const replace = (file, alias, source, imported) =>
replacements.set(key(file, alias), { source, imported });
// 以下短名映射仅适用于本次可信发布版本,不是跨版本通用的编译器符号表。
const vue = {
$: "watchSyncEffect",
A: "nextTick",
@@ -218,6 +228,7 @@ replace("DEP0rg1v.js", "t", "~/utils/brand.js", "logoUrl");
replace("ecKN0m8a.js", "r", "#internal/nuxt/paths", "publicAssetsURL");
const owners = new Map();
// 页面、布局和业务组件一一对应源码路径,重复归属会报错,避免静默覆盖。
const own = Object.entries(pc.modules).filter(([p]) =>
/^(components|layouts|pages)\//.test(p),
);
@@ -276,6 +287,7 @@ for (const file of files) {
taken.add(output);
}
const authExports = {
// 为已核实的认证公共接口恢复业务名;未知模块导出仍保留原有绑定。
a: "loginErrorMessage",
c: "ApiError",
d: "apiCacheKey",
@@ -296,6 +308,7 @@ const exportNames = new Map(
Object.entries(authExports).map(([a, n]) => [key("BLvPXbVl.js", a), n]),
);
function lookup(file, exported) {
// 优先使用官方包/手工适配映射,其余依赖落到当前 app/lib,而不是历史发布路径。
const direct = replacements.get(key(file, exported));
if (direct) return direct;
const r = resolve(file, exported);
@@ -312,6 +325,7 @@ const emitted = new Set();
const pending = [...owners.keys()].filter((f) => f !== chartOwner?.[0]);
const patchFiles = [];
async function add(file, code, parser) {
// 先收集格式化内容,所有代码文件最终通过 apply_patch 新增,失败即中止。
const formatted = await format(code, {
parser:
parser ||
@@ -360,6 +374,7 @@ for (const route of routes.elements) {
route._meta = metaProps;
}
while (pending.length) {
// 从业务入口遍历依赖闭包;emitted 去重,以免循环依赖反复生成同一文件。
const file = pending.shift();
if (emitted.has(file)) continue;
emitted.add(file);
@@ -379,6 +394,7 @@ while (pending.length) {
);
}
if (file === "BLvPXbVl.js") {
// 针对此发布包剥离内嵌的 Pinia/密码库实现,保留业务逻辑并接回正式 npm 依赖。
// Business logic only; Pinia, file downloads and cryptography are npm dependencies.
let keep = false;
const body = [];
@@ -442,6 +458,7 @@ while (pending.length) {
});
traverse(ast, {
Program(p) {
// 只重写被引用的导入绑定,同步改名所有引用以避免与当前作用域已有变量冲突。
p.scope.crawl();
for (const stmt of [...p.get("body")]) {
if (!stmt.isImportDeclaration()) continue;
@@ -510,6 +527,7 @@ while (pending.length) {
});
for (const dep of dependencies) if (!emitted.has(dep)) pending.push(dep);
if (owner) {
// 业务组件包装为可编辑 SFC,恢复路由元数据并显式导入原有作用域 CSS。
const defaultExport = ast.program.body.find((n) =>
t.isExportDefaultDeclaration(n),
);
@@ -564,6 +582,7 @@ await add(
);
await add(
"docs/module-inventory.json",
// 此清单是本地重建记录;docs 已被 Git 忽略,不应作为产品运行依赖或强制加入版本库。
JSON.stringify(
{
pages: own.filter(([p]) => p.startsWith("pages/")).map(([p]) => p),
@@ -584,6 +603,7 @@ await add(
),
);
for (const { file, code } of patchFiles) {
// apply_patch 只新增目标;不使用整目录替换,也不清理已有文件。
const target = path.join(root, file);
fs.mkdirSync(path.dirname(target), { recursive: true });
const patch = `*** Begin Patch\n*** Add File: ${target}\n${code
@@ -598,6 +618,7 @@ for (const { file, code } of patchFiles) {
}
// Static binary assets retain their deployed paths; no JS bundle is copied.
const cssFiles = [
// 样式保留发布版 scope 选择器,避免在模板已丢失时重新分配作用域导致外观失配。
...new Set(
Object.values(pc.modules)
.filter((v) => v.resourceType === "style")
@@ -608,6 +629,7 @@ fs.mkdirSync(path.join(root, "app/assets/styles"), { recursive: true });
for (const file of cssFiles) {
let css = fs.readFileSync(path.join(chunkDir, file), "utf8");
css = css.replace(
// 图标字体改用 public/icons 的稳定地址,脱离旧 _nuxt 输出目录。
/url\(([^)]*primeicons[^)]*)\)/g,
(_, v) =>
`url(${v.replace(/^["']?|["']?$/g, "").replace(/^\.\//, "/icons/")})`,
@@ -621,6 +643,7 @@ for (const file of cssFiles) {
if (result.status !== 0) throw Error(result.stdout + result.stderr);
}
for (const name of fs.readdirSync(path.join(input, "public")))
// public 的图片和字体保留原访问路径;不复制 _nuxt bundle 或构建版本目录。
if (
name !== "_nuxt" &&
!name.startsWith("._") &&
@@ -634,6 +657,7 @@ for (const name of fs.readdirSync(path.join(input, "public")))
});
}
fs.mkdirSync(path.join(root, "public/icons"), { recursive: true });
// PrimeIcons 字体单独归档,配合前面的 CSS URL 重写使用。
for (const name of fs.readdirSync(chunkDir))
if (name.startsWith("primeicons.") && !name.endsWith(".js"))
fs.copyFileSync(