#!/usr/bin/env node import { readFileSync } from "node:fs"; import path from "node:path"; import { resolveInsideRoot } from "../src/core/workspace.js"; import { analyzeLive2DMinJsSource } from "../src/tools/deobfuscation.js"; import { auditLive2DModuleMigrations, type Live2DModuleMigrationDecisionCatalog, type Live2DModuleMigrationDraftManifest, } from "../src/tools/live2dModuleMigrations.js"; import { auditLive2DIdentifierNameDecisionCatalog, assertLive2DVariableDecisionCatalogIsOffline, auditLive2DVariableNameDecisionCatalog, type Live2DIdentifierNameDecisionCatalog, type Live2DVariableNameDecisionCatalog, type Live2DVariableNameSummary, } from "../src/tools/live2dVariableNames.js"; interface CliOptions { identifierDecisionPath: string; implementationRoot: string; manifestPath: string; moduleDecisionPath: string; productionRoot: string; sourcePath: string; variableDecisionPath: string; } const EXPECTED_SUMMARY: Live2DVariableNameSummary = { directImplementationBindings: { classifiedSynthetic: 67, coveredFromSource: 1325, locals: 810, parameters: 582, total: 1392, }, functions: 567, helperSyntheticBindings: 5, ignoredLeadingImplementationThis: 116, nonDirectSourceMappings: 413, reviewedBindingSets: 76, sourceBindings: { locals: 1169, parameters: 578, total: 1747 }, sourceDispositions: { inlined: 233, merged: 110, "omitted-unreachable": 1, "removed-source-defect": 5, renamed: 1398, }, syntheticDispositions: { "defensive-extension": 3, "semantic-split": 53, "synthetic-helper": 16, }, }; /** Parses only the workspace-bounded paths consumed by the current-state audit. */ function parseOptions(argv: string[]): CliOptions { const args = new Map(); for (let index = 0; index < argv.length; index += 2) { const key = argv[index]; const value = argv[index + 1]; if (!key?.startsWith("--") || value === undefined) { throw new Error(`Invalid Live2D variable audit argument near ${key ?? ""}`); } args.set(key.slice(2), value); } return { identifierDecisionPath: resolveInsideRoot( args.get("identifier-decisions") ?? "docs/live2d-deobfuscation/identifier-name-decisions.json", ), implementationRoot: resolveInsideRoot( args.get("implementation-root") ?? "Vue/kt-blog-web/src/components/blog/live2d/vendor/cubism2Core", ), manifestPath: resolveInsideRoot( args.get("manifest") ?? "docs/live2d-deobfuscation/module-splits.json", ), moduleDecisionPath: resolveInsideRoot( args.get("module-decisions") ?? "docs/live2d-deobfuscation/module-migration-decisions.json", ), productionRoot: resolveInsideRoot( args.get("production-root") ?? "Vue/kt-blog-web/src/components/blog/live2d", ), sourcePath: resolveInsideRoot( args.get("source") ?? "Vue/kt-blog-web/public/live2d/wordpress-moc/live2d.min.js", ), variableDecisionPath: resolveInsideRoot( args.get("variable-decisions") ?? "docs/live2d-deobfuscation/variable-name-decisions.json", ), }; } /** Reads one typed JSON catalog and leaves semantic validation to its audit function. */ function readJson(filePath: string): T { return JSON.parse(readFileSync(filePath, "utf8")) as T; } /** Runs function-level migration proof before variable-level coverage and offline-only gates. */ function main(): void { const options = parseOptions(process.argv.slice(2)); const source = readFileSync(options.sourcePath, "utf8"); const sourceAudit = analyzeLive2DMinJsSource(source, { sourcePath: path .relative(resolveInsideRoot("."), options.sourcePath) .replaceAll("\\", "/"), }); const moduleCatalog = readJson( options.moduleDecisionPath, ); const migration = auditLive2DModuleMigrations({ decisions: moduleCatalog, implementationRoot: options.implementationRoot, moduleManifest: readJson(options.manifestPath), source, sourceFunctions: sourceAudit.functions, workspaceRoot: resolveInsideRoot("."), }); if ( migration.summary.migrated !== 567 || migration.summary.omittedUnreachable !== 60 || migration.summary.pending !== 0 || migration.summary.total !== 627 ) { throw new Error(`Live2D module migration prerequisite is incomplete: ${JSON.stringify(migration.summary)}`); } const variable = auditLive2DVariableNameDecisionCatalog({ catalog: readJson(options.variableDecisionPath), implementationRoot: options.implementationRoot, moduleCatalog, }); if (JSON.stringify(variable.summary) !== JSON.stringify(EXPECTED_SUMMARY)) { throw new Error(`Live2D variable census drifted: ${JSON.stringify(variable.summary)}`); } const identifiers = auditLive2DIdentifierNameDecisionCatalog( readJson(options.identifierDecisionPath), options.productionRoot, ); const expectedIdentifierSummary = { "keep-semantic": 1, remove: 10, rename: 42, total: 53, unresolved: 0, }; if (JSON.stringify(identifiers.summary) !== JSON.stringify(expectedIdentifierSummary)) { throw new Error( `Live2D identifier census drifted: ${JSON.stringify(identifiers.summary)}`, ); } assertLive2DVariableDecisionCatalogIsOffline(options.productionRoot); process.stdout.write( `${JSON.stringify({ identifiers, migration: migration.summary, offlineOnly: true, ownerHelperTargets: variable.ownerHelperTargets, sourceSha256: variable.sourceSha256, variable: variable.summary, })}\n`, ); } main();