@creeperhost/modlens-mcp 1.6.0

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.
Files changed (382) hide show
  1. package/README.md +938 -0
  2. package/dist/access-flags.d.ts +58 -0
  3. package/dist/access-flags.d.ts.map +1 -0
  4. package/dist/access-flags.js +79 -0
  5. package/dist/access-flags.js.map +1 -0
  6. package/dist/access-flags.test.d.ts +2 -0
  7. package/dist/access-flags.test.d.ts.map +1 -0
  8. package/dist/access-flags.test.js +189 -0
  9. package/dist/access-flags.test.js.map +1 -0
  10. package/dist/batch-ingest.d.ts +2 -0
  11. package/dist/batch-ingest.d.ts.map +1 -0
  12. package/dist/batch-ingest.js +54 -0
  13. package/dist/batch-ingest.js.map +1 -0
  14. package/dist/batch-resolve-mixins.d.ts +2 -0
  15. package/dist/batch-resolve-mixins.d.ts.map +1 -0
  16. package/dist/batch-resolve-mixins.js +36 -0
  17. package/dist/batch-resolve-mixins.js.map +1 -0
  18. package/dist/cache.d.ts +16 -0
  19. package/dist/cache.d.ts.map +1 -0
  20. package/dist/cache.js +36 -0
  21. package/dist/cache.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.d.ts.map +1 -0
  24. package/dist/cli.js +1318 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/curseforge.d.ts +53 -0
  27. package/dist/curseforge.d.ts.map +1 -0
  28. package/dist/curseforge.js +104 -0
  29. package/dist/curseforge.js.map +1 -0
  30. package/dist/db-backend.d.ts +17 -0
  31. package/dist/db-backend.d.ts.map +1 -0
  32. package/dist/db-backend.js +40 -0
  33. package/dist/db-backend.js.map +1 -0
  34. package/dist/db.d.ts +11 -0
  35. package/dist/db.d.ts.map +1 -0
  36. package/dist/db.js +55 -0
  37. package/dist/db.js.map +1 -0
  38. package/dist/embed-queue.d.ts +12 -0
  39. package/dist/embed-queue.d.ts.map +1 -0
  40. package/dist/embed-queue.js +103 -0
  41. package/dist/embed-queue.js.map +1 -0
  42. package/dist/embeddings.d.ts +21 -0
  43. package/dist/embeddings.d.ts.map +1 -0
  44. package/dist/embeddings.js +55 -0
  45. package/dist/embeddings.js.map +1 -0
  46. package/dist/env-path.d.ts +19 -0
  47. package/dist/env-path.d.ts.map +1 -0
  48. package/dist/env-path.js +40 -0
  49. package/dist/env-path.js.map +1 -0
  50. package/dist/fetch-utils.d.ts +24 -0
  51. package/dist/fetch-utils.d.ts.map +1 -0
  52. package/dist/fetch-utils.js +73 -0
  53. package/dist/fetch-utils.js.map +1 -0
  54. package/dist/fetch-utils.test.d.ts +2 -0
  55. package/dist/fetch-utils.test.d.ts.map +1 -0
  56. package/dist/fetch-utils.test.js +133 -0
  57. package/dist/fetch-utils.test.js.map +1 -0
  58. package/dist/generated/sqlite/browser.d.ts +55 -0
  59. package/dist/generated/sqlite/browser.d.ts.map +1 -0
  60. package/dist/generated/sqlite/browser.js +18 -0
  61. package/dist/generated/sqlite/browser.js.map +1 -0
  62. package/dist/generated/sqlite/client.d.ts +72 -0
  63. package/dist/generated/sqlite/client.d.ts.map +1 -0
  64. package/dist/generated/sqlite/client.js +33 -0
  65. package/dist/generated/sqlite/client.js.map +1 -0
  66. package/dist/generated/sqlite/commonInputTypes.d.ts +387 -0
  67. package/dist/generated/sqlite/commonInputTypes.d.ts.map +1 -0
  68. package/dist/generated/sqlite/commonInputTypes.js +11 -0
  69. package/dist/generated/sqlite/commonInputTypes.js.map +1 -0
  70. package/dist/generated/sqlite/enums.d.ts +2 -0
  71. package/dist/generated/sqlite/enums.d.ts.map +1 -0
  72. package/dist/generated/sqlite/enums.js +11 -0
  73. package/dist/generated/sqlite/enums.js.map +1 -0
  74. package/dist/generated/sqlite/internal/class.d.ts +226 -0
  75. package/dist/generated/sqlite/internal/class.d.ts.map +1 -0
  76. package/dist/generated/sqlite/internal/class.js +84 -0
  77. package/dist/generated/sqlite/internal/class.js.map +1 -0
  78. package/dist/generated/sqlite/internal/prismaNamespace.d.ts +1344 -0
  79. package/dist/generated/sqlite/internal/prismaNamespace.d.ts.map +1 -0
  80. package/dist/generated/sqlite/internal/prismaNamespace.js +220 -0
  81. package/dist/generated/sqlite/internal/prismaNamespace.js.map +1 -0
  82. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts +204 -0
  83. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  84. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js +191 -0
  85. package/dist/generated/sqlite/internal/prismaNamespaceBrowser.js.map +1 -0
  86. package/dist/generated/sqlite/models/DocEntry.d.ts +1223 -0
  87. package/dist/generated/sqlite/models/DocEntry.d.ts.map +1 -0
  88. package/dist/generated/sqlite/models/DocEntry.js +2 -0
  89. package/dist/generated/sqlite/models/DocEntry.js.map +1 -0
  90. package/dist/generated/sqlite/models/McSourceFile.d.ts +1289 -0
  91. package/dist/generated/sqlite/models/McSourceFile.d.ts.map +1 -0
  92. package/dist/generated/sqlite/models/McSourceFile.js +2 -0
  93. package/dist/generated/sqlite/models/McSourceFile.js.map +1 -0
  94. package/dist/generated/sqlite/models/McVersion.d.ts +1315 -0
  95. package/dist/generated/sqlite/models/McVersion.d.ts.map +1 -0
  96. package/dist/generated/sqlite/models/McVersion.js +2 -0
  97. package/dist/generated/sqlite/models/McVersion.js.map +1 -0
  98. package/dist/generated/sqlite/models/McVersionDiff.d.ts +1061 -0
  99. package/dist/generated/sqlite/models/McVersionDiff.d.ts.map +1 -0
  100. package/dist/generated/sqlite/models/McVersionDiff.js +2 -0
  101. package/dist/generated/sqlite/models/McVersionDiff.js.map +1 -0
  102. package/dist/generated/sqlite/models/Mod.d.ts +2396 -0
  103. package/dist/generated/sqlite/models/Mod.d.ts.map +1 -0
  104. package/dist/generated/sqlite/models/Mod.js +2 -0
  105. package/dist/generated/sqlite/models/Mod.js.map +1 -0
  106. package/dist/generated/sqlite/models/ModClass.d.ts +1254 -0
  107. package/dist/generated/sqlite/models/ModClass.d.ts.map +1 -0
  108. package/dist/generated/sqlite/models/ModClass.js +2 -0
  109. package/dist/generated/sqlite/models/ModClass.js.map +1 -0
  110. package/dist/generated/sqlite/models/ModSourceFile.d.ts +1283 -0
  111. package/dist/generated/sqlite/models/ModSourceFile.d.ts.map +1 -0
  112. package/dist/generated/sqlite/models/ModSourceFile.js +2 -0
  113. package/dist/generated/sqlite/models/ModSourceFile.js.map +1 -0
  114. package/dist/generated/sqlite/models/ModTag.d.ts +1278 -0
  115. package/dist/generated/sqlite/models/ModTag.d.ts.map +1 -0
  116. package/dist/generated/sqlite/models/ModTag.js +2 -0
  117. package/dist/generated/sqlite/models/ModTag.js.map +1 -0
  118. package/dist/generated/sqlite/models/ModVersionDiff.d.ts +1073 -0
  119. package/dist/generated/sqlite/models/ModVersionDiff.d.ts.map +1 -0
  120. package/dist/generated/sqlite/models/ModVersionDiff.js +2 -0
  121. package/dist/generated/sqlite/models/ModVersionDiff.js.map +1 -0
  122. package/dist/generated/sqlite/models/Primer.d.ts +1319 -0
  123. package/dist/generated/sqlite/models/Primer.d.ts.map +1 -0
  124. package/dist/generated/sqlite/models/Primer.js +2 -0
  125. package/dist/generated/sqlite/models/Primer.js.map +1 -0
  126. package/dist/generated/sqlite/models.d.ts +12 -0
  127. package/dist/generated/sqlite/models.d.ts.map +1 -0
  128. package/dist/generated/sqlite/models.js +2 -0
  129. package/dist/generated/sqlite/models.js.map +1 -0
  130. package/dist/jar.d.ts +7 -0
  131. package/dist/jar.d.ts.map +1 -0
  132. package/dist/jar.js +52 -0
  133. package/dist/jar.js.map +1 -0
  134. package/dist/java-tools.d.ts +46 -0
  135. package/dist/java-tools.d.ts.map +1 -0
  136. package/dist/java-tools.js +263 -0
  137. package/dist/java-tools.js.map +1 -0
  138. package/dist/launcher.d.ts +3 -0
  139. package/dist/launcher.d.ts.map +1 -0
  140. package/dist/launcher.js +105 -0
  141. package/dist/launcher.js.map +1 -0
  142. package/dist/mappings.d.ts +95 -0
  143. package/dist/mappings.d.ts.map +1 -0
  144. package/dist/mappings.js +1094 -0
  145. package/dist/mappings.js.map +1 -0
  146. package/dist/mappings.test.d.ts +2 -0
  147. package/dist/mappings.test.d.ts.map +1 -0
  148. package/dist/mappings.test.js +161 -0
  149. package/dist/mappings.test.js.map +1 -0
  150. package/dist/minecraft.d.ts +21 -0
  151. package/dist/minecraft.d.ts.map +1 -0
  152. package/dist/minecraft.js +70 -0
  153. package/dist/minecraft.js.map +1 -0
  154. package/dist/modpacks-ch.d.ts +224 -0
  155. package/dist/modpacks-ch.d.ts.map +1 -0
  156. package/dist/modpacks-ch.js +146 -0
  157. package/dist/modpacks-ch.js.map +1 -0
  158. package/dist/modrinth.d.ts +67 -0
  159. package/dist/modrinth.d.ts.map +1 -0
  160. package/dist/modrinth.js +88 -0
  161. package/dist/modrinth.js.map +1 -0
  162. package/dist/platform-adapter.d.ts +23 -0
  163. package/dist/platform-adapter.d.ts.map +1 -0
  164. package/dist/platform-adapter.js +2 -0
  165. package/dist/platform-adapter.js.map +1 -0
  166. package/dist/platform.d.ts +47 -0
  167. package/dist/platform.d.ts.map +1 -0
  168. package/dist/platform.js +175 -0
  169. package/dist/platform.js.map +1 -0
  170. package/dist/processor.d.ts +51 -0
  171. package/dist/processor.d.ts.map +1 -0
  172. package/dist/processor.js +655 -0
  173. package/dist/processor.js.map +1 -0
  174. package/dist/processor.test.d.ts +2 -0
  175. package/dist/processor.test.d.ts.map +1 -0
  176. package/dist/processor.test.js +629 -0
  177. package/dist/processor.test.js.map +1 -0
  178. package/dist/repositories/embeddings-sqlite.d.ts +38 -0
  179. package/dist/repositories/embeddings-sqlite.d.ts.map +1 -0
  180. package/dist/repositories/embeddings-sqlite.js +208 -0
  181. package/dist/repositories/embeddings-sqlite.js.map +1 -0
  182. package/dist/repositories/embeddings.d.ts +40 -0
  183. package/dist/repositories/embeddings.d.ts.map +1 -0
  184. package/dist/repositories/embeddings.js +136 -0
  185. package/dist/repositories/embeddings.js.map +1 -0
  186. package/dist/repositories/index.d.ts +2 -0
  187. package/dist/repositories/index.d.ts.map +1 -0
  188. package/dist/repositories/index.js +15 -0
  189. package/dist/repositories/index.js.map +1 -0
  190. package/dist/repositories/mcVersion.d.ts +16 -0
  191. package/dist/repositories/mcVersion.d.ts.map +1 -0
  192. package/dist/repositories/mcVersion.js +52 -0
  193. package/dist/repositories/mcVersion.js.map +1 -0
  194. package/dist/repositories/mod.d.ts +241 -0
  195. package/dist/repositories/mod.d.ts.map +1 -0
  196. package/dist/repositories/mod.js +342 -0
  197. package/dist/repositories/mod.js.map +1 -0
  198. package/dist/repositories/packs.d.ts +44 -0
  199. package/dist/repositories/packs.d.ts.map +1 -0
  200. package/dist/repositories/packs.js +138 -0
  201. package/dist/repositories/packs.js.map +1 -0
  202. package/dist/search-adapter.d.ts +34 -0
  203. package/dist/search-adapter.d.ts.map +1 -0
  204. package/dist/search-adapter.js +195 -0
  205. package/dist/search-adapter.js.map +1 -0
  206. package/dist/search.d.ts +6 -0
  207. package/dist/search.d.ts.map +1 -0
  208. package/dist/search.js +54 -0
  209. package/dist/search.js.map +1 -0
  210. package/dist/search.test.d.ts +2 -0
  211. package/dist/search.test.d.ts.map +1 -0
  212. package/dist/search.test.js +71 -0
  213. package/dist/search.test.js.map +1 -0
  214. package/dist/security.d.ts +98 -0
  215. package/dist/security.d.ts.map +1 -0
  216. package/dist/security.js +446 -0
  217. package/dist/security.js.map +1 -0
  218. package/dist/security.test.d.ts +2 -0
  219. package/dist/security.test.d.ts.map +1 -0
  220. package/dist/security.test.js +687 -0
  221. package/dist/security.test.js.map +1 -0
  222. package/dist/server.d.ts +2 -0
  223. package/dist/server.d.ts.map +1 -0
  224. package/dist/server.js +1465 -0
  225. package/dist/server.js.map +1 -0
  226. package/dist/setup.d.ts +2 -0
  227. package/dist/setup.d.ts.map +1 -0
  228. package/dist/setup.js +1113 -0
  229. package/dist/setup.js.map +1 -0
  230. package/dist/tools/bytecode.d.ts +129 -0
  231. package/dist/tools/bytecode.d.ts.map +1 -0
  232. package/dist/tools/bytecode.js +679 -0
  233. package/dist/tools/bytecode.js.map +1 -0
  234. package/dist/tools/catalog.d.ts +54 -0
  235. package/dist/tools/catalog.d.ts.map +1 -0
  236. package/dist/tools/catalog.js +249 -0
  237. package/dist/tools/catalog.js.map +1 -0
  238. package/dist/tools/compat-check.d.ts +23 -0
  239. package/dist/tools/compat-check.d.ts.map +1 -0
  240. package/dist/tools/compat-check.js +250 -0
  241. package/dist/tools/compat-check.js.map +1 -0
  242. package/dist/tools/compat-check.test.d.ts +2 -0
  243. package/dist/tools/compat-check.test.d.ts.map +1 -0
  244. package/dist/tools/compat-check.test.js +179 -0
  245. package/dist/tools/compat-check.test.js.map +1 -0
  246. package/dist/tools/diagnostics.d.ts +10 -0
  247. package/dist/tools/diagnostics.d.ts.map +1 -0
  248. package/dist/tools/diagnostics.js +133 -0
  249. package/dist/tools/diagnostics.js.map +1 -0
  250. package/dist/tools/diagnostics.test.d.ts +2 -0
  251. package/dist/tools/diagnostics.test.d.ts.map +1 -0
  252. package/dist/tools/diagnostics.test.js +168 -0
  253. package/dist/tools/diagnostics.test.js.map +1 -0
  254. package/dist/tools/docs.d.ts +19 -0
  255. package/dist/tools/docs.d.ts.map +1 -0
  256. package/dist/tools/docs.js +230 -0
  257. package/dist/tools/docs.js.map +1 -0
  258. package/dist/tools/embed-registry.d.ts +125 -0
  259. package/dist/tools/embed-registry.d.ts.map +1 -0
  260. package/dist/tools/embed-registry.js +560 -0
  261. package/dist/tools/embed-registry.js.map +1 -0
  262. package/dist/tools/gradle.d.ts +16 -0
  263. package/dist/tools/gradle.d.ts.map +1 -0
  264. package/dist/tools/gradle.js +182 -0
  265. package/dist/tools/gradle.js.map +1 -0
  266. package/dist/tools/graphify.d.ts +144 -0
  267. package/dist/tools/graphify.d.ts.map +1 -0
  268. package/dist/tools/graphify.js +798 -0
  269. package/dist/tools/graphify.js.map +1 -0
  270. package/dist/tools/graphify.test.d.ts +2 -0
  271. package/dist/tools/graphify.test.d.ts.map +1 -0
  272. package/dist/tools/graphify.test.js +39 -0
  273. package/dist/tools/graphify.test.js.map +1 -0
  274. package/dist/tools/ingest.d.ts +85 -0
  275. package/dist/tools/ingest.d.ts.map +1 -0
  276. package/dist/tools/ingest.js +477 -0
  277. package/dist/tools/ingest.js.map +1 -0
  278. package/dist/tools/ingest.test.d.ts +2 -0
  279. package/dist/tools/ingest.test.d.ts.map +1 -0
  280. package/dist/tools/ingest.test.js +340 -0
  281. package/dist/tools/ingest.test.js.map +1 -0
  282. package/dist/tools/kubejs.d.ts +37 -0
  283. package/dist/tools/kubejs.d.ts.map +1 -0
  284. package/dist/tools/kubejs.js +245 -0
  285. package/dist/tools/kubejs.js.map +1 -0
  286. package/dist/tools/mappings.d.ts +17 -0
  287. package/dist/tools/mappings.d.ts.map +1 -0
  288. package/dist/tools/mappings.js +85 -0
  289. package/dist/tools/mappings.js.map +1 -0
  290. package/dist/tools/mc-fts.d.ts +101 -0
  291. package/dist/tools/mc-fts.d.ts.map +1 -0
  292. package/dist/tools/mc-fts.js +279 -0
  293. package/dist/tools/mc-fts.js.map +1 -0
  294. package/dist/tools/mcmeta.d.ts +41 -0
  295. package/dist/tools/mcmeta.d.ts.map +1 -0
  296. package/dist/tools/mcmeta.js +365 -0
  297. package/dist/tools/mcmeta.js.map +1 -0
  298. package/dist/tools/mixin-scan.d.ts +113 -0
  299. package/dist/tools/mixin-scan.d.ts.map +1 -0
  300. package/dist/tools/mixin-scan.js +433 -0
  301. package/dist/tools/mixin-scan.js.map +1 -0
  302. package/dist/tools/mod-data.d.ts +83 -0
  303. package/dist/tools/mod-data.d.ts.map +1 -0
  304. package/dist/tools/mod-data.js +630 -0
  305. package/dist/tools/mod-data.js.map +1 -0
  306. package/dist/tools/mod-tags.d.ts +59 -0
  307. package/dist/tools/mod-tags.d.ts.map +1 -0
  308. package/dist/tools/mod-tags.js +279 -0
  309. package/dist/tools/mod-tags.js.map +1 -0
  310. package/dist/tools/mod-tags.test.d.ts +2 -0
  311. package/dist/tools/mod-tags.test.d.ts.map +1 -0
  312. package/dist/tools/mod-tags.test.js +75 -0
  313. package/dist/tools/mod-tags.test.js.map +1 -0
  314. package/dist/tools/modpacks-ch.d.ts +257 -0
  315. package/dist/tools/modpacks-ch.d.ts.map +1 -0
  316. package/dist/tools/modpacks-ch.js +516 -0
  317. package/dist/tools/modpacks-ch.js.map +1 -0
  318. package/dist/tools/packtools.d.ts +113 -0
  319. package/dist/tools/packtools.d.ts.map +1 -0
  320. package/dist/tools/packtools.js +809 -0
  321. package/dist/tools/packtools.js.map +1 -0
  322. package/dist/tools/packtools.test.d.ts +2 -0
  323. package/dist/tools/packtools.test.d.ts.map +1 -0
  324. package/dist/tools/packtools.test.js +99 -0
  325. package/dist/tools/packtools.test.js.map +1 -0
  326. package/dist/tools/platform.d.ts +68 -0
  327. package/dist/tools/platform.d.ts.map +1 -0
  328. package/dist/tools/platform.js +452 -0
  329. package/dist/tools/platform.js.map +1 -0
  330. package/dist/tools/primers.d.ts +36 -0
  331. package/dist/tools/primers.d.ts.map +1 -0
  332. package/dist/tools/primers.js +451 -0
  333. package/dist/tools/primers.js.map +1 -0
  334. package/dist/tools/reports.d.ts +19 -0
  335. package/dist/tools/reports.d.ts.map +1 -0
  336. package/dist/tools/reports.js +573 -0
  337. package/dist/tools/reports.js.map +1 -0
  338. package/dist/tools/source.d.ts +10 -0
  339. package/dist/tools/source.d.ts.map +1 -0
  340. package/dist/tools/source.js +101 -0
  341. package/dist/tools/source.js.map +1 -0
  342. package/dist/tools/vanilla-data.d.ts +127 -0
  343. package/dist/tools/vanilla-data.d.ts.map +1 -0
  344. package/dist/tools/vanilla-data.js +824 -0
  345. package/dist/tools/vanilla-data.js.map +1 -0
  346. package/dist/tools/vanilla.d.ts +171 -0
  347. package/dist/tools/vanilla.d.ts.map +1 -0
  348. package/dist/tools/vanilla.js +612 -0
  349. package/dist/tools/vanilla.js.map +1 -0
  350. package/dist/tools/version-diff.d.ts +113 -0
  351. package/dist/tools/version-diff.d.ts.map +1 -0
  352. package/dist/tools/version-diff.js +519 -0
  353. package/dist/tools/version-diff.js.map +1 -0
  354. package/dist/tools/version-diff.test.d.ts +2 -0
  355. package/dist/tools/version-diff.test.d.ts.map +1 -0
  356. package/dist/tools/version-diff.test.js +102 -0
  357. package/dist/tools/version-diff.test.js.map +1 -0
  358. package/dist/validate.d.ts +19 -0
  359. package/dist/validate.d.ts.map +1 -0
  360. package/dist/validate.js +54 -0
  361. package/dist/validate.js.map +1 -0
  362. package/dist/validate.test.d.ts +2 -0
  363. package/dist/validate.test.d.ts.map +1 -0
  364. package/dist/validate.test.js +73 -0
  365. package/dist/validate.test.js.map +1 -0
  366. package/package.json +62 -0
  367. package/prisma/backends/schema.sqlite.prisma +215 -0
  368. package/prisma/backends/template.db +0 -0
  369. package/prisma/schema.prisma +303 -0
  370. package/scripts/backup.mjs +151 -0
  371. package/scripts/build-template-db.mjs +38 -0
  372. package/scripts/check-atm10.mjs +27 -0
  373. package/scripts/check-sync.mjs +13 -0
  374. package/scripts/create-fts-index.mjs +50 -0
  375. package/scripts/enable-pgvector.mjs +90 -0
  376. package/scripts/enable-sqlite-vec.mjs +158 -0
  377. package/scripts/migrate-backend.mjs +188 -0
  378. package/scripts/migrate-embed-provenance.mjs +80 -0
  379. package/scripts/migrate-metadata-source.mjs +62 -0
  380. package/scripts/migrate-mod-source-files.mjs +67 -0
  381. package/scripts/post-update.mjs +77 -0
  382. package/scripts/verify-atm10.mjs +63 -0
