250 lines
8.7 KiB
JavaScript
250 lines
8.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import {
|
|
analyzeLive2DMinJsSource,
|
|
buildCallResolutionReviewCatalog,
|
|
buildFunctionNameReviewCatalog,
|
|
buildLegacyFunctionRenameCatalog,
|
|
type Live2DCallResolutionDecisionCatalog,
|
|
type Live2DFunctionNameDecisionCatalog,
|
|
} from "../src/tools/deobfuscation.js";
|
|
import {
|
|
auditLive2DModuleMigrations,
|
|
type Live2DModuleMigrationDecisionCatalog,
|
|
type Live2DModuleMigrationManifest,
|
|
} from "../src/tools/live2dModuleMigrations.js";
|
|
import { analyzeLive2DBootstrapGraph } from "../src/tools/live2dBootstrapGraph.js";
|
|
import {
|
|
auditLive2DRuntimeCoverage,
|
|
type Live2DRuntimeBehaviorDecisionCatalog,
|
|
} from "../src/tools/live2dRuntimeCoverage.js";
|
|
|
|
interface CliOptions {
|
|
behaviorDecisionPath: string;
|
|
callDecisionPath: string;
|
|
functionDecisionPath: string;
|
|
implementationRoot: string;
|
|
migrationDecisionPath: string;
|
|
moduleManifestPath: string;
|
|
outputPath: string;
|
|
productionRoot: string;
|
|
sourcePath: string;
|
|
}
|
|
|
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const workspaceRoot = path.resolve(
|
|
process.env.KT_WORKSPACE_ROOT || path.join(dirname, "../../.."),
|
|
);
|
|
|
|
/** Resolves a CLI path against the shared KT workspace root. */
|
|
function resolveWorkspacePath(value: string): string {
|
|
return path.isAbsolute(value) ? value : path.resolve(workspaceRoot, value);
|
|
}
|
|
|
|
/** Parses the bounded current-state runtime coverage CLI. */
|
|
function parseArgs(argv: string[]): CliOptions {
|
|
const args = new Map<string, string>();
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const item = argv[index];
|
|
if (!item.startsWith("--")) continue;
|
|
const [key, inlineValue] = item.slice(2).split("=", 2);
|
|
if (inlineValue !== undefined) {
|
|
args.set(key, inlineValue);
|
|
continue;
|
|
}
|
|
const next = argv[index + 1];
|
|
if (next && !next.startsWith("--")) {
|
|
args.set(key, next);
|
|
index += 1;
|
|
}
|
|
}
|
|
return {
|
|
behaviorDecisionPath: resolveWorkspacePath(
|
|
args.get("behavior-decisions") ??
|
|
"docs/live2d-deobfuscation/runtime-behavior-decisions.json",
|
|
),
|
|
callDecisionPath: resolveWorkspacePath(
|
|
args.get("call-decisions") ?? "docs/live2d-deobfuscation/call-resolutions.json",
|
|
),
|
|
functionDecisionPath: resolveWorkspacePath(
|
|
args.get("function-decisions") ??
|
|
"docs/live2d-deobfuscation/function-name-decisions.json",
|
|
),
|
|
implementationRoot: resolveWorkspacePath(
|
|
args.get("implementation-root") ??
|
|
"Vue/kt-blog-web/src/components/blog/live2d/vendor/cubism2Core",
|
|
),
|
|
migrationDecisionPath: resolveWorkspacePath(
|
|
args.get("migration-decisions") ??
|
|
"docs/live2d-deobfuscation/module-migration-decisions.json",
|
|
),
|
|
moduleManifestPath: resolveWorkspacePath(
|
|
args.get("module-manifest") ?? "docs/live2d-deobfuscation/module-splits.json",
|
|
),
|
|
outputPath: resolveWorkspacePath(
|
|
args.get("output") ??
|
|
".kt-workspace/deobfuscate/blog-live2d-cubism2-minjs/live2d.min/runtime-coverage.generated.json",
|
|
),
|
|
productionRoot: resolveWorkspacePath(
|
|
args.get("production-root") ?? "Vue/kt-blog-web/src/components/blog/live2d",
|
|
),
|
|
sourcePath: resolveWorkspacePath(
|
|
args.get("source") ??
|
|
"Vue/kt-blog-web/public/live2d/wordpress-moc/live2d.min.js",
|
|
),
|
|
};
|
|
}
|
|
|
|
/** Reads one typed JSON input while semantic validation remains in the audit. */
|
|
function readJson<T>(filePath: string): T {
|
|
return JSON.parse(readFileSync(filePath, "utf8")) as T;
|
|
}
|
|
|
|
/** Writes one deterministic generated coverage artifact under `.kt-workspace`. */
|
|
function writeJson(filePath: string, value: unknown): void {
|
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
/** Rebuilds every current catalog and emits the complete runtime coverage report. */
|
|
function main(): void {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
const source = readFileSync(options.sourcePath, "utf8");
|
|
const sourcePath = path.relative(workspaceRoot, options.sourcePath).replaceAll("\\", "/");
|
|
const sourceAudit = analyzeLive2DMinJsSource(source, { sourcePath });
|
|
const functionDecisions = readJson<Live2DFunctionNameDecisionCatalog>(
|
|
options.functionDecisionPath,
|
|
);
|
|
const callDecisions = readJson<Live2DCallResolutionDecisionCatalog>(
|
|
options.callDecisionPath,
|
|
);
|
|
const functionCatalog = buildFunctionNameReviewCatalog(
|
|
sourceAudit,
|
|
buildLegacyFunctionRenameCatalog(sourceAudit, []),
|
|
functionDecisions,
|
|
);
|
|
const callCatalog = buildCallResolutionReviewCatalog(
|
|
sourceAudit,
|
|
functionCatalog,
|
|
callDecisions,
|
|
);
|
|
const bootstrapGraph = analyzeLive2DBootstrapGraph(
|
|
source,
|
|
sourceAudit,
|
|
functionCatalog,
|
|
callCatalog,
|
|
);
|
|
const moduleManifest = readJson<Live2DModuleMigrationManifest>(
|
|
options.moduleManifestPath,
|
|
);
|
|
const migrationCatalog = readJson<Live2DModuleMigrationDecisionCatalog>(
|
|
options.migrationDecisionPath,
|
|
);
|
|
const migrationAudit = auditLive2DModuleMigrations({
|
|
decisions: migrationCatalog,
|
|
implementationRoot: options.implementationRoot,
|
|
moduleManifest,
|
|
source,
|
|
sourceFunctions: sourceAudit.functions,
|
|
workspaceRoot,
|
|
});
|
|
const behaviorCatalog = readJson<Live2DRuntimeBehaviorDecisionCatalog>(
|
|
options.behaviorDecisionPath,
|
|
);
|
|
const runtimeRoot = path.join(options.productionRoot, "runtime");
|
|
const runtimeEntryPath = path.join(options.implementationRoot, "runtimeCore.ts");
|
|
const rendererPath = path.join(runtimeRoot, "webglLive2DRenderer.ts");
|
|
const coordinatesPath = path.join(runtimeRoot, "cubism2PointerCoordinates.ts");
|
|
const coordinateSpec = resolveWorkspacePath(
|
|
"Vue/kt-blog-web/src/__tests__/live2d/Cubism2PointerCoordinates.spec.ts",
|
|
);
|
|
const trackingSpec = resolveWorkspacePath(
|
|
"Vue/kt-blog-web/src/__tests__/live2d/Cubism2PointerTracking.spec.ts",
|
|
);
|
|
const report = auditLive2DRuntimeCoverage({
|
|
behaviorCatalog,
|
|
callCatalog,
|
|
coreRoot: options.implementationRoot,
|
|
expected: {
|
|
calls: 1411,
|
|
functions: 627,
|
|
migrated: 567,
|
|
modules: 38,
|
|
omittedUnreachable: 60,
|
|
supportModules: 5,
|
|
},
|
|
functionCatalog,
|
|
fullPageGazeCoordinatesPath: coordinatesPath,
|
|
fullPageGazeRendererPath: rendererPath,
|
|
migrationAudit,
|
|
migrationCatalog,
|
|
moduleManifest,
|
|
productionRoot: options.productionRoot,
|
|
productBehaviorTests: [
|
|
{
|
|
filePath: coordinateSpec,
|
|
testName: "uses the whole viewport as a continuous page-level look-at range",
|
|
},
|
|
{
|
|
filePath: coordinateSpec,
|
|
testName: "retains page-level distance after the pointer leaves the canvas",
|
|
},
|
|
{
|
|
filePath: trackingSpec,
|
|
testName: "tracks mouse movement across the page and removes every listener on destroy",
|
|
},
|
|
],
|
|
retiredPaths: [
|
|
path.join(options.productionRoot, "vendor/cubism2Core.ts"),
|
|
path.join(runtimeRoot, "cubism2CoreLoader.ts"),
|
|
path.join(options.implementationRoot, "compatibility"),
|
|
path.join(options.implementationRoot, "coreGlobals.ts"),
|
|
path.join(options.implementationRoot, "legacyKernel.ts"),
|
|
path.join(options.implementationRoot, "sdkGlobalInstaller.ts"),
|
|
path.join(options.implementationRoot, "sdkGlobalNames.ts"),
|
|
path.join(options.implementationRoot, "autoEyeBlink.ts"),
|
|
path.join(options.implementationRoot, "legacyMotion.ts"),
|
|
path.join(options.implementationRoot, "webglAttributes.ts"),
|
|
path.join(options.implementationRoot, "webglBlendFactors.ts"),
|
|
path.join(options.implementationRoot, "webglDrawTail.ts"),
|
|
path.join(options.implementationRoot, "webglFramebuffer.ts"),
|
|
path.join(options.implementationRoot, "webglShaderLocations.ts"),
|
|
path.join(options.implementationRoot, "webglTextureBindings.ts"),
|
|
path.join(options.implementationRoot, "webglUniforms.ts"),
|
|
],
|
|
runtimeConsumerPaths: [rendererPath],
|
|
runtimeEntryPath,
|
|
sourceWindowExports: bootstrapGraph.windowExports
|
|
.slice()
|
|
.sort((left, right) => left.exportOrder - right.exportOrder)
|
|
.map((entry) => entry.exportName),
|
|
workspaceRoot,
|
|
});
|
|
writeJson(options.outputPath, report);
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
calls: report.calls,
|
|
modules: report.modules,
|
|
ok: true,
|
|
outputPath: path.relative(workspaceRoot, options.outputPath).replaceAll("\\", "/"),
|
|
postCoreContracts: report.postCoreContracts,
|
|
sourceSha256: report.sourceSha256,
|
|
summary: report.summary,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exit(1);
|
|
}
|