598 lines
21 KiB
JavaScript
598 lines
21 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createHash } from "node:crypto";
|
|
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 { analyzeLive2DBootstrapGraph } from "../src/tools/live2dBootstrapGraph.js";
|
|
import { findMissingLive2DImplementationTargets } from "../src/tools/live2dImplementationTargets.js";
|
|
import {
|
|
buildLive2DModuleReviewPack,
|
|
type Live2DModuleReviewPackManifest,
|
|
} from "../src/tools/live2dModuleReviewPack.js";
|
|
import {
|
|
buildLive2DModuleGraphAnalysis,
|
|
buildLive2DModuleSplitCatalog,
|
|
type Live2DModuleGraphAnalysis,
|
|
type Live2DModuleCouplingEdge,
|
|
type Live2DModuleSplitCatalog,
|
|
validateLive2DModuleSplitCatalog,
|
|
} from "../src/tools/live2dModuleSplits.js";
|
|
import {
|
|
auditLive2DModuleMigrations,
|
|
type Live2DModuleMigrationDecisionCatalog,
|
|
} from "../src/tools/live2dModuleMigrations.js";
|
|
import { analyzeLive2DSourceBindings } from "../src/tools/live2dSourceBindings.js";
|
|
|
|
interface CliOptions {
|
|
bindingOutputPath: string;
|
|
bootstrapOutputPath: string;
|
|
callDecisionPath: string;
|
|
draftOutputPath: string;
|
|
functionDecisionPath: string;
|
|
finalOutputPath: string | null;
|
|
graphOutputPath: string;
|
|
implementationRoot: string;
|
|
migrationDecisionPath: string;
|
|
reviewResultPaths: string[];
|
|
reviewJsonOutputPath: string;
|
|
reviewMarkdownOutputPath: string;
|
|
sourcePath: string;
|
|
}
|
|
|
|
interface Live2DModuleOwnerReviewRow {
|
|
decision: "accept-owner" | "change-owner" | "omitted-unreachable";
|
|
evidence: string[];
|
|
functionId: string;
|
|
ownerModule: string | null;
|
|
reason: string;
|
|
reviewStatus: "reviewed";
|
|
}
|
|
|
|
interface Live2DModuleOwnerReviewResult {
|
|
findings: Array<{ severity: "Critical" | "Important" | "Minor" } & Record<string, unknown>>;
|
|
functionIdsSha256: string;
|
|
partitionId: string;
|
|
reviewedRows: Live2DModuleOwnerReviewRow[];
|
|
rowCount: number;
|
|
rowsSha256: string;
|
|
schemaVersion: 1 | "live2d-module-owner-review-result/v1";
|
|
sourceSha256: string;
|
|
summary: unknown;
|
|
}
|
|
|
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const workspaceRoot = path.resolve(
|
|
process.env.KT_WORKSPACE_ROOT || path.join(dirname, "../../.."),
|
|
);
|
|
const generatedRoot =
|
|
".kt-workspace/deobfuscate/blog-live2d-cubism2-minjs/live2d.min";
|
|
|
|
/** 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 source/catalog/output surface for the module-split audit. */
|
|
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 {
|
|
bindingOutputPath: resolveWorkspacePath(
|
|
args.get("binding-output") ?? `${generatedRoot}/module-source-bindings.generated.json`,
|
|
),
|
|
bootstrapOutputPath: resolveWorkspacePath(
|
|
args.get("bootstrap-output") ?? `${generatedRoot}/module-bootstrap.generated.json`,
|
|
),
|
|
callDecisionPath: resolveWorkspacePath(
|
|
args.get("call-decisions") ?? "docs/live2d-deobfuscation/call-resolutions.json",
|
|
),
|
|
draftOutputPath: resolveWorkspacePath(
|
|
args.get("draft-output") ?? `${generatedRoot}/module-splits.draft.generated.json`,
|
|
),
|
|
functionDecisionPath: resolveWorkspacePath(
|
|
args.get("function-decisions") ??
|
|
"docs/live2d-deobfuscation/function-name-decisions.json",
|
|
),
|
|
finalOutputPath: args.has("final-output")
|
|
? resolveWorkspacePath(args.get("final-output")!)
|
|
: null,
|
|
graphOutputPath: resolveWorkspacePath(
|
|
args.get("graph-output") ?? `${generatedRoot}/module-graph.generated.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",
|
|
),
|
|
reviewJsonOutputPath: resolveWorkspacePath(
|
|
args.get("review-json-output") ?? `${generatedRoot}/module-review-pack.generated.json`,
|
|
),
|
|
reviewResultPaths: ["a", "b", "c"].map((suffix) =>
|
|
resolveWorkspacePath(
|
|
args.get(`review-${suffix}`) ??
|
|
`.kt-workspace/subagents/results/2026-07-15-live2d-task4-review-${suffix}.json`,
|
|
),
|
|
),
|
|
reviewMarkdownOutputPath: resolveWorkspacePath(
|
|
args.get("review-markdown-output") ??
|
|
".kt-workspace/subagents/2026-07-15-live2d-task4-module-owner-source-pack.md",
|
|
),
|
|
sourcePath: resolveWorkspacePath(
|
|
args.get("source") ??
|
|
"Vue/kt-blog-web/public/live2d/wordpress-moc/live2d.min.js",
|
|
),
|
|
};
|
|
}
|
|
|
|
/** Reads a typed JSON catalog while leaving semantic validation to the builders. */
|
|
function readJson<T>(filePath: string): T {
|
|
return JSON.parse(readFileSync(filePath, "utf8")) as T;
|
|
}
|
|
|
|
/** Writes deterministic pretty JSON with one trailing LF. */
|
|
function writeJson(filePath: string, value: unknown): void {
|
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
/** Writes a generated UTF-8 text artifact after creating its evidence directory. */
|
|
function writeText(filePath: string, value: string): void {
|
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
writeFileSync(filePath, value.endsWith("\n") ? value : `${value}\n`, "utf8");
|
|
}
|
|
|
|
/** Adapts only bootstrap prototype ownership into structural function clusters. */
|
|
function buildBootstrapStructuralCouplingEdges(
|
|
bootstrapAnalysis: ReturnType<typeof analyzeLive2DBootstrapGraph>,
|
|
): Live2DModuleCouplingEdge[] {
|
|
return bootstrapAnalysis.couplingEdges
|
|
.filter((edge) => edge.kind === "prototype-owner")
|
|
.map((edge) => ({
|
|
couplingKey: edge.couplingKey,
|
|
functionIds: edge.functionIds,
|
|
kind: "prototype-owner" as const,
|
|
}));
|
|
}
|
|
|
|
/** Produces a stable SHA-256 for a generated text payload. */
|
|
function sha256(value: string): string {
|
|
return createHash("sha256").update(value).digest("hex");
|
|
}
|
|
|
|
/** Validates the three disjoint reviewer outputs and indexes their 627 decisions. */
|
|
function validateModuleOwnerReviews(
|
|
reviewPack: Live2DModuleReviewPackManifest,
|
|
results: readonly Live2DModuleOwnerReviewResult[],
|
|
): Map<string, Live2DModuleOwnerReviewRow & { partitionId: string }> {
|
|
if (results.length !== reviewPack.partitions.length) {
|
|
throw new Error("Live2D module review result partition count mismatch");
|
|
}
|
|
const decisions = new Map<
|
|
string,
|
|
Live2DModuleOwnerReviewRow & { partitionId: string }
|
|
>();
|
|
for (const partition of reviewPack.partitions) {
|
|
const result = results.find(
|
|
(candidate) => candidate.partitionId === partition.partitionId,
|
|
);
|
|
if (!result) {
|
|
throw new Error(`Missing Live2D module review result: ${partition.partitionId}`);
|
|
}
|
|
if (
|
|
(result.schemaVersion !== 1 &&
|
|
result.schemaVersion !== "live2d-module-owner-review-result/v1") ||
|
|
result.sourceSha256 !== reviewPack.sourceSha256 ||
|
|
result.rowCount !== partition.rowCount ||
|
|
result.functionIdsSha256 !== partition.functionIdsSha256 ||
|
|
result.rowsSha256 !== partition.rowsSha256 ||
|
|
result.reviewedRows.length !== partition.rows.length
|
|
) {
|
|
throw new Error(`Live2D module review seals mismatch: ${partition.partitionId}`);
|
|
}
|
|
const expectedById = new Map(
|
|
partition.rows.map((row) => [row.functionId, row]),
|
|
);
|
|
for (const row of result.reviewedRows) {
|
|
const expected = expectedById.get(row.functionId);
|
|
if (!expected || decisions.has(row.functionId)) {
|
|
throw new Error(`Duplicate or foreign Live2D module review row: ${row.functionId}`);
|
|
}
|
|
if (
|
|
row.reviewStatus !== "reviewed" ||
|
|
row.reason.trim().length === 0 ||
|
|
row.evidence.length === 0
|
|
) {
|
|
throw new Error(`Incomplete Live2D module review row: ${row.functionId}`);
|
|
}
|
|
if (expected.ownerCandidate === null && expected.reachability !== "reachable") {
|
|
if (row.decision !== "omitted-unreachable" || row.ownerModule !== null) {
|
|
throw new Error(`Reachability mismatch in module review: ${row.functionId}`);
|
|
}
|
|
} else if (row.ownerModule === null || row.decision === "omitted-unreachable") {
|
|
throw new Error(`Reachable function lacks module owner: ${row.functionId}`);
|
|
} else if (
|
|
row.decision === "accept-owner" &&
|
|
row.ownerModule !== expected.ownerCandidate
|
|
) {
|
|
throw new Error(`Accepted owner differs from candidate: ${row.functionId}`);
|
|
} else if (
|
|
row.decision === "change-owner" &&
|
|
row.ownerModule === expected.ownerCandidate
|
|
) {
|
|
throw new Error(`Changed owner still equals candidate: ${row.functionId}`);
|
|
}
|
|
decisions.set(row.functionId, {
|
|
...row,
|
|
partitionId: partition.partitionId,
|
|
});
|
|
}
|
|
}
|
|
if (decisions.size !== reviewPack.summary.functionCount) {
|
|
throw new Error(`Live2D module review coverage is ${decisions.size}/627`);
|
|
}
|
|
return decisions;
|
|
}
|
|
|
|
/** Rebuilds module dependency rows after applying the reviewed owner decisions. */
|
|
function buildReviewedModules(
|
|
functions: Array<
|
|
Live2DModuleSplitCatalog["functions"][number] & {
|
|
ownerReview: Live2DModuleOwnerReviewRow & { partitionId: string };
|
|
}
|
|
>,
|
|
graph: Live2DModuleGraphAnalysis,
|
|
): Array<Record<string, unknown>> {
|
|
const functionById = new Map(functions.map((entry) => [entry.functionId, entry]));
|
|
const modules = new Map<
|
|
string,
|
|
{
|
|
clusterIds: Set<string>;
|
|
couplingKeys: Set<string>;
|
|
couplingModules: Set<string>;
|
|
dependencyModules: Set<string>;
|
|
implementationFunctionIds: string[];
|
|
ownedFunctionIds: string[];
|
|
}
|
|
>();
|
|
for (const entry of functions) {
|
|
const modulePaths = new Set([
|
|
...(entry.ownerModule ? [entry.ownerModule] : []),
|
|
...entry.implementationModules,
|
|
]);
|
|
for (const modulePath of modulePaths) {
|
|
const module = modules.get(modulePath) ?? {
|
|
clusterIds: new Set<string>(),
|
|
couplingKeys: new Set<string>(),
|
|
couplingModules: new Set<string>(),
|
|
dependencyModules: new Set<string>(),
|
|
implementationFunctionIds: [],
|
|
ownedFunctionIds: [],
|
|
};
|
|
module.clusterIds.add(entry.clusterId);
|
|
if (entry.implementationModules.includes(modulePath)) {
|
|
module.implementationFunctionIds.push(entry.functionId);
|
|
}
|
|
if (entry.ownerModule === modulePath) {
|
|
module.ownedFunctionIds.push(entry.functionId);
|
|
}
|
|
modules.set(modulePath, module);
|
|
}
|
|
if (entry.ownerModule) {
|
|
for (const implementationModule of entry.implementationModules) {
|
|
if (implementationModule !== entry.ownerModule) {
|
|
modules
|
|
.get(entry.ownerModule)!
|
|
.dependencyModules.add(implementationModule);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const edge of graph.callEdges) {
|
|
const caller = functionById.get(edge.callerFunctionId)?.ownerModule;
|
|
const target = functionById.get(edge.targetFunctionId)?.ownerModule;
|
|
if (caller && target && caller !== target) {
|
|
modules.get(caller)!.dependencyModules.add(target);
|
|
}
|
|
}
|
|
for (const edge of graph.couplingEdges) {
|
|
const owners = [
|
|
...new Set(
|
|
edge.functionIds
|
|
.map((functionId) => functionById.get(functionId)?.ownerModule)
|
|
.filter((owner): owner is string => owner !== null && owner !== undefined),
|
|
),
|
|
];
|
|
for (const owner of owners) {
|
|
const module = modules.get(owner)!;
|
|
module.couplingKeys.add(edge.couplingKey);
|
|
for (const peer of owners) {
|
|
if (peer !== owner) {
|
|
module.couplingModules.add(peer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return [...modules.entries()]
|
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
.map(([modulePath, module]) => ({
|
|
clusterIds: [...module.clusterIds].sort(),
|
|
couplingKeys: [...module.couplingKeys].sort(),
|
|
couplingModules: [...module.couplingModules].sort(),
|
|
dependencyModules: [...module.dependencyModules].sort(),
|
|
implementationFunctionIds: module.implementationFunctionIds.sort(
|
|
(left, right) =>
|
|
functionById.get(left)!.sourceOrder - functionById.get(right)!.sourceOrder,
|
|
),
|
|
ownedFunctionIds: module.ownedFunctionIds.sort(
|
|
(left, right) =>
|
|
functionById.get(left)!.sourceOrder - functionById.get(right)!.sourceOrder,
|
|
),
|
|
modulePath,
|
|
}));
|
|
}
|
|
|
|
/** Builds the durable reviewed split manifest with ownership and initialization evidence. */
|
|
function buildReviewedModuleSplitManifest(
|
|
reviewPack: Live2DModuleReviewPackManifest,
|
|
draft: Live2DModuleSplitCatalog,
|
|
graph: Live2DModuleGraphAnalysis,
|
|
bindingAnalysis: ReturnType<typeof analyzeLive2DSourceBindings>,
|
|
bootstrapAnalysis: ReturnType<typeof analyzeLive2DBootstrapGraph>,
|
|
results: readonly Live2DModuleOwnerReviewResult[],
|
|
): Record<string, unknown> {
|
|
const decisions = validateModuleOwnerReviews(reviewPack, results);
|
|
const functions = draft.functions.map((entry) => {
|
|
const ownerReview = decisions.get(entry.functionId)!;
|
|
const ownerModule = ownerReview.ownerModule;
|
|
return {
|
|
...entry,
|
|
ownerModule,
|
|
ownerReview,
|
|
ownershipStatus:
|
|
ownerModule === null ? ("omitted-unreachable" as const) : ("owned" as const),
|
|
};
|
|
});
|
|
const criticalOrImportant = results.flatMap((result) => result.findings).filter(
|
|
(finding) =>
|
|
finding.severity === "Critical" || finding.severity === "Important",
|
|
);
|
|
if (criticalOrImportant.length > 0) {
|
|
throw new Error("Live2D module owner review has blocking findings");
|
|
}
|
|
const modules = buildReviewedModules(functions, graph);
|
|
if (modules.length !== draft.modules.length) {
|
|
throw new Error(
|
|
`Live2D reviewed manifest lost implementation modules: ${modules.length}/${draft.modules.length}`,
|
|
);
|
|
}
|
|
return {
|
|
bootstrap: bootstrapAnalysis,
|
|
functions,
|
|
graphSummary: graph.summary,
|
|
modules,
|
|
reviews: results.map((result) => ({
|
|
findings: result.findings,
|
|
functionIdsSha256: result.functionIdsSha256,
|
|
partitionId: result.partitionId,
|
|
rowCount: result.rowCount,
|
|
rowsSha256: result.rowsSha256,
|
|
summary: result.summary,
|
|
})),
|
|
schemaVersion: "live2d-module-splits/v1",
|
|
sharedImplementations: draft.sharedImplementations,
|
|
sourceBindings: bindingAnalysis,
|
|
sourcePath: reviewPack.sourcePath,
|
|
sourceSha256: reviewPack.sourceSha256,
|
|
summary: {
|
|
blockingFindingCount: criticalOrImportant.length,
|
|
functionCount: functions.length,
|
|
moduleCount: modules.length,
|
|
ownerModuleCount: new Set(
|
|
functions.map((entry) => entry.ownerModule).filter(Boolean),
|
|
).size,
|
|
omittedFunctionCount: functions.filter((entry) => entry.ownerModule === null)
|
|
.length,
|
|
ownedFunctionCount: functions.filter((entry) => entry.ownerModule !== null)
|
|
.length,
|
|
reviewedFunctionCount: decisions.size,
|
|
sharedImplementationCount: draft.sharedImplementations.length,
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Adds source-sealed semantic migration state to every reviewed owner decision. */
|
|
function attachModuleMigrationState(
|
|
manifest: Record<string, unknown>,
|
|
source: string,
|
|
sourceFunctions: ReturnType<typeof analyzeLive2DMinJsSource>["functions"],
|
|
options: CliOptions,
|
|
): Record<string, unknown> {
|
|
const migrationAudit = auditLive2DModuleMigrations({
|
|
decisions: readJson<Live2DModuleMigrationDecisionCatalog>(options.migrationDecisionPath),
|
|
implementationRoot: options.implementationRoot,
|
|
moduleManifest: manifest as unknown as Parameters<
|
|
typeof auditLive2DModuleMigrations
|
|
>[0]["moduleManifest"],
|
|
source,
|
|
sourceFunctions,
|
|
workspaceRoot,
|
|
});
|
|
const migrationByFunctionId = new Map(
|
|
migrationAudit.functions.map((entry) => [entry.functionId, entry.migrationStatus]),
|
|
);
|
|
const functions = (manifest.functions as Array<Record<string, unknown>>).map((entry) => ({
|
|
...entry,
|
|
migrationStatus: migrationByFunctionId.get(String(entry.functionId)),
|
|
}));
|
|
return {
|
|
...manifest,
|
|
functions,
|
|
schemaVersion: "live2d-module-splits/v2",
|
|
summary: {
|
|
...(manifest.summary as Record<string, unknown>),
|
|
migratedFunctionCount: migrationAudit.summary.migrated,
|
|
omittedMigrationFunctionCount: migrationAudit.summary.omittedUnreachable,
|
|
pendingMigrationFunctionCount: migrationAudit.summary.pending,
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Generates all source-sealed Task 4 audits and the exhaustive three-way review pack. */
|
|
function main(): void {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
const source = readFileSync(options.sourcePath, "utf8");
|
|
const sourceLabel = path
|
|
.relative(workspaceRoot, options.sourcePath)
|
|
.replaceAll("\\", "/");
|
|
const sourceAudit = analyzeLive2DMinJsSource(source, {
|
|
sourcePath: sourceLabel,
|
|
});
|
|
const functionCatalog = buildFunctionNameReviewCatalog(
|
|
sourceAudit,
|
|
buildLegacyFunctionRenameCatalog(sourceAudit, []),
|
|
readJson<Live2DFunctionNameDecisionCatalog>(options.functionDecisionPath),
|
|
);
|
|
const implementationRoot = path.resolve(options.implementationRoot);
|
|
const implementationRootPrefix = `${implementationRoot}${path.sep}`;
|
|
const missingImplementationTargets = findMissingLive2DImplementationTargets(
|
|
functionCatalog.functions,
|
|
(modulePath) => {
|
|
const implementationPath = path.resolve(implementationRoot, modulePath);
|
|
if (!implementationPath.startsWith(implementationRootPrefix)) {
|
|
return null;
|
|
}
|
|
return readFileSync(implementationPath, "utf8");
|
|
},
|
|
);
|
|
if (missingImplementationTargets.length > 0) {
|
|
throw new Error(
|
|
`Live2D reviewed implementation targets are missing: ${missingImplementationTargets
|
|
.map((entry) => `${entry.functionId} -> ${entry.targetSymbol} (${entry.reason})`)
|
|
.join(", ")}`,
|
|
);
|
|
}
|
|
const callCatalog = buildCallResolutionReviewCatalog(
|
|
sourceAudit,
|
|
functionCatalog,
|
|
readJson<Live2DCallResolutionDecisionCatalog>(options.callDecisionPath),
|
|
);
|
|
const bindingAnalysis = analyzeLive2DSourceBindings(
|
|
source,
|
|
sourceAudit,
|
|
functionCatalog,
|
|
);
|
|
const bootstrapAnalysis = analyzeLive2DBootstrapGraph(
|
|
source,
|
|
sourceAudit,
|
|
functionCatalog,
|
|
callCatalog,
|
|
);
|
|
const graph = buildLive2DModuleGraphAnalysis(
|
|
functionCatalog,
|
|
callCatalog,
|
|
[
|
|
...bindingAnalysis.couplingEdges,
|
|
...buildBootstrapStructuralCouplingEdges(bootstrapAnalysis),
|
|
],
|
|
);
|
|
const draft = buildLive2DModuleSplitCatalog(functionCatalog, graph);
|
|
const validation = validateLive2DModuleSplitCatalog(
|
|
functionCatalog,
|
|
graph,
|
|
draft,
|
|
);
|
|
const reviewPack = buildLive2DModuleReviewPack(
|
|
functionCatalog,
|
|
graph,
|
|
draft,
|
|
);
|
|
|
|
writeJson(options.bindingOutputPath, bindingAnalysis);
|
|
writeJson(options.bootstrapOutputPath, bootstrapAnalysis);
|
|
writeJson(options.graphOutputPath, graph);
|
|
writeJson(options.draftOutputPath, draft);
|
|
writeText(options.reviewJsonOutputPath, reviewPack.json);
|
|
writeText(options.reviewMarkdownOutputPath, reviewPack.markdown);
|
|
let finalOutput: { path: string; sha256: string } | null = null;
|
|
if (options.finalOutputPath) {
|
|
const reviewedManifest = attachModuleMigrationState(
|
|
buildReviewedModuleSplitManifest(
|
|
reviewPack.manifest,
|
|
draft,
|
|
graph,
|
|
bindingAnalysis,
|
|
bootstrapAnalysis,
|
|
options.reviewResultPaths.map((filePath) =>
|
|
readJson<Live2DModuleOwnerReviewResult>(filePath),
|
|
),
|
|
),
|
|
source,
|
|
sourceAudit.functions,
|
|
options,
|
|
);
|
|
const reviewedJson = `${JSON.stringify(reviewedManifest, null, 2)}\n`;
|
|
writeText(options.finalOutputPath, reviewedJson);
|
|
finalOutput = {
|
|
path: options.finalOutputPath,
|
|
sha256: sha256(reviewedJson),
|
|
};
|
|
}
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
bindingSummary: bindingAnalysis.summary,
|
|
bootstrapSummary: bootstrapAnalysis.summary,
|
|
graphSummary: graph.summary,
|
|
moduleValidation: validation,
|
|
outputs: {
|
|
binding: options.bindingOutputPath,
|
|
bootstrap: options.bootstrapOutputPath,
|
|
draft: options.draftOutputPath,
|
|
graph: options.graphOutputPath,
|
|
reviewJson: options.reviewJsonOutputPath,
|
|
reviewMarkdown: options.reviewMarkdownOutputPath,
|
|
reviewedManifest: finalOutput,
|
|
},
|
|
reviewPack: {
|
|
jsonSha256: reviewPack.jsonSha256,
|
|
markdownSha256: reviewPack.markdownSha256,
|
|
summary: reviewPack.manifest.summary,
|
|
},
|
|
sourceSha256: sourceAudit.sourceSha256,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
|
|
main();
|