@@ -0,0 +1,630 @@
1
+ /**
2
+ * Tools for reading data/asset files directly from mod JARs.
3
+ *
4
+ * Provides feature parity with the vanilla data tools (recipes, loot tables,
5
+ * advancements, blockstates, models, biomes, sounds, lang, structures) but
6
+ * for any mod in the DB.
7
+ *
8
+ * All functions resolve the mod's JAR path from the DB then use
9
+ * extractEntry / listEntries from jar.ts to read the ZIP/JAR contents.
10
+ * No decompilation is required.
11
+ */
12
+ import { extractEntry, listEntries } from "../jar.js";
13
+ import { resolveModRef, findModById, listModsSlim } from "../repositories/mod.js";
14
+ import { writeFile, mkdir } from "fs/promises";
15
+ import { join, extname, dirname, resolve } from "path";
16
+ import { CACHE_ROOT } from "../cache.js";
17
+ // ── Internal helpers ──────────────────────────────────────────────────────────
18
+ async function resolveMod(modId) {
19
+ if (typeof modId === "number") {
20
+ return findModById(modId);
21
+ }
22
+ const numeric = parseInt(modId, 10);
23
+ if (!isNaN(numeric)) {
24
+ const byNum = await findModById(numeric);
25
+ if (byNum)
26
+ return byNum;
27
+ }
28
+ return resolveModRef(String(modId));
29
+ }
30
+ /**
31
+ * Detect all namespaces present under a prefix (data/ or assets/) in a JAR.
32
+ * E.g. data/mymod/ → "mymod", data/minecraft/ → "minecraft"
33
+ */
34
+ function detectNamespaces(jarPath, prefix) {
35
+ const entries = listEntries(jarPath, `${prefix}/`);
36
+ const ns = new Set();
37
+ for (const e of entries) {
38
+ const parts = e.split("/");
39
+ if (parts.length >= 2 && parts[1])
40
+ ns.add(parts[1]);
41
+ }
42
+ return [...ns].filter(n => n !== "META-INF" && !n.includes("."));
43
+ }
44
+ function readJson(jarPath, entryPath) {
45
+ const buf = extractEntry(jarPath, entryPath);
46
+ if (!buf)
47
+ return null;
48
+ try {
49
+ return JSON.parse(buf.toString("utf8"));
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function listJsonEntries(jarPath, prefix) {
56
+ return listEntries(jarPath, prefix).filter(e => e.endsWith(".json") && !e.endsWith("/"));
57
+ }
58
+ const DATA_TYPES = [
59
+ { type: "recipe", root: "data", subPath: "recipe/", altSubPaths: ["recipes/"], idPattern: /^data\/([^/]+)\/recipes?\/(.*?)\.json$/, resultKey: "recipes" },
60
+ { type: "loot_table", root: "data", subPath: "loot_tables/", altSubPaths: ["loot_table/"], idPattern: /^data\/([^/]+)\/loot_tables?\/(.*?)\.json$/, resultKey: "lootTables" },
61
+ { type: "advancement", root: "data", subPath: "advancement/", altSubPaths: ["advancements/"], idPattern: /^data\/([^/]+)\/advancements?\/(.*?)\.json$/, resultKey: "advancements" },
62
+ { type: "blockstate", root: "assets", subPath: "blockstates/", idPattern: /^assets\/([^/]+)\/blockstates\/(.*?)\.json$/, resultKey: "blockstates" },
63
+ { type: "model", root: "assets", subPath: "models/", idPattern: /^assets\/([^/]+)\/models\/(.*?)\.json$/, resultKey: "models" },
64
+ { type: "biome", root: "data", subPath: "worldgen/biome/", idPattern: /^data\/([^/]+)\/worldgen\/biome\/(.*?)\.json$/, resultKey: "biomes" },
65
+ { type: "structure", root: "data", subPath: "worldgen/structure/", idPattern: /^data\/([^/]+)\/worldgen\/structure\/(.*?)\.json$/, resultKey: "structures" },
66
+ { type: "particle", root: "assets", subPath: "particles/", idPattern: /^assets\/([^/]+)\/particles\/(.*?)\.json$/, resultKey: "particles" },
67
+ { type: "damage_type", root: "data", subPath: "damage_type/", idPattern: /^data\/([^/]+)\/damage_type\/(.*?)\.json$/, resultKey: "damageTypes" },
68
+ { type: "enchantment", root: "data", subPath: "enchantment/", idPattern: /^data\/([^/]+)\/enchantment\/(.*?)\.json$/, resultKey: "enchantments" },
69
+ ];
70
+ function getDescriptor(type) {
71
+ return DATA_TYPES.find(d => d.type === type);
72
+ }
73
+ /**
74
+ * List all entries of a registered data type in a mod JAR.
75
+ * type: one of the keys in DATA_TYPES (recipe, loot_table, advancement, blockstate, model, biome, structure, particle, damage_type, enchantment)
76
+ */
77
+ export async function listModData(modId, type, opts) {
78
+ const descriptor = getDescriptor(type);
79
+ if (!descriptor)
80
+ return { error: `Unknown data type: "${type}". Known types: ${DATA_TYPES.map(d => d.type).join(", ")}` };
81
+ const mod = await resolveMod(modId);
82
+ if (!mod)
83
+ return { error: `Mod not found: ${modId}` };
84
+ const { root, subPath, altSubPaths = [], idPattern, resultKey } = descriptor;
85
+ const ns = opts?.namespace ?? mod.modId;
86
+ let entries = listJsonEntries(mod.jarPath, `${root}/${ns}/${subPath}`);
87
+ for (const alt of altSubPaths) {
88
+ if (entries.length === 0)
89
+ entries = listJsonEntries(mod.jarPath, `${root}/${ns}/${alt}`);
90
+ }
91
+ if (entries.length === 0) {
92
+ const allNs = detectNamespaces(mod.jarPath, root);
93
+ for (const n of allNs) {
94
+ for (const sp of [subPath, ...altSubPaths]) {
95
+ entries.push(...listJsonEntries(mod.jarPath, `${root}/${n}/${sp}`));
96
+ }
97
+ }
98
+ }
99
+ if (opts?.filter) {
100
+ const f = opts.filter.toLowerCase();
101
+ entries = entries.filter(e => e.toLowerCase().includes(f));
102
+ }
103
+ const ids = entries.map(e => { const m = e.match(idPattern); return m ? `${m[1]}:${m[2]}` : e; });
104
+ return { mod: mod.modId, type, count: ids.length, [resultKey]: ids };
105
+ }
106
+ /**
107
+ * Get a single data entry by resource location from a mod JAR.
108
+ * type: one of the keys in DATA_TYPES
109
+ * id: resource location string, e.g. "mymod:iron_sword" or "iron_sword"
110
+ */
111
+ export async function getModData(modId, type, id, opts) {
112
+ const descriptor = getDescriptor(type);
113
+ if (!descriptor)
114
+ return getModGenericDataType(modId, type, id, opts?.namespace);
115
+ const mod = await resolveMod(modId);
116
+ if (!mod)
117
+ return { error: `Mod not found: ${modId}` };
118
+ const { root, subPath, altSubPaths = [] } = descriptor;
119
+ const ns = opts?.namespace ?? (id.includes(":") ? id.split(":")[0] : mod.modId);
120
+ const path = id.includes(":") ? id.split(":")[1] : id;
121
+ for (const sp of [subPath, ...altSubPaths]) {
122
+ const data = readJson(mod.jarPath, `${root}/${ns}/${sp}${path}.json`);
123
+ if (data)
124
+ return { mod: mod.modId, type, id: `${ns}:${path}`, data };
125
+ }
126
+ return { mod: mod.modId, type, id, found: false };
127
+ }
128
+ // ── Generic JAR file access ───────────────────────────────────────────────────
129
+ /**
130
+ * List all files inside a mod JAR under an optional path prefix.
131
+ * prefix: e.g. "data/mymod/recipe/", "assets/mymod/blockstates/"
132
+ */
133
+ export async function listModJarFiles(modId, prefix = "") {
134
+ const mod = await resolveMod(modId);
135
+ if (!mod)
136
+ return { error: `Mod not found: ${modId}` };
137
+ const entries = listEntries(mod.jarPath, prefix)
138
+ .filter(e => !e.endsWith("/"))
139
+ .sort();
140
+ return { mod: mod.modId, prefix: prefix || "(root)", count: entries.length, entries };
141
+ }
142
+ const BINARY_EXTS = new Set([".png", ".ogg", ".gif", ".wav", ".nbt", ".dat", ".mca", ".jar", ".class", ".ttf", ".otf", ".ico", ".webp", ".jpg", ".jpeg", ".bmp"]);
143
+ /**
144
+ * Read any file from a mod JAR by its internal path.
145
+ * Returns parsed JSON for .json files, cached file path for binary files, raw text for others.
146
+ * path: e.g. "data/mymod/recipe/iron_sword.json", "assets/mymod/sounds.json"
147
+ */
148
+ export async function getModJarFile(modId, path) {
149
+ const mod = await resolveMod(modId);
150
+ if (!mod)
151
+ return { error: `Mod not found: ${modId}` };
152
+ const buf = extractEntry(mod.jarPath, path);
153
+ if (!buf)
154
+ return { mod: mod.modId, path, found: false };
155
+ const ext = extname(path).toLowerCase();
156
+ if (BINARY_EXTS.has(ext)) {
157
+ const jarExtractRoot = resolve(CACHE_ROOT, "jar-extract");
158
+ const cachePath = resolve(join(CACHE_ROOT, "jar-extract", mod.modId, path));
159
+ if (!cachePath.startsWith(jarExtractRoot + require("path").sep)) {
160
+ return { error: `Path traversal rejected: ${path}` };
161
+ }
162
+ await mkdir(dirname(cachePath), { recursive: true });
163
+ await writeFile(cachePath, buf);
164
+ return { mod: mod.modId, path, cachedAt: cachePath, encoding: "binary", sizeBytes: buf.length };
165
+ }
166
+ if (path.endsWith(".json")) {
167
+ try {
168
+ const data = JSON.parse(buf.toString("utf8"));
169
+ return { mod: mod.modId, path, data };
170
+ }
171
+ catch {
172
+ return { mod: mod.modId, path, raw: buf.toString("utf8") };
173
+ }
174
+ }
175
+ return { mod: mod.modId, path, raw: buf.toString("utf8").slice(0, 4096) };
176
+ }
177
+ // ── Models ────────────────────────────────────────────────────────────────────
178
+ export async function getModModel(modId, modelPath, namespace) {
179
+ const mod = await resolveMod(modId);
180
+ if (!mod)
181
+ return { error: `Mod not found: ${modId}` };
182
+ const ns = namespace ?? (modelPath.includes(":") ? modelPath.split(":")[0] : mod.modId);
183
+ const path = modelPath.includes(":") ? modelPath.split(":")[1] : modelPath;
184
+ const fullPath = path.includes("/") ? path : `item/${path}`;
185
+ const data = readJson(mod.jarPath, `assets/${ns}/models/${fullPath}.json`);
186
+ if (data)
187
+ return { mod: mod.modId, model: `${ns}:${fullPath}`, data };
188
+ return { mod: mod.modId, model: modelPath, found: false };
189
+ }
190
+ // ── Lang ──────────────────────────────────────────────────────────────────────
191
+ export async function getModLang(modId, filter, limit = 200) {
192
+ const mod = await resolveMod(modId);
193
+ if (!mod)
194
+ return { error: `Mod not found: ${modId}` };
195
+ // Try all namespaces
196
+ const allNs = detectNamespaces(mod.jarPath, "assets");
197
+ let lang = null;
198
+ let usedNs = "";
199
+ for (const ns of allNs) {
200
+ for (const name of ["en_us.json", "en_US.json"]) {
201
+ const buf = extractEntry(mod.jarPath, `assets/${ns}/lang/${name}`);
202
+ if (buf) {
203
+ try {
204
+ lang = JSON.parse(buf.toString("utf8"));
205
+ usedNs = ns;
206
+ break;
207
+ }
208
+ catch { /* skip */ }
209
+ }
210
+ }
211
+ if (lang)
212
+ break;
213
+ }
214
+ if (!lang)
215
+ return { mod: mod.modId, found: false, note: "No en_us.json lang file found." };
216
+ let entries = Object.entries(lang);
217
+ if (filter) {
218
+ const f = filter.toLowerCase();
219
+ entries = entries.filter(([k, v]) => k.toLowerCase().includes(f) || v.toLowerCase().includes(f));
220
+ }
221
+ return {
222
+ mod: mod.modId,
223
+ namespace: usedNs,
224
+ total: Object.keys(lang).length,
225
+ shown: Math.min(entries.length, limit),
226
+ filter: filter ?? "(none)",
227
+ entries: Object.fromEntries(entries.slice(0, limit)),
228
+ };
229
+ }
230
+ // ── Sounds ────────────────────────────────────────────────────────────────────
231
+ export async function getModSounds(modId, namespace) {
232
+ const mod = await resolveMod(modId);
233
+ if (!mod)
234
+ return { error: `Mod not found: ${modId}` };
235
+ const ns = namespace ?? mod.modId;
236
+ let data = readJson(mod.jarPath, `assets/${ns}/sounds.json`);
237
+ if (!data) {
238
+ const allNs = detectNamespaces(mod.jarPath, "assets");
239
+ for (const n of allNs.filter(n => n !== "minecraft")) {
240
+ data = readJson(mod.jarPath, `assets/${n}/sounds.json`);
241
+ if (data) {
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ if (!data)
247
+ return { mod: mod.modId, found: false };
248
+ return { mod: mod.modId, data };
249
+ }
250
+ // ── Tags (data pack) ──────────────────────────────────────────────────────────
251
+ export async function listModDataTags(modId, registry, namespace, filter) {
252
+ const mod = await resolveMod(modId);
253
+ if (!mod)
254
+ return { error: `Mod not found: ${modId}` };
255
+ const ns = namespace ?? mod.modId;
256
+ const regPath = registry ? `data/${ns}/tags/${registry}/` : `data/${ns}/tags/`;
257
+ let entries = listJsonEntries(mod.jarPath, regPath);
258
+ if (entries.length === 0 && !namespace) {
259
+ const allNs = detectNamespaces(mod.jarPath, "data");
260
+ for (const n of allNs) {
261
+ const base = registry ? `data/${n}/tags/${registry}/` : `data/${n}/tags/`;
262
+ entries.push(...listJsonEntries(mod.jarPath, base));
263
+ }
264
+ }
265
+ if (filter) {
266
+ const f = filter.toLowerCase();
267
+ entries = entries.filter(e => e.toLowerCase().includes(f));
268
+ }
269
+ const ids = entries.map(e => {
270
+ const m = e.match(/^data\/([^/]+)\/tags\/([^/]+)\/(.*?)\.json$/);
271
+ return m ? { registry: m[2], tag: `${m[1]}:${m[3]}`, path: e } : { registry: "unknown", tag: e, path: e };
272
+ });
273
+ return { mod: mod.modId, count: ids.length, tags: ids };
274
+ }
275
+ export async function getModDataTag(modId, registry, id, namespace) {
276
+ const mod = await resolveMod(modId);
277
+ if (!mod)
278
+ return { error: `Mod not found: ${modId}` };
279
+ const ns = namespace ?? (id.includes(":") ? id.split(":")[0] : mod.modId);
280
+ const path = id.includes(":") ? id.split(":")[1] : id;
281
+ const data = readJson(mod.jarPath, `data/${ns}/tags/${registry}/${path}.json`);
282
+ if (data)
283
+ return { mod: mod.modId, registry, tag: `${ns}:${path}`, data };
284
+ return { mod: mod.modId, registry, tag: id, found: false };
285
+ }
286
+ // ── Atlas ─────────────────────────────────────────────────────────────────────
287
+ export async function getModAtlas(modId, atlas = "blocks", namespace) {
288
+ const mod = await resolveMod(modId);
289
+ if (!mod)
290
+ return { error: `Mod not found: ${modId}` };
291
+ const ns = namespace ?? mod.modId;
292
+ const name = atlas.replace(".json", "");
293
+ let data = readJson(mod.jarPath, `assets/${ns}/atlases/${name}.json`);
294
+ if (!data && !namespace) {
295
+ const allNs = detectNamespaces(mod.jarPath, "assets");
296
+ for (const n of allNs.filter(n => n !== "minecraft")) {
297
+ data = readJson(mod.jarPath, `assets/${n}/atlases/${name}.json`);
298
+ if (data)
299
+ break;
300
+ }
301
+ }
302
+ if (data)
303
+ return { mod: mod.modId, atlas: name, data };
304
+ return { mod: mod.modId, atlas: name, found: false };
305
+ }
306
+ // ── Generic data-path helpers (worldgen, dimension, variants, etc.) ───────────
307
+ /**
308
+ * Maps mod_data type names → list of candidate subdirectory paths under data/<ns>/.
309
+ * Some types have alternate folder names for compatibility across MC versions.
310
+ */
311
+ export const GENERIC_DATA_DIRS = {
312
+ configured_feature: ["worldgen/configured_feature"],
313
+ placed_feature: ["worldgen/placed_feature"],
314
+ structure_set: ["worldgen/structure_set"],
315
+ noise: ["worldgen/noise"],
316
+ density_function: ["worldgen/density_function"],
317
+ processor_list: ["worldgen/processor_list"],
318
+ template_pool: ["worldgen/template_pool"],
319
+ dimension_type: ["dimension_type"],
320
+ dimension: ["dimension"],
321
+ trim_material: ["trim_material"],
322
+ trim_pattern: ["trim_pattern"],
323
+ painting_variant: ["painting_variant"],
324
+ wolf_variant: ["wolf_variant"],
325
+ cat_variant: ["cat_variant"],
326
+ chat_type: ["chat_type"],
327
+ };
328
+ export async function listModGenericDataType(modId, typeKey, namespace, filter) {
329
+ const mod = await resolveMod(modId);
330
+ if (!mod)
331
+ return { error: `Mod not found: ${modId}` };
332
+ const dirs = GENERIC_DATA_DIRS[typeKey];
333
+ if (!dirs)
334
+ return { error: `Unknown data type: ${typeKey}` };
335
+ let entries = [];
336
+ if (namespace) {
337
+ for (const dir of dirs) {
338
+ entries.push(...listJsonEntries(mod.jarPath, `data/${namespace}/${dir}/`));
339
+ }
340
+ }
341
+ if (entries.length === 0) {
342
+ const allNs = detectNamespaces(mod.jarPath, "data");
343
+ for (const ns of allNs) {
344
+ for (const dir of dirs) {
345
+ entries.push(...listJsonEntries(mod.jarPath, `data/${ns}/${dir}/`));
346
+ }
347
+ }
348
+ }
349
+ if (filter) {
350
+ const f = filter.toLowerCase();
351
+ entries = entries.filter(e => e.toLowerCase().includes(f));
352
+ }
353
+ const ids = entries.map(e => {
354
+ // data/<ns>/<dir>/[sub/]name.json → ns:dir/[sub/]name
355
+ const m = e.match(/^data\/([^/]+)\/(.+?)\.json$/);
356
+ if (!m)
357
+ return e;
358
+ // Strip the type dir prefix to get just the resource path
359
+ const fullPath = m[2]; // e.g. worldgen/configured_feature/my_feature
360
+ // Find which dir prefix applies
361
+ for (const dir of dirs) {
362
+ if (fullPath.startsWith(dir + "/")) {
363
+ return `${m[1]}:${fullPath.slice(dir.length + 1)}`;
364
+ }
365
+ }
366
+ return `${m[1]}:${fullPath}`;
367
+ });
368
+ return { mod: mod.modId, type: typeKey, count: ids.length, entries: ids };
369
+ }
370
+ export async function getModGenericDataType(modId, typeKey, id, namespace) {
371
+ const mod = await resolveMod(modId);
372
+ if (!mod)
373
+ return { error: `Mod not found: ${modId}` };
374
+ const dirs = GENERIC_DATA_DIRS[typeKey];
375
+ if (!dirs)
376
+ return { error: `Unknown data type: ${typeKey}` };
377
+ const ns = namespace ?? (id.includes(":") ? id.split(":")[0] : mod.modId);
378
+ const path = id.includes(":") ? id.split(":")[1] : id;
379
+ for (const dir of dirs) {
380
+ const data = readJson(mod.jarPath, `data/${ns}/${dir}/${path}.json`);
381
+ if (data)
382
+ return { mod: mod.modId, type: typeKey, id: `${ns}:${path}`, data };
383
+ }
384
+ return { mod: mod.modId, type: typeKey, id, found: false };
385
+ }
386
+ // ── Mod manifest reader ───────────────────────────────────────────────────────
387
+ /**
388
+ * Read and parse the mod's loader manifest file:
389
+ * NeoForge/Forge → META-INF/neoforge.mods.toml or META-INF/mods.toml
390
+ * Fabric → fabric.mod.json
391
+ * Quilt → quilt.mod.json
392
+ *
393
+ * Returns raw text (TOML) or parsed JSON depending on format.
394
+ */
395
+ export async function getModManifest(modId) {
396
+ const mod = await resolveMod(modId);
397
+ if (!mod)
398
+ return { error: `Mod not found: ${modId}` };
399
+ const candidates = [
400
+ { path: "META-INF/neoforge.mods.toml", format: "toml" },
401
+ { path: "META-INF/mods.toml", format: "toml" },
402
+ { path: "fabric.mod.json", format: "json" },
403
+ { path: "quilt.mod.json", format: "json" },
404
+ { path: "mcmod.info", format: "json" },
405
+ ];
406
+ for (const { path, format } of candidates) {
407
+ const buf = extractEntry(mod.jarPath, path);
408
+ if (!buf)
409
+ continue;
410
+ const raw = buf.toString("utf8");
411
+ if (format === "json") {
412
+ try {
413
+ return { mod: mod.modId, manifestPath: path, format, data: JSON.parse(raw) };
414
+ }
415
+ catch {
416
+ return { mod: mod.modId, manifestPath: path, format, raw };
417
+ }
418
+ }
419
+ // TOML — return raw text (TOML parsing requires a dep we don't have)
420
+ return { mod: mod.modId, manifestPath: path, format, raw };
421
+ }
422
+ return { mod: mod.modId, error: "No manifest file found (neoforge.mods.toml, mods.toml, fabric.mod.json, quilt.mod.json, mcmod.info)" };
423
+ }
424
+ // ── Config file browser ───────────────────────────────────────────────────────
425
+ /**
426
+ * List default config files shipped inside the JAR under defaultconfigs/ or config/.
427
+ * These are the starter configs a mod ships; they get copied to the instance config dir on first run.
428
+ */
429
+ export async function listModConfigs(modId) {
430
+ const mod = await resolveMod(modId);
431
+ if (!mod)
432
+ return { error: `Mod not found: ${modId}` };
433
+ const prefixes = ["defaultconfigs/", "config/"];
434
+ const files = [];
435
+ for (const prefix of prefixes) {
436
+ files.push(...listEntries(mod.jarPath, prefix).filter(e => !e.endsWith("/")));
437
+ }
438
+ return { mod: mod.modId, count: files.length, configs: files };
439
+ }
440
+ export async function getModConfig(modId, path) {
441
+ const mod = await resolveMod(modId);
442
+ if (!mod)
443
+ return { error: `Mod not found: ${modId}` };
444
+ const buf = extractEntry(mod.jarPath, path);
445
+ if (!buf)
446
+ return { mod: mod.modId, path, found: false };
447
+ const raw = buf.toString("utf8");
448
+ if (path.endsWith(".json")) {
449
+ try {
450
+ return { mod: mod.modId, path, data: JSON.parse(raw) };
451
+ }
452
+ catch { /* fall through */ }
453
+ }
454
+ return { mod: mod.modId, path, raw: raw.slice(0, 8192) };
455
+ }
456
+ // ── Data diff between mod versions ────────────────────────────────────────────
457
+ /**
458
+ * Compare data files between two mod versions (by DB id).
459
+ * Returns: files only in A (removed), only in B (added), and files in both whose
460
+ * JSON content changed. Use type filter to scope to a specific category.
461
+ */
462
+ export async function diffModData(dbIdA, dbIdB, dataType) {
463
+ const [modA, modB] = await Promise.all([
464
+ findModById(dbIdA),
465
+ findModById(dbIdB),
466
+ ]);
467
+ if (!modA)
468
+ return { error: `Mod #${dbIdA} not found` };
469
+ if (!modB)
470
+ return { error: `Mod #${dbIdB} not found` };
471
+ const getDataEntries = (jarPath) => {
472
+ let entries = listJsonEntries(jarPath, "data/");
473
+ if (dataType) {
474
+ const f = dataType.toLowerCase();
475
+ entries = entries.filter(e => e.toLowerCase().includes(f));
476
+ }
477
+ const map = new Map();
478
+ for (const e of entries) {
479
+ const buf = extractEntry(jarPath, e);
480
+ map.set(e, buf ? buf.toString("utf8") : "");
481
+ }
482
+ return map;
483
+ };
484
+ const [filesA, filesB] = await Promise.all([
485
+ Promise.resolve(getDataEntries(modA.jarPath)),
486
+ Promise.resolve(getDataEntries(modB.jarPath)),
487
+ ]);
488
+ const keysA = new Set(filesA.keys());
489
+ const keysB = new Set(filesB.keys());
490
+ const added = [...keysB].filter(k => !keysA.has(k));
491
+ const removed = [...keysA].filter(k => !keysB.has(k));
492
+ const changed = [];
493
+ for (const k of keysA) {
494
+ if (!keysB.has(k))
495
+ continue;
496
+ const contentA = filesA.get(k).trim();
497
+ const contentB = filesB.get(k).trim();
498
+ if (contentA !== contentB) {
499
+ // Try to summarise the change at JSON key level
500
+ let note = "content changed";
501
+ try {
502
+ const a = JSON.parse(contentA);
503
+ const b = JSON.parse(contentB);
504
+ const changedKeys = [...new Set([...Object.keys(a), ...Object.keys(b)])]
505
+ .filter(key => JSON.stringify(a[key]) !== JSON.stringify(b[key]));
506
+ if (changedKeys.length > 0)
507
+ note = `changed keys: ${changedKeys.join(", ")}`;
508
+ }
509
+ catch { /* not JSON or nested — keep note as-is */ }
510
+ changed.push({ path: k, note });
511
+ }
512
+ }
513
+ return {
514
+ modA: { id: dbIdA, modId: modA.modId, version: modA.version },
515
+ modB: { id: dbIdB, modId: modB.modId, version: modB.version },
516
+ dataType: dataType ?? "(all)",
517
+ summary: { added: added.length, removed: removed.length, changed: changed.length },
518
+ added,
519
+ removed,
520
+ changed,
521
+ };
522
+ }
523
+ function extractIngredients(recipe) {
524
+ const items = [];
525
+ const push = (v) => {
526
+ if (!v)
527
+ return;
528
+ if (typeof v === "string") {
529
+ items.push(v);
530
+ return;
531
+ }
532
+ if (typeof v === "object" && v !== null) {
533
+ const o = v;
534
+ if (o.item)
535
+ items.push(o.item);
536
+ else if (o.tag)
537
+ items.push(`#${o.tag}`);
538
+ }
539
+ };
540
+ // shaped/shapeless via ingredients array or key map
541
+ if (Array.isArray(recipe.ingredients))
542
+ recipe.ingredients.forEach(push);
543
+ if (recipe.ingredient)
544
+ push(recipe.ingredient);
545
+ if (recipe.base)
546
+ push(recipe.base);
547
+ if (recipe.addition)
548
+ push(recipe.addition);
549
+ if (recipe.input)
550
+ push(recipe.input);
551
+ if (recipe.key)
552
+ Object.values(recipe.key).forEach(push);
553
+ return items;
554
+ }
555
+ function extractResult(recipe) {
556
+ if (!recipe.result)
557
+ return null;
558
+ if (typeof recipe.result === "string")
559
+ return recipe.result;
560
+ return recipe.result.id ?? recipe.result.item ?? null;
561
+ }
562
+ /**
563
+ * Trace the full crafting dependency tree for an item.
564
+ * Recursively resolves all recipes that produce it across all mods in the DB,
565
+ * and for each ingredient finds its recipes too.
566
+ *
567
+ * itemId: resource id of the target item, e.g. "mekanism:steel_ingot"
568
+ * maxDepth: recursion guard (default 6)
569
+ * Returns a tree structure with recipes and their ingredients' sub-trees.
570
+ */
571
+ export async function traceRecipeChain(itemId, maxDepth = 6) {
572
+ // Build a recipe index: resultId → [{modId, recipeId, type, ingredients, raw}]
573
+ const allMods = await listModsSlim();
574
+ // Index all recipes from all mods (lazy — cache by modId)
575
+ const recipeIndex = new Map();
576
+ for (const mod of allMods) {
577
+ const entries = listEntries(mod.jarPath, "data/").filter(e => e.includes("/recipes/") && e.endsWith(".json"));
578
+ for (const entry of entries) {
579
+ const buf = extractEntry(mod.jarPath, entry);
580
+ if (!buf)
581
+ continue;
582
+ try {
583
+ const recipe = JSON.parse(buf.toString("utf8"));
584
+ const result = extractResult(recipe);
585
+ if (!result)
586
+ continue;
587
+ const normalId = result.includes(":") ? result : `minecraft:${result}`;
588
+ if (!recipeIndex.has(normalId))
589
+ recipeIndex.set(normalId, []);
590
+ recipeIndex.get(normalId).push({
591
+ mod: mod.modId,
592
+ recipeId: entry,
593
+ type: recipe.type ?? "unknown",
594
+ ingredients: extractIngredients(recipe),
595
+ resultCount: typeof recipe.result === "object" && recipe.result !== null
596
+ ? recipe.result.count ?? 1
597
+ : 1,
598
+ });
599
+ }
600
+ catch { /* skip malformed */ }
601
+ }
602
+ }
603
+ const visited = new Set();
604
+ const buildTree = (id, depth) => {
605
+ if (depth > maxDepth || visited.has(id)) {
606
+ return { item: id, note: depth > maxDepth ? "maxDepth reached" : "circular ref skipped", recipes: [] };
607
+ }
608
+ visited.add(id);
609
+ const normal = id.includes(":") ? id : `minecraft:${id}`;
610
+ const recipes = recipeIndex.get(normal) ?? [];
611
+ const recipeNodes = recipes.map(r => ({
612
+ mod: r.mod,
613
+ recipeId: r.recipeId,
614
+ type: r.type,
615
+ resultCount: r.resultCount,
616
+ ingredients: r.ingredients.map(ing => buildTree(ing, depth + 1)),
617
+ }));
618
+ visited.delete(id); // allow same item via different paths at different depths
619
+ return { item: normal, recipeCount: recipes.length, recipes: recipeNodes };
620
+ };
621
+ const tree = buildTree(itemId, 0);
622
+ return {
623
+ target: itemId,
624
+ maxDepth,
625
+ modsScanned: allMods.length,
626
+ totalRecipesIndexed: [...recipeIndex.values()].reduce((s, v) => s + v.length, 0),
627
+ tree,
628
+ };
629
+ }
630
+ //# sourceMappingURL=mod-data.js.map