@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +40 -23
- package/dist/build-suggested-call.d.ts +29 -0
- package/dist/build-suggested-call.js +58 -0
- package/dist/cache-registry.d.ts +3 -1
- package/dist/cache-registry.js +50 -6
- package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
- package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
- package/dist/entry-tools/batch-class-members-service.js +97 -0
- package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
- package/dist/entry-tools/batch-class-source-service.js +100 -0
- package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
- package/dist/entry-tools/batch-mappings-service.js +66 -0
- package/dist/entry-tools/batch-runner.d.ts +72 -0
- package/dist/entry-tools/batch-runner.js +90 -0
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
- package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
- package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
- package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
- package/dist/entry-tools/manage-cache-service.d.ts +16 -16
- package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
- package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
- package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
- package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
- package/dist/entry-tools/validate-project/internal.d.ts +142 -0
- package/dist/entry-tools/validate-project/internal.js +303 -0
- package/dist/entry-tools/validate-project-service.d.ts +67 -47
- package/dist/entry-tools/validate-project-service.js +13 -563
- package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
- package/dist/entry-tools/verify-mixin-target-service.js +323 -0
- package/dist/error-mapping.d.ts +40 -0
- package/dist/error-mapping.js +139 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +147 -1354
- package/dist/mapping/internal-types.d.ts +54 -0
- package/dist/mapping/internal-types.js +14 -0
- package/dist/mapping/loaders/mojang.d.ts +2 -0
- package/dist/mapping/loaders/mojang.js +64 -0
- package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
- package/dist/mapping/loaders/tiny-loom.js +73 -0
- package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
- package/dist/mapping/loaders/tiny-maven.js +104 -0
- package/dist/mapping/loaders/types.d.ts +14 -0
- package/dist/mapping/loaders/types.js +2 -0
- package/dist/mapping/lookup.d.ts +52 -0
- package/dist/mapping/lookup.js +496 -0
- package/dist/mapping/parsers/normalize.d.ts +10 -0
- package/dist/mapping/parsers/normalize.js +52 -0
- package/dist/mapping/parsers/proguard.d.ts +20 -0
- package/dist/mapping/parsers/proguard.js +138 -0
- package/dist/mapping/parsers/symbol-records.d.ts +27 -0
- package/dist/mapping/parsers/symbol-records.js +216 -0
- package/dist/mapping/parsers/tiny.d.ts +9 -0
- package/dist/mapping/parsers/tiny.js +96 -0
- package/dist/mapping/types.d.ts +147 -0
- package/dist/mapping/types.js +2 -0
- package/dist/mapping-pipeline-service.js +3 -2
- package/dist/mapping-service.d.ts +8 -145
- package/dist/mapping-service.js +30 -1207
- package/dist/mixin/access-validators.d.ts +9 -0
- package/dist/mixin/access-validators.js +257 -0
- package/dist/mixin/annotation-validators.d.ts +5 -0
- package/dist/mixin/annotation-validators.js +162 -0
- package/dist/mixin/helpers.d.ts +28 -0
- package/dist/mixin/helpers.js +315 -0
- package/dist/mixin/parsed-validator.d.ts +8 -0
- package/dist/mixin/parsed-validator.js +337 -0
- package/dist/mixin/types.d.ts +208 -0
- package/dist/mixin/types.js +28 -0
- package/dist/mixin-validator.d.ts +9 -201
- package/dist/mixin-validator.js +8 -1020
- package/dist/source/access-validate.d.ts +4 -0
- package/dist/source/access-validate.js +254 -0
- package/dist/source/artifact-resolver.d.ts +111 -0
- package/dist/source/artifact-resolver.js +1271 -0
- package/dist/source/cache-metrics.d.ts +26 -0
- package/dist/source/cache-metrics.js +172 -0
- package/dist/source/class-source/members-builder.d.ts +34 -0
- package/dist/source/class-source/members-builder.js +46 -0
- package/dist/source/class-source/snippet-builder.d.ts +19 -0
- package/dist/source/class-source/snippet-builder.js +46 -0
- package/dist/source/class-source-helpers.d.ts +34 -0
- package/dist/source/class-source-helpers.js +140 -0
- package/dist/source/class-source.d.ts +42 -0
- package/dist/source/class-source.js +883 -0
- package/dist/source/descriptor-utils.d.ts +6 -0
- package/dist/source/descriptor-utils.js +37 -0
- package/dist/source/file-access.d.ts +4 -0
- package/dist/source/file-access.js +102 -0
- package/dist/source/indexer.d.ts +82 -0
- package/dist/source/indexer.js +522 -0
- package/dist/source/lifecycle/diff-utils.d.ts +9 -0
- package/dist/source/lifecycle/diff-utils.js +107 -0
- package/dist/source/lifecycle/diff.d.ts +2 -0
- package/dist/source/lifecycle/diff.js +265 -0
- package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
- package/dist/source/lifecycle/mapping-helpers.js +327 -0
- package/dist/source/lifecycle/runtime-check.d.ts +2 -0
- package/dist/source/lifecycle/runtime-check.js +142 -0
- package/dist/source/lifecycle/trace.d.ts +2 -0
- package/dist/source/lifecycle/trace.js +231 -0
- package/dist/source/lifecycle.d.ts +4 -0
- package/dist/source/lifecycle.js +5 -0
- package/dist/source/search.d.ts +51 -0
- package/dist/source/search.js +676 -0
- package/dist/source/shared-utils.d.ts +6 -0
- package/dist/source/shared-utils.js +55 -0
- package/dist/source/state.d.ts +26 -0
- package/dist/source/state.js +24 -0
- package/dist/source/symbol-resolver.d.ts +3 -0
- package/dist/source/symbol-resolver.js +212 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
- package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
- package/dist/source/validate-mixin/pipeline/parse.js +10 -0
- package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
- package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
- package/dist/source/validate-mixin/pipeline-context.js +93 -0
- package/dist/source/validate-mixin.d.ts +22 -0
- package/dist/source/validate-mixin.js +799 -0
- package/dist/source/workspace-target.d.ts +18 -0
- package/dist/source/workspace-target.js +305 -0
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +1 -1
- package/dist/source-service.d.ts +164 -170
- package/dist/source-service.js +70 -6116
- package/dist/stage-emitter.d.ts +13 -0
- package/dist/stage-emitter.js +30 -0
- package/dist/stdio-supervisor.d.ts +61 -0
- package/dist/stdio-supervisor.js +326 -9
- package/dist/tool-contract-manifest.d.ts +1 -1
- package/dist/tool-contract-manifest.js +23 -6
- package/dist/tool-guidance.d.ts +82 -0
- package/dist/tool-guidance.js +734 -0
- package/dist/tool-schema-registry.d.ts +16 -0
- package/dist/tool-schema-registry.js +37 -0
- package/dist/tool-schemas.d.ts +3518 -0
- package/dist/tool-schemas.js +813 -0
- package/dist/types.d.ts +36 -0
- package/dist/version-service.js +7 -6
- package/dist/workspace-context-cache.d.ts +32 -0
- package/dist/workspace-context-cache.js +66 -0
- package/dist/workspace-mapping-service.d.ts +16 -0
- package/dist/workspace-mapping-service.js +173 -1
- package/docs/README-ja.md +416 -0
- package/docs/examples.md +483 -0
- package/docs/tool-reference.md +462 -0
- package/package.json +17 -4
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, open, rename, stat, unlink } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { buildSuggestedCall } from "../build-suggested-call.js";
|
|
6
|
+
import { decompileBinaryJar } from "../decompiler/vineflower.js";
|
|
7
|
+
import { ERROR_CODES, createError, isAppError } from "../errors.js";
|
|
8
|
+
import { log } from "../logger.js";
|
|
9
|
+
import { resolveMojangTinyFile } from "../mojang-tiny-mapping-service.js";
|
|
10
|
+
import { iterateJavaEntriesAsUtf8 } from "../source-jar-reader.js";
|
|
11
|
+
import { extractSymbolsFromSource } from "../symbols/symbol-extractor.js";
|
|
12
|
+
import { remapJar } from "../tiny-remapper-service.js";
|
|
13
|
+
import { resolveTinyRemapperJar } from "../tiny-remapper-resolver.js";
|
|
14
|
+
import { resolveVineflowerJar } from "../vineflower-resolver.js";
|
|
15
|
+
import { enforceCacheLimits, recordRemappedJarBytes, releaseRemappedJarBytes, touchCacheMetrics, upsertCacheMetrics } from "./cache-metrics.js";
|
|
16
|
+
import { normalizePathStyle } from "./shared-utils.js";
|
|
17
|
+
export const INDEX_SCHEMA_VERSION = 1;
|
|
18
|
+
function chunkArray(items, chunkSize) {
|
|
19
|
+
const size = Math.max(1, Math.trunc(chunkSize));
|
|
20
|
+
if (items.length === 0) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for (let index = 0; index < items.length; index += size) {
|
|
25
|
+
chunks.push(items.slice(index, index + size));
|
|
26
|
+
}
|
|
27
|
+
return chunks;
|
|
28
|
+
}
|
|
29
|
+
export async function indexArtifact(svc, input) {
|
|
30
|
+
const artifactId = input.artifactId?.trim();
|
|
31
|
+
if (!artifactId) {
|
|
32
|
+
throw createError({
|
|
33
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
34
|
+
message: "artifactId must be non-empty."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const artifact = svc.getArtifact(artifactId);
|
|
38
|
+
const force = input.force ?? false;
|
|
39
|
+
const hasFiles = svc.filesRepo.listFiles(artifact.artifactId, { limit: 1 }).items.length > 0;
|
|
40
|
+
const meta = svc.indexMetaRepo.get(artifact.artifactId);
|
|
41
|
+
const expectedSignature = artifact.artifactSignature ?? fallbackArtifactSignature(artifact.artifactId);
|
|
42
|
+
const reason = resolveIndexRebuildReason({
|
|
43
|
+
force,
|
|
44
|
+
expectedSignature,
|
|
45
|
+
hasFiles,
|
|
46
|
+
meta
|
|
47
|
+
});
|
|
48
|
+
if (reason === "already_current") {
|
|
49
|
+
svc.metrics.recordReindexSkip();
|
|
50
|
+
const currentMeta = meta;
|
|
51
|
+
return {
|
|
52
|
+
artifactId: artifact.artifactId,
|
|
53
|
+
reindexed: false,
|
|
54
|
+
reason,
|
|
55
|
+
counts: {
|
|
56
|
+
files: currentMeta.filesCount,
|
|
57
|
+
symbols: currentMeta.symbolsCount,
|
|
58
|
+
ftsRows: currentMeta.ftsRowsCount
|
|
59
|
+
},
|
|
60
|
+
indexedAt: currentMeta.indexedAt,
|
|
61
|
+
durationMs: 0,
|
|
62
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const resolved = toResolvedArtifact(svc, artifact);
|
|
66
|
+
const rebuilt = await rebuildAndPersistArtifactIndex(svc, resolved, reason);
|
|
67
|
+
svc.metrics.recordReindex();
|
|
68
|
+
return {
|
|
69
|
+
artifactId: artifact.artifactId,
|
|
70
|
+
reindexed: true,
|
|
71
|
+
reason,
|
|
72
|
+
counts: {
|
|
73
|
+
files: rebuilt.files.length,
|
|
74
|
+
symbols: rebuilt.symbols.length,
|
|
75
|
+
ftsRows: rebuilt.files.length
|
|
76
|
+
},
|
|
77
|
+
indexedAt: rebuilt.indexedAt,
|
|
78
|
+
durationMs: rebuilt.indexDurationMs,
|
|
79
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function fallbackArtifactSignature(artifactId) {
|
|
83
|
+
return createHash("sha256").update(artifactId).digest("hex");
|
|
84
|
+
}
|
|
85
|
+
export function resolveIndexRebuildReason(input) {
|
|
86
|
+
if (input.force) {
|
|
87
|
+
return "force";
|
|
88
|
+
}
|
|
89
|
+
if (!input.hasFiles || !input.meta) {
|
|
90
|
+
return "missing_meta";
|
|
91
|
+
}
|
|
92
|
+
if (input.meta.indexSchemaVersion !== INDEX_SCHEMA_VERSION) {
|
|
93
|
+
return "schema_mismatch";
|
|
94
|
+
}
|
|
95
|
+
if (input.meta.artifactSignature !== input.expectedSignature) {
|
|
96
|
+
return "signature_mismatch";
|
|
97
|
+
}
|
|
98
|
+
return "already_current";
|
|
99
|
+
}
|
|
100
|
+
export function toResolvedArtifact(svc, artifact) {
|
|
101
|
+
return {
|
|
102
|
+
artifactId: artifact.artifactId,
|
|
103
|
+
artifactAlias: artifact.alias,
|
|
104
|
+
artifactSignature: artifact.artifactSignature ?? fallbackArtifactSignature(artifact.artifactId),
|
|
105
|
+
origin: artifact.origin,
|
|
106
|
+
binaryJarPath: artifact.binaryJarPath,
|
|
107
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
108
|
+
coordinate: artifact.coordinate,
|
|
109
|
+
version: artifact.version,
|
|
110
|
+
requestedMapping: artifact.requestedMapping,
|
|
111
|
+
mappingApplied: artifact.mappingApplied,
|
|
112
|
+
repoUrl: artifact.repoUrl,
|
|
113
|
+
provenance: artifact.provenance,
|
|
114
|
+
qualityFlags: artifact.qualityFlags,
|
|
115
|
+
isDecompiled: artifact.isDecompiled,
|
|
116
|
+
resolvedAt: new Date().toISOString()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export async function rebuildAndPersistArtifactIndex(svc, resolved, reason) {
|
|
120
|
+
const rebuilt = await buildRebuiltArtifactData(svc, resolved);
|
|
121
|
+
const timestamp = new Date().toISOString();
|
|
122
|
+
const chunkSize = Math.max(1, svc.config.indexInsertChunkSize ?? 200);
|
|
123
|
+
const tx = svc.db.transaction(() => {
|
|
124
|
+
svc.artifactsRepo.upsertArtifact({
|
|
125
|
+
artifactId: resolved.artifactId,
|
|
126
|
+
alias: resolved.artifactAlias,
|
|
127
|
+
origin: resolved.origin,
|
|
128
|
+
coordinate: resolved.coordinate,
|
|
129
|
+
version: resolved.version,
|
|
130
|
+
binaryJarPath: resolved.binaryJarPath,
|
|
131
|
+
sourceJarPath: resolved.sourceJarPath,
|
|
132
|
+
repoUrl: resolved.repoUrl,
|
|
133
|
+
requestedMapping: resolved.requestedMapping,
|
|
134
|
+
mappingApplied: resolved.mappingApplied,
|
|
135
|
+
provenance: resolved.provenance,
|
|
136
|
+
qualityFlags: resolved.qualityFlags,
|
|
137
|
+
artifactSignature: resolved.artifactSignature,
|
|
138
|
+
isDecompiled: resolved.isDecompiled,
|
|
139
|
+
timestamp
|
|
140
|
+
});
|
|
141
|
+
svc.filesRepo.clearFilesForArtifact(resolved.artifactId);
|
|
142
|
+
for (const chunk of chunkArray(rebuilt.files, chunkSize)) {
|
|
143
|
+
svc.filesRepo.insertFilesForArtifact(resolved.artifactId, chunk);
|
|
144
|
+
}
|
|
145
|
+
svc.symbolsRepo.clearSymbolsForArtifact(resolved.artifactId);
|
|
146
|
+
for (const chunk of chunkArray(rebuilt.symbols, chunkSize)) {
|
|
147
|
+
svc.symbolsRepo.insertSymbolsForArtifact(resolved.artifactId, chunk);
|
|
148
|
+
}
|
|
149
|
+
svc.indexMetaRepo.upsert({
|
|
150
|
+
artifactId: resolved.artifactId,
|
|
151
|
+
artifactSignature: resolved.artifactSignature,
|
|
152
|
+
indexSchemaVersion: INDEX_SCHEMA_VERSION,
|
|
153
|
+
filesCount: rebuilt.files.length,
|
|
154
|
+
symbolsCount: rebuilt.symbols.length,
|
|
155
|
+
ftsRowsCount: rebuilt.files.length,
|
|
156
|
+
indexedAt: rebuilt.indexedAt,
|
|
157
|
+
indexDurationMs: rebuilt.indexDurationMs
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
tx();
|
|
161
|
+
upsertCacheMetrics(svc, resolved.artifactId, rebuilt.totalContentBytes, timestamp);
|
|
162
|
+
log("info", "index.rebuild.done", {
|
|
163
|
+
artifactId: resolved.artifactId,
|
|
164
|
+
reason,
|
|
165
|
+
files: rebuilt.files.length,
|
|
166
|
+
symbols: rebuilt.symbols.length,
|
|
167
|
+
indexDurationMs: rebuilt.indexDurationMs
|
|
168
|
+
});
|
|
169
|
+
return rebuilt;
|
|
170
|
+
}
|
|
171
|
+
export async function buildRebuiltArtifactData(svc, resolved) {
|
|
172
|
+
const indexStartedAt = Date.now();
|
|
173
|
+
let files = [];
|
|
174
|
+
if (resolved.sourceJarPath) {
|
|
175
|
+
files = await loadFromSourceJar(svc, resolved.sourceJarPath);
|
|
176
|
+
}
|
|
177
|
+
else if (resolved.binaryJarPath) {
|
|
178
|
+
const decompileInputJarPath = await maybeRemapBinaryForMojang(svc, resolved);
|
|
179
|
+
// When the binary jar was remapped from obfuscated to mojang, swap the resolved
|
|
180
|
+
// artifact's binaryJarPath to the remapped jar so downstream bytecode consumers
|
|
181
|
+
// (getClassMembers, validateMixin) look up mojang names in the mojang jar — not
|
|
182
|
+
// the original obfuscated jar. Persistence in upsertArtifact happens after this
|
|
183
|
+
// function returns, so the swap reaches both the database row and the
|
|
184
|
+
// resolveArtifact response.
|
|
185
|
+
if (decompileInputJarPath !== resolved.binaryJarPath) {
|
|
186
|
+
resolved.binaryJarPath = decompileInputJarPath;
|
|
187
|
+
}
|
|
188
|
+
const vineflowerPath = await resolveVineflowerJar(svc.config.cacheDir, svc.config.vineflowerJarPath);
|
|
189
|
+
const decompileStartedAt = Date.now();
|
|
190
|
+
try {
|
|
191
|
+
const decompileResult = await decompileBinaryJar(decompileInputJarPath, svc.config.cacheDir, {
|
|
192
|
+
vineflowerJarPath: vineflowerPath,
|
|
193
|
+
artifactIdCandidate: resolved.artifactId,
|
|
194
|
+
timeoutMs: 120_000,
|
|
195
|
+
signature: resolved.artifactId
|
|
196
|
+
});
|
|
197
|
+
files = decompileResult.javaFiles.map((entry) => ({
|
|
198
|
+
filePath: normalizePathStyle(entry.filePath),
|
|
199
|
+
content: entry.content,
|
|
200
|
+
contentBytes: Buffer.byteLength(entry.content, "utf8"),
|
|
201
|
+
contentHash: createHash("sha256").update(entry.content).digest("hex")
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
catch (caughtError) {
|
|
205
|
+
if (isAppError(caughtError) && caughtError.code === ERROR_CODES.DECOMPILER_FAILED) {
|
|
206
|
+
throw createError({
|
|
207
|
+
code: ERROR_CODES.DECOMPILER_FAILED,
|
|
208
|
+
message: caughtError.message,
|
|
209
|
+
details: {
|
|
210
|
+
...(caughtError.details ?? {}),
|
|
211
|
+
artifactId: resolved.artifactId,
|
|
212
|
+
binaryJarPath: resolved.binaryJarPath,
|
|
213
|
+
producedJavaCount: typeof caughtError.details?.producedJavaCount === "number"
|
|
214
|
+
? caughtError.details.producedJavaCount
|
|
215
|
+
: 0,
|
|
216
|
+
nextAction: "Verify Java runtime and Vineflower availability, then retry. If available, prefer source-backed artifacts.",
|
|
217
|
+
recommendedCommand: "echo $MCP_VINEFLOWER_JAR_PATH"
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
throw caughtError;
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
svc.metrics.recordDuration("decompile_duration_ms", Date.now() - decompileStartedAt);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
throw createError({
|
|
229
|
+
code: ERROR_CODES.SOURCE_NOT_FOUND,
|
|
230
|
+
message: "No source artifact available.",
|
|
231
|
+
details: {
|
|
232
|
+
artifactId: resolved.artifactId,
|
|
233
|
+
nextAction: "Use list-artifact-files to inspect the artifact's contents.",
|
|
234
|
+
...buildSuggestedCall({
|
|
235
|
+
tool: "list-artifact-files",
|
|
236
|
+
params: { artifactId: resolved.artifactId }
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const symbols = [];
|
|
242
|
+
for (const file of files) {
|
|
243
|
+
const extracted = extractSymbolsFromSource(file.filePath, file.content);
|
|
244
|
+
for (const symbol of extracted) {
|
|
245
|
+
symbols.push({
|
|
246
|
+
filePath: file.filePath,
|
|
247
|
+
...symbol
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
files,
|
|
253
|
+
symbols,
|
|
254
|
+
indexedAt: new Date().toISOString(),
|
|
255
|
+
indexDurationMs: Date.now() - indexStartedAt,
|
|
256
|
+
totalContentBytes: files.reduce((sum, file) => sum + file.contentBytes, 0)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export function getArtifact(svc, artifactId) {
|
|
260
|
+
if (artifactId.includes("..") || artifactId.includes("/")) {
|
|
261
|
+
// intentionally reject suspicious IDs that are not artifact hashes
|
|
262
|
+
throw createError({
|
|
263
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
264
|
+
message: "artifactId contains invalid characters.",
|
|
265
|
+
details: { artifactId }
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const artifact = svc.artifactsRepo.getArtifact(artifactId);
|
|
269
|
+
if (!artifact) {
|
|
270
|
+
throw createError({
|
|
271
|
+
code: ERROR_CODES.SOURCE_NOT_FOUND,
|
|
272
|
+
message: "Artifact not found. Resolve context first.",
|
|
273
|
+
details: {
|
|
274
|
+
artifactId,
|
|
275
|
+
nextAction: "Use resolve-artifact to resolve a source artifact first.",
|
|
276
|
+
...buildSuggestedCall({
|
|
277
|
+
tool: "resolve-artifact",
|
|
278
|
+
params: { target: { kind: "version", value: "latest" } }
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return artifact;
|
|
284
|
+
}
|
|
285
|
+
export async function ingestIfNeeded(svc, resolved) {
|
|
286
|
+
const existing = svc.artifactsRepo.getArtifact(resolved.artifactId);
|
|
287
|
+
const hasFiles = svc.filesRepo.listFiles(resolved.artifactId, { limit: 1 }).items.length > 0;
|
|
288
|
+
const meta = svc.indexMetaRepo.get(resolved.artifactId);
|
|
289
|
+
const reason = resolveIndexRebuildReason({
|
|
290
|
+
force: false,
|
|
291
|
+
expectedSignature: resolved.artifactSignature,
|
|
292
|
+
hasFiles,
|
|
293
|
+
meta
|
|
294
|
+
});
|
|
295
|
+
if (existing && reason === "already_current") {
|
|
296
|
+
// Mojang binary-remap reconciliation on the warm cache hit path:
|
|
297
|
+
// resolveSourceTargetInternal always returns the original binary jar
|
|
298
|
+
// (resolver does not know about prior remap output), so without this
|
|
299
|
+
// step a warm-cache resolve would return mappingApplied="mojang"
|
|
300
|
+
// alongside binaryJarPath pointing at the obfuscated client jar.
|
|
301
|
+
// maybeRemapBinaryForMojang short-circuits on a healthy cache hit
|
|
302
|
+
// (existsSync + ZIP magic) and re-remaps when the cache is missing
|
|
303
|
+
// or corrupted, so this also recovers from out-of-band cache loss.
|
|
304
|
+
const transformChain = resolved.provenance?.transformChain ?? [];
|
|
305
|
+
if (transformChain.includes("binary-remap:obf->mojang") && resolved.binaryJarPath) {
|
|
306
|
+
const reconciledBinaryJarPath = await maybeRemapBinaryForMojang(svc, resolved);
|
|
307
|
+
if (reconciledBinaryJarPath !== resolved.binaryJarPath) {
|
|
308
|
+
resolved.binaryJarPath = reconciledBinaryJarPath;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Backfill / rotate alias on the warm-cache path. Without this, schema-v4
|
|
312
|
+
// migrated rows (alias=NULL) and rows whose alias parameters changed since
|
|
313
|
+
// the last upsert would return an artifactAlias from resolveArtifact that
|
|
314
|
+
// does not resolve back via getArtifact(alias), breaking the 3.1b lookup
|
|
315
|
+
// contract. UNIQUE conflicts here are caller bugs (two distinct artifactIds
|
|
316
|
+
// colliding on alias) and surface as DB errors rather than silent drift.
|
|
317
|
+
if (resolved.artifactAlias && existing.alias !== resolved.artifactAlias) {
|
|
318
|
+
svc.artifactsRepo.setAlias(resolved.artifactId, resolved.artifactAlias);
|
|
319
|
+
}
|
|
320
|
+
svc.metrics.recordArtifactCacheHit();
|
|
321
|
+
const touchedAt = new Date().toISOString();
|
|
322
|
+
svc.artifactsRepo.touchArtifact(resolved.artifactId, touchedAt);
|
|
323
|
+
touchCacheMetrics(svc, resolved.artifactId, touchedAt);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const inflight = svc.state.inflightArtifactIngests.get(resolved.artifactId);
|
|
327
|
+
if (inflight) {
|
|
328
|
+
await inflight;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const ingestPromise = rebuildMissingArtifactIndex(svc, resolved, reason);
|
|
332
|
+
svc.state.inflightArtifactIngests.set(resolved.artifactId, ingestPromise);
|
|
333
|
+
try {
|
|
334
|
+
await ingestPromise;
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
if (svc.state.inflightArtifactIngests.get(resolved.artifactId) === ingestPromise) {
|
|
338
|
+
svc.state.inflightArtifactIngests.delete(resolved.artifactId);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async function rebuildMissingArtifactIndex(svc, resolved, reason) {
|
|
343
|
+
svc.metrics.recordArtifactCacheMiss();
|
|
344
|
+
svc.metrics.recordReindex();
|
|
345
|
+
log("info", "index.rebuild.start", {
|
|
346
|
+
artifactId: resolved.artifactId,
|
|
347
|
+
reason
|
|
348
|
+
});
|
|
349
|
+
await rebuildAndPersistArtifactIndex(svc, resolved, reason === "already_current" ? "missing_meta" : reason);
|
|
350
|
+
enforceCacheLimits(svc);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* If the resolved artifact's transformChain promised an "obf -> mojang"
|
|
354
|
+
* binary remap, run tiny-remapper now and return the remapped jar path.
|
|
355
|
+
* Otherwise return the original binaryJarPath unchanged.
|
|
356
|
+
*
|
|
357
|
+
* Cache safety: writes to a per-attempt temp file then atomic-renames into
|
|
358
|
+
* <cacheDir>/remapped/<artifactId>.jar. A per-target inflight Promise map
|
|
359
|
+
* collapses concurrent calls so two simultaneous resolveArtifact calls for
|
|
360
|
+
* the same artifactId share one tiny-remapper run instead of racing on the
|
|
361
|
+
* same output path.
|
|
362
|
+
*/
|
|
363
|
+
export async function maybeRemapBinaryForMojang(svc, resolved) {
|
|
364
|
+
const binaryJarPath = resolved.binaryJarPath;
|
|
365
|
+
if (!binaryJarPath) {
|
|
366
|
+
throw createError({
|
|
367
|
+
code: ERROR_CODES.SOURCE_NOT_FOUND,
|
|
368
|
+
message: "Cannot run binary remap: resolved artifact has no binary jar path.",
|
|
369
|
+
details: { artifactId: resolved.artifactId }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const transformChain = resolved.provenance?.transformChain ?? [];
|
|
373
|
+
if (!transformChain.includes("binary-remap:obf->mojang")) {
|
|
374
|
+
return binaryJarPath;
|
|
375
|
+
}
|
|
376
|
+
if (!resolved.version) {
|
|
377
|
+
throw createError({
|
|
378
|
+
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
379
|
+
message: "Binary remap promised but artifact has no resolved Minecraft version.",
|
|
380
|
+
details: {
|
|
381
|
+
artifactId: resolved.artifactId,
|
|
382
|
+
binaryJarPath,
|
|
383
|
+
nextAction: "Use target.kind=\"version\" so the remap pipeline can locate Mojang mappings."
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
const remappedDir = join(svc.config.cacheDir, "remapped");
|
|
388
|
+
const remappedJarPath = join(remappedDir, `${resolved.artifactId}.jar`);
|
|
389
|
+
if (existsSync(remappedJarPath)) {
|
|
390
|
+
// Validate the cached jar is at least structurally a ZIP (`PK\x03\x04`) and
|
|
391
|
+
// non-empty before reusing. If a prior atomic-rename window was interrupted
|
|
392
|
+
// or the cache file was hand-edited, drop it and re-remap rather than
|
|
393
|
+
// silently feeding a corrupt jar into Vineflower.
|
|
394
|
+
if (await isUsableJarFile(remappedJarPath)) {
|
|
395
|
+
await recordRemappedJarBytesFromDisk(svc, resolved.artifactId, remappedJarPath);
|
|
396
|
+
return remappedJarPath;
|
|
397
|
+
}
|
|
398
|
+
log("warn", "binary-remap.cache.evict-corrupt", {
|
|
399
|
+
artifactId: resolved.artifactId,
|
|
400
|
+
remappedJarPath
|
|
401
|
+
});
|
|
402
|
+
try {
|
|
403
|
+
await unlink(remappedJarPath);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// ignore: race with another process or already-deleted file.
|
|
407
|
+
}
|
|
408
|
+
releaseRemappedJarBytes(svc, resolved.artifactId);
|
|
409
|
+
}
|
|
410
|
+
const inflight = svc.state.inflightRemaps.get(remappedJarPath);
|
|
411
|
+
if (inflight) {
|
|
412
|
+
return inflight;
|
|
413
|
+
}
|
|
414
|
+
const remapPromise = runBinaryRemap(svc, {
|
|
415
|
+
version: resolved.version,
|
|
416
|
+
inputJar: binaryJarPath,
|
|
417
|
+
remappedDir,
|
|
418
|
+
remappedJarPath
|
|
419
|
+
});
|
|
420
|
+
svc.state.inflightRemaps.set(remappedJarPath, remapPromise);
|
|
421
|
+
try {
|
|
422
|
+
const path = await remapPromise;
|
|
423
|
+
await recordRemappedJarBytesFromDisk(svc, resolved.artifactId, path);
|
|
424
|
+
return path;
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
svc.state.inflightRemaps.delete(remappedJarPath);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
export async function recordRemappedJarBytesFromDisk(svc, artifactId, path) {
|
|
431
|
+
try {
|
|
432
|
+
const fileStat = await stat(path);
|
|
433
|
+
recordRemappedJarBytes(svc, artifactId, fileStat.size);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// best-effort: accounting will be rebuilt on the next refreshCacheMetrics.
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Best-effort structural check that `path` is a non-empty file beginning with
|
|
441
|
+
* the ZIP local-file-header magic (`50 4B 03 04`). Used to drop partial /
|
|
442
|
+
* corrupt remap-cache entries before they reach Vineflower. False positives
|
|
443
|
+
* are acceptable (Vineflower will surface a clearer error); false negatives
|
|
444
|
+
* are not (a corrupt cache hit must be evicted).
|
|
445
|
+
*/
|
|
446
|
+
export async function isUsableJarFile(path) {
|
|
447
|
+
try {
|
|
448
|
+
const stats = await stat(path);
|
|
449
|
+
if (!stats.isFile() || stats.size < 4) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
let handle;
|
|
457
|
+
try {
|
|
458
|
+
handle = await open(path, "r");
|
|
459
|
+
const header = Buffer.alloc(4);
|
|
460
|
+
const { bytesRead } = await handle.read(header, 0, 4, 0);
|
|
461
|
+
return bytesRead === 4 && header[0] === 0x50 && header[1] === 0x4b && header[2] === 0x03 && header[3] === 0x04;
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
finally {
|
|
467
|
+
await handle?.close().catch(() => undefined);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
export async function runBinaryRemap(svc, input) {
|
|
471
|
+
const tinyRemapperJarPath = await resolveTinyRemapperJar(svc.config.cacheDir, svc.config.tinyRemapperJarPath);
|
|
472
|
+
const mojangTiny = await resolveMojangTinyFile(input.version, svc.config);
|
|
473
|
+
await mkdir(input.remappedDir, { recursive: true });
|
|
474
|
+
const tempPath = `${input.remappedJarPath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
475
|
+
const remapStartedAt = Date.now();
|
|
476
|
+
try {
|
|
477
|
+
await remapJar(tinyRemapperJarPath, {
|
|
478
|
+
inputJar: input.inputJar,
|
|
479
|
+
outputJar: tempPath,
|
|
480
|
+
mappingsFile: mojangTiny.path,
|
|
481
|
+
fromNamespace: "obfuscated",
|
|
482
|
+
toNamespace: "mojang",
|
|
483
|
+
timeoutMs: svc.config.remapTimeoutMs,
|
|
484
|
+
maxMemoryMb: svc.config.remapMaxMemoryMb
|
|
485
|
+
});
|
|
486
|
+
const tempStats = await stat(tempPath);
|
|
487
|
+
if (tempStats.size === 0) {
|
|
488
|
+
throw createError({
|
|
489
|
+
code: ERROR_CODES.REMAP_FAILED,
|
|
490
|
+
message: "tiny-remapper produced an empty output jar.",
|
|
491
|
+
details: { inputJar: input.inputJar, tempPath }
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
await rename(tempPath, input.remappedJarPath);
|
|
495
|
+
return input.remappedJarPath;
|
|
496
|
+
}
|
|
497
|
+
catch (caughtError) {
|
|
498
|
+
try {
|
|
499
|
+
await unlink(tempPath);
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
// tempPath may not exist if remapJar failed before writing anything; ignore.
|
|
503
|
+
}
|
|
504
|
+
throw caughtError;
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
svc.metrics.recordDuration("binary_remap_duration_ms", Date.now() - remapStartedAt);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
export async function loadFromSourceJar(svc, sourceJarPath) {
|
|
511
|
+
const files = [];
|
|
512
|
+
for await (const entry of iterateJavaEntriesAsUtf8(sourceJarPath, svc.config.maxContentBytes)) {
|
|
513
|
+
files.push({
|
|
514
|
+
filePath: normalizePathStyle(entry.filePath),
|
|
515
|
+
content: entry.content,
|
|
516
|
+
contentBytes: Buffer.byteLength(entry.content, "utf8"),
|
|
517
|
+
contentHash: createHash("sha256").update(entry.content).digest("hex")
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return files;
|
|
521
|
+
}
|
|
522
|
+
//# sourceMappingURL=indexer.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DiffClassMemberDelta, DiffMember, DiffMemberChange } from "../../source-service.js";
|
|
2
|
+
type DiffMemberChangedField = "accessFlags" | "isSynthetic" | "javaSignature" | "jvmDescriptor";
|
|
3
|
+
export declare function sortDiffMembers(members: DiffMember[]): DiffMember[];
|
|
4
|
+
export declare function sortDiffMemberChanges(changes: DiffMemberChange[]): DiffMemberChange[];
|
|
5
|
+
export declare function changedMemberFields(fromMember: DiffMember, toMember: DiffMember, includeDescriptor: boolean): DiffMemberChangedField[];
|
|
6
|
+
export declare function diffMembersByKey(fromMembersInput: DiffMember[], toMembersInput: DiffMember[], buildKey: (member: DiffMember) => string, includeDescriptorInModified: boolean): DiffClassMemberDelta;
|
|
7
|
+
export declare function emptyDiffDelta(): DiffClassMemberDelta;
|
|
8
|
+
export declare function compactDiffDelta(delta: DiffClassMemberDelta): DiffClassMemberDelta;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export function sortDiffMembers(members) {
|
|
2
|
+
return [...members].sort((left, right) => {
|
|
3
|
+
const nameCompare = left.name.localeCompare(right.name);
|
|
4
|
+
if (nameCompare !== 0) {
|
|
5
|
+
return nameCompare;
|
|
6
|
+
}
|
|
7
|
+
const descriptorCompare = left.jvmDescriptor.localeCompare(right.jvmDescriptor);
|
|
8
|
+
if (descriptorCompare !== 0) {
|
|
9
|
+
return descriptorCompare;
|
|
10
|
+
}
|
|
11
|
+
return left.ownerFqn.localeCompare(right.ownerFqn);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function sortDiffMemberChanges(changes) {
|
|
15
|
+
return [...changes].sort((left, right) => {
|
|
16
|
+
const keyCompare = left.key.localeCompare(right.key);
|
|
17
|
+
if (keyCompare !== 0) {
|
|
18
|
+
return keyCompare;
|
|
19
|
+
}
|
|
20
|
+
const fromOwnerCompare = (left.from?.ownerFqn ?? "").localeCompare(right.from?.ownerFqn ?? "");
|
|
21
|
+
if (fromOwnerCompare !== 0) {
|
|
22
|
+
return fromOwnerCompare;
|
|
23
|
+
}
|
|
24
|
+
return (left.to?.ownerFqn ?? "").localeCompare(right.to?.ownerFqn ?? "");
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function changedMemberFields(fromMember, toMember, includeDescriptor) {
|
|
28
|
+
const changed = [];
|
|
29
|
+
if (fromMember.accessFlags !== toMember.accessFlags) {
|
|
30
|
+
changed.push("accessFlags");
|
|
31
|
+
}
|
|
32
|
+
if (fromMember.isSynthetic !== toMember.isSynthetic) {
|
|
33
|
+
changed.push("isSynthetic");
|
|
34
|
+
}
|
|
35
|
+
if (fromMember.javaSignature !== toMember.javaSignature) {
|
|
36
|
+
changed.push("javaSignature");
|
|
37
|
+
}
|
|
38
|
+
if (includeDescriptor && fromMember.jvmDescriptor !== toMember.jvmDescriptor) {
|
|
39
|
+
changed.push("jvmDescriptor");
|
|
40
|
+
}
|
|
41
|
+
return changed;
|
|
42
|
+
}
|
|
43
|
+
export function diffMembersByKey(fromMembersInput, toMembersInput, buildKey, includeDescriptorInModified) {
|
|
44
|
+
const fromMembers = sortDiffMembers(fromMembersInput);
|
|
45
|
+
const toMembers = sortDiffMembers(toMembersInput);
|
|
46
|
+
const fromByKey = new Map();
|
|
47
|
+
const toByKey = new Map();
|
|
48
|
+
for (const member of fromMembers) {
|
|
49
|
+
const key = buildKey(member);
|
|
50
|
+
if (!fromByKey.has(key)) {
|
|
51
|
+
fromByKey.set(key, member);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const member of toMembers) {
|
|
55
|
+
const key = buildKey(member);
|
|
56
|
+
if (!toByKey.has(key)) {
|
|
57
|
+
toByKey.set(key, member);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const added = [];
|
|
61
|
+
const removed = [];
|
|
62
|
+
const modified = [];
|
|
63
|
+
for (const [key, toMember] of toByKey.entries()) {
|
|
64
|
+
const fromMember = fromByKey.get(key);
|
|
65
|
+
if (!fromMember) {
|
|
66
|
+
added.push(toMember);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const changed = changedMemberFields(fromMember, toMember, includeDescriptorInModified);
|
|
70
|
+
if (changed.length > 0) {
|
|
71
|
+
modified.push({
|
|
72
|
+
key,
|
|
73
|
+
from: fromMember,
|
|
74
|
+
to: toMember,
|
|
75
|
+
changed
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const [key, fromMember] of fromByKey.entries()) {
|
|
80
|
+
if (!toByKey.has(key)) {
|
|
81
|
+
removed.push(fromMember);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
added: sortDiffMembers(added),
|
|
86
|
+
removed: sortDiffMembers(removed),
|
|
87
|
+
modified: sortDiffMemberChanges(modified)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function emptyDiffDelta() {
|
|
91
|
+
return {
|
|
92
|
+
added: [],
|
|
93
|
+
removed: [],
|
|
94
|
+
modified: []
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function compactDiffDelta(delta) {
|
|
98
|
+
return {
|
|
99
|
+
added: delta.added,
|
|
100
|
+
removed: delta.removed,
|
|
101
|
+
modified: delta.modified.map((change) => ({
|
|
102
|
+
key: change.key,
|
|
103
|
+
changed: [...change.changed]
|
|
104
|
+
}))
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=diff-utils.js.map
|