/** One-time source reconstruction. Input is explicit; generated app has no runtime link to it. */ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { spawnSync } from "node:child_process"; import { parse } from "@babel/parser"; import generator from "@babel/generator"; import traverser from "@babel/traverse"; import * as t from "@babel/types"; 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"); 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")) ) ).default; const chunkDir = path.join(input, "public/_nuxt"); const asts = new Map(); const files = [ ...new Set( Object.values(pc.modules) .filter((v) => v.resourceType === "script") .map((v) => v.file), ), ]; for (const file of files) asts.set( file, parse(fs.readFileSync(path.join(chunkDir, file), "utf8"), { sourceType: "module", }), ); const key = (file, name) => `${file}:${name}`; const textValue = (n) => t.isTemplateLiteral(n) && n.expressions.length === 0 ? n.quasis[0].value.cooked : n?.value; function resolve(file, exported, seen = new Set()) { if (seen.has(key(file, exported))) throw Error(`Cyclic export ${file}:${exported}`); seen.add(key(file, exported)); const ast = asts.get(file); if (!ast) throw Error(`Missing ${file}`); const imports = new Map(); for (const n of ast.program.body) if (t.isImportDeclaration(n)) for (const s of n.specifiers) imports.set(s.local.name, { file: path.basename(n.source.value), name: t.isImportDefaultSpecifier(s) ? "default" : s.imported?.name || s.imported?.value, }); for (const n of ast.program.body) { if (t.isExportDefaultDeclaration(n) && exported === "default") return { file, name: exported, local: t.isIdentifier(n.declaration) ? n.declaration.name : null, }; if (t.isExportNamedDeclaration(n)) for (const s of n.specifiers) if ((s.exported.name || s.exported.value) === exported) { const imp = n.source ? { file: path.basename(n.source.value), name: s.local.name } : imports.get(s.local.name); if (imp?.file.endsWith(".js")) return resolve(imp.file, imp.name, seen); return { file, name: exported, local: s.local.name }; } } throw Error(`No export ${file}:${exported}`); } const replacements = new Map(); const replace = (file, alias, source, imported) => replacements.set(key(file, alias), { source, imported }); const vue = { $: "watchSyncEffect", A: "nextTick", B: "provide", Bt: "toDisplayString", C: "guardReactiveProps", D: "isVNode", E: "inject", F: "onErrorCaptured", G: "resolveDirective", H: "renderList", I: "onMounted", J: "useId", K: "resolveDynamicComponent", L: "onUnmounted", Lt: "normalizeClass", M: "onBeforeMount", N: "onBeforeUnmount", O: "mergeModels", P: "onDeactivated", Q: "watchEffect", R: "onUpdated", Rt: "normalizeProps", S: "getCurrentInstance", T: "hasInjectionContext", U: "renderSlot", V: "queuePostFlushCb", W: "resolveComponent", X: "warn", Y: "useModel", Z: "watch", _: "createStaticVNode", _t: "toValue", a: "Suspense", at: "isReadonly", b: "defineAsyncComponent", c: "cloneVNode", ct: "onScopeDispose", d: "createBlock", dt: "ref", et: "withCtx", f: "createCommentVNode", ft: "shallowReactive", g: "createSlots", gt: "toRefs", h: "createRenderer", ht: "toRef", i: "KeepAlive", it: "isReactive", j: "onActivated", k: "mergeProps", l: "computed", lt: "reactive", m: "createHydrationRenderer", mt: "toRaw", n: "BaseTransitionPropsValidators", nt: "effectScope", o: "Teleport", ot: "isRef", p: "createElementBlock", pt: "shallowRef", q: "toHandlers", r: "Fragment", rt: "getCurrentScope", s: "callWithAsyncErrorHandling", st: "markRaw", t: "BaseTransition", tt: "withDirectives", u: "createElementVNode", ut: "readonly", v: "createTextVNode", vt: "unref", w: "h", x: "defineComponent", y: "createVNode", z: "openBlock", zt: "normalizeStyle", }; const shared = { At: "isPlainObject", Ct: "includeBooleanAttr", Dt: "isModelListener", Et: "isFunction", Ft: "looseEqual", It: "looseToNumber", Mt: "isSpecialBooleanAttr", Nt: "isString", Ot: "isObject", Pt: "isSymbol", St: "hyphenate", Tt: "isArray", Vt: "toNumber", bt: "capitalize", jt: "isPromise", kt: "isOn", wt: "invokeArrayFns", xt: "extend", yt: "camelize", }; for (const [a, n] of Object.entries(vue)) replace("CaDPEW5H.js", a, "vue", n); for (const [a, n] of Object.entries(shared)) replace("CaDPEW5H.js", a, "@vue/shared", n); for (const [a, n] of Object.entries({ a: "vModelText", c: "withModifiers", i: "vModelRadio", n: "createApp", o: "vShow", r: "createSSRApp", s: "withKeys", t: "Transition", })) replace("CHOHVZjb.js", a, "vue", n); for (const [file, map] of Object.entries({ "BXrXAUMD.js": { a: "useNuxtApp", o: "useRuntimeConfig" }, "DNcWRfqH.js": { f: "useRoute", p: "useRouter", u: "navigateTo" }, "BJ9CQb77.js": { n: "useHead" }, "ClFYPW8t.js": { t: "useState" }, })) for (const [a, n] of Object.entries(map)) replace(file, a, "#app", n); replace("Cq-ZALh2.js", "D", "vue-router", "onBeforeRouteLeave"); replace("Gudm9e0k.js", "t", "#components", "NuxtLink"); replace("BnCkphz4.js", "t", "#components", "Icon"); replace("uBIymjUX.js", "t", "~/utils/load-module.js", "loadModule"); replace("BLvPXbVl.js", "_", "pinia", "createPinia"); replace("BLvPXbVl.js", "v", "pinia", "setActivePinia"); replace("BLvPXbVl.js", "y", "pinia", "shouldHydrate"); replace("BLvPXbVl.js", "s", "~/utils/crypto.js", "getCrypto"); replace("BSkUL2SB.js", "n", "primevue/usetoast", "useToast"); replace("iGbquyGC.js", "t", "primevue/tooltip", "default"); 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), ); for (const [source, mod] of own) { const r = resolve(mod.file, "default"); if (owners.has(r.file) && owners.get(r.file).source !== source) throw Error(`Multiple owners ${r.file}`); owners.set(r.file, { source, exported: r.name, local: r.local }); replacements.set(key(r.file, r.name), { source: `~/${source}`, imported: "default", label: path.basename(source, ".vue"), }); } // Official libraries replace bundled copies. Resolve tiny re-export wrappers first. for (const [source, mod] of Object.entries(pc.modules)) { if (!source.includes("/node_modules/") || mod.resourceType !== "script") continue; let npm = source.slice(source.lastIndexOf("/node_modules/") + 14); if (npm.startsWith("nuxt/") || npm.startsWith("@nuxt/icon")) continue; npm = npm.replace(/\/index\.mjs$/, ""); if (npm === "@novnc/novnc/core/rfb.js") npm = "@novnc/novnc"; const r = resolve(mod.file, "default"); if (!owners.has(r.file)) replacements.set(key(r.file, r.name), { source: npm, imported: "default", label: npm.split("/").at(-1), }); } // The existing chart component is recovered separately; ECharts stays a library dependency. const chartOwner = [...owners.entries()].find( ([, v]) => v.source === "components/ChartBox.vue", ); if (chartOwner) replacements.set(key(chartOwner[0], chartOwner[1].exported), { source: "~/components/ChartBox.vue", imported: "default", label: "ChartBox", }); const names = new Map(); for (const d of Object.values(pc.dependencies)) for (const v of Object.values(d.preload || {})) if (v.name && v.file?.endsWith(".js") && !names.has(v.file)) names.set(v.file, v.name); const paths = new Map(); const taken = new Set(); for (const file of files) { let output = owners.has(file) ? `app/${owners.get(file).source}` : `app/lib/${(names.get(file) || "module").replace(/[^\w-]/g, "-")}.js`; let i = 2; const base = output; while (taken.has(output)) output = base.replace(/\.js$/, `-${i++}.js`); paths.set(file, output); taken.add(output); } const authExports = { a: "loginErrorMessage", c: "ApiError", d: "apiCacheKey", f: "apiCacheVersion", g: "sanitizeMessage", h: "operationMessage", i: "securityMethods", l: "useApi", m: "writeApiCache", n: "assertSecurityComplete", o: "isValidJwt", p: "readApiCache", r: "securityChallenge", t: "useAuth", u: "clearApiCache", }; const exportNames = new Map( Object.entries(authExports).map(([a, n]) => [key("BLvPXbVl.js", a), n]), ); function lookup(file, exported) { const direct = replacements.get(key(file, exported)); if (direct) return direct; const r = resolve(file, exported); return ( replacements.get(key(r.file, r.name)) || { source: "~/" + paths.get(r.file).replace(/^app\//, ""), imported: exportNames.get(key(r.file, r.name)) || r.name, file: r.file, label: names.get(r.file), } ); } const emitted = new Set(); const pending = [...owners.keys()].filter((f) => f !== chartOwner?.[0]); const patchFiles = []; async function add(file, code, parser) { const formatted = await format(code, { parser: parser || (file.endsWith(".vue") ? "vue" : file.endsWith(".json") ? "json" : "babel"), printWidth: 110, }); patchFiles.push({ file, code: formatted }); } // Save route metadata without compiled preload machinery; Nuxt owns the new module graph. const entry = asts.get("CNMaXYQo.js"); let routes; traverse(entry, { VariableDeclarator(p) { if (p.node.id.name === "Qi") routes = t.cloneNode(p.node.init, true); }, }); for (const route of routes.elements) { let file; for (const prop of route.properties) if (prop.key.name === "component") traverse( t.file( t.program([t.expressionStatement(t.cloneNode(prop.value, true))]), ), { CallExpression(p) { if (p.node.callee.type === "Import") file = path.basename(textValue(p.node.arguments[0])); }, }, ); const owner = owners.get(resolve(file, "default").file); const metaProps = route.properties.find((p) => p.key.name === "meta")?.value.properties || []; const alias = route.properties.find((p) => p.key.name === "alias"); if (alias) metaProps.push(t.objectProperty(t.identifier("alias"), alias.value)); const redirect = route.properties.find((p) => p.key.name === "redirect"); if (redirect) metaProps.push(t.objectProperty(t.identifier("redirect"), redirect.value)); route._source = owner.source; route._meta = metaProps; } while (pending.length) { const file = pending.shift(); if (emitted.has(file)) continue; emitted.add(file); if (!asts.has(file)) throw Error(`No AST ${file}`); let ast = t.cloneNode(asts.get(file), true); if (file === "BDbvURLy.js") { ast.program.body = ast.program.body.filter( (n) => !(t.isFunctionDeclaration(n) && n.id.name === "y") && !(t.isImportDeclaration(n) && n.source.value === "./j97ZVn8z.js"), ); ast.program.body.unshift( t.importDeclaration( [t.importSpecifier(t.identifier("y"), t.identifier("reloadNuxtApp"))], t.stringLiteral("#app"), ), ); } if (file === "BLvPXbVl.js") { // Business logic only; Pinia, file downloads and cryptography are npm dependencies. let keep = false; const body = []; for (const n of ast.program.body) { if (t.isImportDeclaration(n)) { body.push(n); continue; } if (t.isVariableDeclaration(n)) { for (const d of n.declarations) { if (d.id.name === "ce") keep = true; if (d.id.name === "Oe") keep = false; if (d.id.name === "ot") keep = true; if (keep) body.push(t.variableDeclaration(n.kind, [d])); } } else if (t.isExportNamedDeclaration(n)) { n.specifiers = n.specifiers.filter( (s) => !["_", "v", "y", "s"].includes(s.exported.name), ); body.push(n); } else if (keep) body.push(n); } body.unshift( t.importDeclaration( [t.importDefaultSpecifier(t.identifier("CryptoJS"))], t.stringLiteral("crypto-js"), ), t.importDeclaration( [t.importSpecifier(t.identifier("se"), t.identifier("defineStore"))], t.stringLiteral("pinia"), ), ); ast.program.body = body; traverse(ast, { MemberExpression(p) { if ( t.isIdentifier(p.node.object, { name: "at" }) && t.isIdentifier(p.node.property, { name: "default" }) ) p.replaceWith(t.identifier("CryptoJS")); }, }); } // Discard old dependency indexes: Vite reconstructs preload URLs from new imports. traverse(ast, { CallExpression(p) { if (t.isIdentifier(p.node.callee, { name: "__vite__mapDeps" })) p.replaceWith(t.arrayExpression([])); }, VariableDeclarator(p) { if (p.node.id.name === "__vite__mapDeps") p.remove(); }, }); const owner = owners.get(file); const dependencies = []; const bindingNames = new Set(); traverse(ast, { BindingIdentifier(p) { bindingNames.add(p.node.name); }, }); traverse(ast, { Program(p) { p.scope.crawl(); for (const stmt of [...p.get("body")]) { if (!stmt.isImportDeclaration()) continue; const original = stmt.node.source.value; if (!original.startsWith(".")) continue; const dep = path.basename(original); const imports = []; for (const spec of [...stmt.node.specifiers]) { if (!p.scope.getBinding(spec.local.name)?.referenced) continue; const alias = t.isImportDefaultSpecifier(spec) ? "default" : spec.imported?.name || spec.imported?.value; const target = lookup(dep, alias); if (target.file) dependencies.push(target.file); let name = target.imported === "default" ? target.label || path.basename(target.source) || "Component" : target.imported; if (name.length < 3 && target.label) name = target.label + "_" + name; name = name.replace(/[^\w$]/g, "_"); if (!/^[A-Za-z_$]/.test(name)) name = "module_" + name; if (name !== spec.local.name) { while (bindingNames.has(name) || p.scope.hasGlobal(name)) name += "Imported"; p.scope.rename(spec.local.name, name); bindingNames.add(name); } const local = t.identifier(spec.local.name); imports.push( t.importDeclaration( [ target.imported === "default" ? t.importDefaultSpecifier(local) : t.importSpecifier(local, t.identifier(target.imported)), ], t.stringLiteral(target.source), ), ); } if (!stmt.node.specifiers.length) { if (file === "BY4y5R3Q.js" && dep === "DNcWRfqH.js") { stmt.remove(); continue; } throw Error(`Side effect import ${file} ${original}`); } stmt.replaceWithMultiple(imports); } // Semantic exported API names are stable throughout the reconstructed project. for (const s of p.node.body) if (t.isExportNamedDeclaration(s)) for (const spec of s.specifiers) { const n = exportNames.get(key(file, spec.exported.name)); if (n) spec.exported = t.identifier(n); } }, CallExpression(p) { if (p.node.callee.type !== "Import") return; const value = textValue(p.node.arguments[0]); if (!value?.startsWith("./")) return; const dep = path.basename(value); const target = lookup(dep, "default"); if (target.file) dependencies.push(target.file); p.node.arguments[0] = t.stringLiteral(target.source); }, }); for (const dep of dependencies) if (!emitted.has(dep)) pending.push(dep); if (owner) { const defaultExport = ast.program.body.find((n) => t.isExportDefaultDeclaration(n), ); if (!defaultExport) { const exportNode = ast.program.body.find( (n) => t.isExportNamedDeclaration(n) && n.specifiers.some((s) => s.exported.name === owner.exported), ); const spec = exportNode?.specifiers.find( (s) => s.exported.name === owner.exported, ); if (!spec) throw Error(`Missing own export ${owner.source}`); exportNode.specifiers = exportNode.specifiers.filter((s) => s !== spec); ast.program.body.push( t.exportDefaultDeclaration(t.identifier(spec.local.name)), ); } const route = routes.elements.find((r) => r._source === owner.source); if (route?._meta.length) ast.program.body.unshift( t.expressionStatement( t.callExpression(t.identifier("definePageMeta"), [ t.objectExpression(route._meta), ]), ), ); const styles = Object.values( pc.dependencies[owner.source]?.styles || {}, ).map((v) => v.file); for (const css of styles) ast.program.body.unshift( t.importDeclaration([], t.stringLiteral("~/assets/styles/" + css)), ); await add( paths.get(file), `\n`, ); } else await add(paths.get(file), generate(ast, { comments: true }).code); } await add( "app/utils/load-module.js", "export function loadModule(load) { return load(); }", ); await add( "app/utils/crypto.js", "import CryptoJS from 'crypto-js'; export function getCrypto() { return CryptoJS.SHA256; }", ); await add( "app/utils/brand.js", "export const logoUrl = '/images/brand/logo.png';", ); await add( "docs/module-inventory.json", JSON.stringify( { pages: own.filter(([p]) => p.startsWith("pages/")).map(([p]) => p), components: own .filter(([p]) => p.startsWith("components/")) .map(([p]) => p), layouts: own.filter(([p]) => p.startsWith("layouts/")).map(([p]) => p), shared: [...emitted] .filter((f) => !owners.has(f)) .map((f) => paths.get(f)), routes: routes.elements.map((r) => ({ source: r._source, path: textValue(r.properties.find((p) => p.key.name === "path").value), })), }, null, 2, ), ); for (const { file, code } of patchFiles) { const target = path.join(root, file); fs.mkdirSync(path.dirname(target), { recursive: true }); const patch = `*** Begin Patch\n*** Add File: ${target}\n${code .split("\n") .map((l) => "+" + l) .join("\n")}\n*** End Patch\n`; const result = spawnSync("apply_patch", [], { input: patch, encoding: "utf8", }); if (result.status !== 0) throw Error(result.stdout + result.stderr); } // Static binary assets retain their deployed paths; no JS bundle is copied. const cssFiles = [ ...new Set( Object.values(pc.modules) .filter((v) => v.resourceType === "style") .map((v) => v.file), ), ]; 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( /url\(([^)]*primeicons[^)]*)\)/g, (_, v) => `url(${v.replace(/^["']?|["']?$/g, "").replace(/^\.\//, "/icons/")})`, ); const out = path.join(root, "app/assets/styles", file); const patch = `*** Begin Patch\n*** Add File: ${out}\n+${css.replace(/\n/g, "\n+")}\n*** End Patch\n`; const result = spawnSync("apply_patch", [], { input: patch, encoding: "utf8", }); if (result.status !== 0) throw Error(result.stdout + result.stderr); } for (const name of fs.readdirSync(path.join(input, "public"))) if ( name !== "_nuxt" && !name.startsWith("._") && !["builds"].includes(name) ) { const src = path.join(input, "public", name); if (fs.lstatSync(src).isDirectory() || !name.endsWith(".js")) fs.cpSync(src, path.join(root, "public", name), { recursive: true, filter: (p) => !path.basename(p).startsWith("._"), }); } fs.mkdirSync(path.join(root, "public/icons"), { recursive: true }); for (const name of fs.readdirSync(chunkDir)) if (name.startsWith("primeicons.") && !name.endsWith(".js")) fs.copyFileSync( path.join(chunkDir, name), path.join(root, "public/icons", name), ); console.log( JSON.stringify({ sourceFiles: patchFiles.length, businessModules: emitted.size, css: cssFiles.length, }), );