modwright 0.1.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 (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,129 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { walk } from "../../fsutil.js";
4
+ import { assertAbsolute, runTool } from "../../toolchain/run.js";
5
+ import { globToRegExp } from "../../validate/context.js";
6
+ import { fileEntry } from "../manifest.js";
7
+ import { resolvedToolNote, resolveOutDirPath, resolveSourceOrStagePath, resolveStageId } from "../paths.js";
8
+ import { applyListingRules } from "./listing.js";
9
+ import { BuildStepError } from "../types.js";
10
+ export const DIVINE_TOOL_VERSION = "unknown (ExportTool zip name only)";
11
+ function toPosix(p) {
12
+ return p.split(path.sep).join("/");
13
+ }
14
+ async function convertGlob(step, ctx, action, destExt, deleteSource) {
15
+ const inDir = resolveStageId(step.in, ctx);
16
+ const location = await ctx.locateTool("divine");
17
+ const re = globToRegExp(step.glob);
18
+ const walked = await walk(inDir, { limit: 200_000 });
19
+ const matches = walked.files.filter((f) => re.test(toPosix(f)));
20
+ if (matches.length === 0) {
21
+ throw new BuildStepError(`${step.step}: no files matched "${step.glob}" under ${inDir}`, step.id, step.step);
22
+ }
23
+ const inputs = [];
24
+ const outputs = [];
25
+ for (const rel of matches) {
26
+ const absIn = path.join(inDir, rel);
27
+ const absOut = absIn.replace(/\.[^./\\]+$/, destExt);
28
+ assertAbsolute([absIn, absOut]);
29
+ const args = ["-g", "bg3", "-a", action, "-s", absIn, "-d", absOut];
30
+ const result = await runTool(location, { args, expectOutputs: [absOut], requireFreshOutputs: true });
31
+ if (!result.ok) {
32
+ throw new BuildStepError(`${step.step} failed on ${rel}: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
33
+ }
34
+ inputs.push(await fileEntry(absIn));
35
+ outputs.push(await fileEntry(absOut));
36
+ if (deleteSource)
37
+ await fs.unlink(absIn);
38
+ }
39
+ return { inputs, outputs };
40
+ }
41
+ export async function runDivineLsxToLsfStep(step, ctx) {
42
+ const inDir = resolveStageId(step.in, ctx);
43
+ if (ctx.mode === "dry-run") {
44
+ return {
45
+ inputs: [],
46
+ outputs: [],
47
+ verified: true,
48
+ plan: `divine convert-resource under ${inDir} (glob ${step.glob})` +
49
+ (step.deleteSource ? ", removing each staged .lsx once its .lsf is written" : ""),
50
+ };
51
+ }
52
+ const deleteSource = step.deleteSource ?? false;
53
+ const { inputs, outputs } = await convertGlob(step, ctx, "convert-resource", ".lsf", deleteSource);
54
+ return {
55
+ inputs,
56
+ outputs,
57
+ verified: true,
58
+ tool: "divine",
59
+ toolVersion: DIVINE_TOOL_VERSION,
60
+ extras: { fileCount: outputs.length, deletedSources: deleteSource ? outputs.length : 0 },
61
+ };
62
+ }
63
+ export async function runDivineLocaStep(step, ctx) {
64
+ const inDir = resolveStageId(step.in, ctx);
65
+ if (ctx.mode === "dry-run") {
66
+ return { inputs: [], outputs: [], verified: true, plan: `divine convert-loca under ${inDir} (glob ${step.glob})` };
67
+ }
68
+ const { inputs, outputs } = await convertGlob(step, ctx, "convert-loca", ".loca", false);
69
+ return { inputs, outputs, verified: true, tool: "divine", toolVersion: DIVINE_TOOL_VERSION, extras: { fileCount: outputs.length } };
70
+ }
71
+ export async function runDivineCreatePackageStep(step, ctx) {
72
+ const from = resolveSourceOrStagePath(step.from, ctx);
73
+ const out = resolveOutDirPath(step.out, ctx);
74
+ const dotSegment = from.split(/[\\/]/).find((seg) => seg.startsWith(".") && seg !== "." && seg !== "..");
75
+ if (dotSegment) {
76
+ throw new BuildStepError(`divine-create-package: the source path ${from} contains a dot-prefixed directory ("${dotSegment}"); ` +
77
+ `Divine silently produces an empty package from such paths. Set build.staging in modwright.json to a ` +
78
+ `path without dot-prefixed segments (for example "build/stage") and gitignore it.`, step.id, step.step);
79
+ }
80
+ const location = await ctx.locateTool("divine");
81
+ const args = ["-g", "bg3", "-a", "create-package", "-s", from, "-d", out];
82
+ if (ctx.mode === "dry-run") {
83
+ return { inputs: [], outputs: [], verified: true, plan: `divine ${args.join(" ")}${resolvedToolNote(location)}` };
84
+ }
85
+ assertAbsolute([from, out]);
86
+ const result = await runTool(location, { args, expectOutputs: [out], requireFreshOutputs: true });
87
+ if (!result.ok) {
88
+ throw new BuildStepError(`divine-create-package failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
89
+ }
90
+ const walked = await walk(from, { limit: 200_000 });
91
+ const staged = new Set(walked.files.map((rel) => rel.split(path.sep).join("/")));
92
+ const listing = await runTool(location, { args: ["-g", "bg3", "-a", "list-package", "-s", out] });
93
+ if (!listing.ok) {
94
+ throw new BuildStepError(`divine-create-package: the pak was written but list-package could not read it back: ` +
95
+ `${listing.error?.message ?? `exit ${listing.exitCode}`}`, step.id, step.step);
96
+ }
97
+ const listed = new Set(listing.stdout
98
+ .split(/\r?\n/)
99
+ .map((line) => line.split("\t")[0].trim().replace(/\\/g, "/"))
100
+ .filter(Boolean));
101
+ const missing = [...staged].filter((rel) => !listed.has(rel));
102
+ if (missing.length > 0) {
103
+ const shown = missing.slice(0, 10).join(", ");
104
+ throw new BuildStepError(`divine-create-package: ${missing.length} staged file(s) are missing from ${out} (${listed.size} listed): ` +
105
+ `${shown}${missing.length > 10 ? ", ..." : ""} — a truncated pak`, step.id, step.step);
106
+ }
107
+ const outputEntry = await fileEntry(out);
108
+ return {
109
+ inputs: [],
110
+ outputs: [outputEntry],
111
+ verified: true,
112
+ tool: "divine",
113
+ toolVersion: DIVINE_TOOL_VERSION,
114
+ extras: { pakSha256: outputEntry.sha256, entryCount: listed.size },
115
+ };
116
+ }
117
+ export async function runVerifyPackageListingStep(step, ctx, of) {
118
+ const location = await ctx.locateTool("divine");
119
+ const args = ["-g", "bg3", "-a", "list-package", "-s", of];
120
+ if (ctx.mode === "dry-run") {
121
+ return { inputs: [], outputs: [], verified: true, plan: `divine ${args.join(" ")}${resolvedToolNote(location)}` };
122
+ }
123
+ assertAbsolute([of]);
124
+ const result = await runTool(location, { args });
125
+ if (!result.ok) {
126
+ throw new BuildStepError(`verify-listing failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
127
+ }
128
+ return applyListingRules(step, of, result.stdout, "divine", DIVINE_TOOL_VERSION, { firstTabField: true });
129
+ }
@@ -0,0 +1,195 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isDirectory, walk } from "../../fsutil.js";
4
+ import { globToRegExp } from "../../validate/context.js";
5
+ import { runTool } from "../../toolchain/run.js";
6
+ import { TOOL_SPECS } from "../../toolchain/specs.js";
7
+ import { directoryEntry, pathEntries } from "../manifest.js";
8
+ import { resolveOutDirPath, resolveSourceOrStagePath, resolveStageId } from "../paths.js";
9
+ import { writeZipFile } from "../zip.js";
10
+ import { BuildStepError, STEP_FAILURE_OUTPUT_CAP } from "../types.js";
11
+ function toPosix(p) {
12
+ return p.split(path.sep).join("/");
13
+ }
14
+ async function walkWhole(root, step) {
15
+ const walked = await walk(root, { limit: 200_000, stat: false });
16
+ if (walked.truncated) {
17
+ throw new BuildStepError(`${step.step}: ${root} holds more than 200000 files; the listing was cut short`, step.id, step.step);
18
+ }
19
+ return walked;
20
+ }
21
+ export async function runAssertAbsentStep(step, ctx) {
22
+ const root = resolveStageId(step.in, ctx);
23
+ if (ctx.mode === "dry-run") {
24
+ return {
25
+ inputs: [],
26
+ outputs: [],
27
+ verified: true,
28
+ plan: `check ${root} for forbidden patterns [${step.patterns.join(", ")}]`,
29
+ };
30
+ }
31
+ if (!(await isDirectory(root))) {
32
+ throw new BuildStepError(`assert-absent: stage subtree not found: ${root}`, step.id, step.step);
33
+ }
34
+ const walked = await walkWhole(root, step);
35
+ const patterns = step.patterns.map(globToRegExp);
36
+ const matched = walked.files.filter((f) => patterns.some((re) => re.test(toPosix(f))));
37
+ if (matched.length > 0) {
38
+ throw new BuildStepError(`assert-absent found forbidden file(s): ${matched.join(", ")}`, step.id, step.step);
39
+ }
40
+ return {
41
+ inputs: [await directoryEntry(root)],
42
+ outputs: [],
43
+ verified: true,
44
+ extras: { checked: walked.files.length, matched: [] },
45
+ };
46
+ }
47
+ export async function runAssertPresentStep(step, ctx) {
48
+ const root = resolveStageId(step.in, ctx);
49
+ if (ctx.mode === "dry-run") {
50
+ return {
51
+ inputs: [],
52
+ outputs: [],
53
+ verified: true,
54
+ plan: `check ${root} contains [${step.paths.join(", ")}]`,
55
+ };
56
+ }
57
+ if (!(await isDirectory(root))) {
58
+ throw new BuildStepError(`assert-present: stage subtree not found: ${root}`, step.id, step.step);
59
+ }
60
+ const walked = await walkWhole(root, step);
61
+ const rels = walked.files.map(toPosix);
62
+ const patterns = step.paths.map(globToRegExp);
63
+ const missing = step.paths.filter((_, i) => !rels.some((f) => patterns[i].test(f)));
64
+ if (missing.length > 0) {
65
+ throw new BuildStepError(`assert-present: missing required path(s): ${missing.join(", ")}`, step.id, step.step);
66
+ }
67
+ return {
68
+ inputs: [await directoryEntry(root)],
69
+ outputs: [],
70
+ verified: true,
71
+ extras: { checked: walked.files.length, missing: [] },
72
+ };
73
+ }
74
+ export async function runZipStep(step, ctx) {
75
+ const from = resolveSourceOrStagePath(step.from, ctx);
76
+ const out = resolveOutDirPath(step.name, ctx);
77
+ if (ctx.mode === "dry-run") {
78
+ return { inputs: [], outputs: [], verified: true, plan: `zip ${from} -> ${out}` };
79
+ }
80
+ if (!(await isDirectory(from))) {
81
+ throw new BuildStepError(`zip: source tree not found: ${from}`, step.id, step.step);
82
+ }
83
+ const walked = await walkWhole(from, step);
84
+ const entries = walked.files
85
+ .map((rel) => ({ name: toPosix(rel), abs: path.join(from, rel) }))
86
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
87
+ await fs.mkdir(path.dirname(out), { recursive: true });
88
+ await writeZipFile(out, entries.map((entry) => ({ name: entry.name, read: () => fs.readFile(entry.abs) })));
89
+ const stat = await fs.stat(out);
90
+ if (stat.size === 0)
91
+ throw new BuildStepError(`zip: wrote an empty archive: ${out}`, step.id, step.step);
92
+ const outputs = await pathEntries([out]);
93
+ return {
94
+ inputs: [await directoryEntry(from)],
95
+ outputs,
96
+ verified: true,
97
+ extras: { entryCount: entries.length, sha256: outputs[0]?.sha256 },
98
+ ...(walked.links && walked.links.length > 0
99
+ ? { warnings: [`zip: ${walked.links.length} symbolic link(s)/junction(s) under ${from} were not followed and are not in the archive: ${walked.links.slice(0, 10).join(", ")}`] }
100
+ : {}),
101
+ };
102
+ }
103
+ export function looksLikePath(raw) {
104
+ return raw.includes("${stage}") || raw.includes("/") || raw.includes("\\") || path.isAbsolute(raw);
105
+ }
106
+ function resolveArg(raw, ctx) {
107
+ return looksLikePath(raw) ? resolveSourceOrStagePath(raw, ctx) : raw;
108
+ }
109
+ async function resolveCommandLocation(step, ctx) {
110
+ const label = `exec:${step.id ?? step.command}`;
111
+ if (!looksLikePath(step.command)) {
112
+ try {
113
+ const located = await ctx.locateTool(step.command);
114
+ if (located.found && located.path)
115
+ return { ...located, id: "exec", label };
116
+ const spec = TOOL_SPECS.find((s) => s.id === step.command);
117
+ if (spec && ctx.mode !== "dry-run") {
118
+ throw new BuildStepError(`${label}: "${step.command}" is a toolchain tool the locator could not find${located.hint ? ` (${located.hint})` : ""}. ` +
119
+ `Point at it with "toolchain": { "${step.command}": "<path to the executable>" } in modwright.json (a project can carry one), ` +
120
+ `or the ${spec.envVar} environment variable, or give the step a path-shaped command.`, step.id, step.step);
121
+ }
122
+ }
123
+ catch (err) {
124
+ if (err instanceof BuildStepError)
125
+ throw err;
126
+ }
127
+ return { id: "exec", label, found: true, path: step.command, usedBy: [] };
128
+ }
129
+ return { id: "exec", label, found: true, path: resolveSourceOrStagePath(step.command, ctx), usedBy: [] };
130
+ }
131
+ export const EXEC_DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
132
+ export async function runExecStep(step, ctx) {
133
+ const location = await resolveCommandLocation(step, ctx);
134
+ const command = location.path;
135
+ const cwd = step.cwd ? resolveSourceOrStagePath(step.cwd, ctx) : ctx.projectRoot;
136
+ const args = step.args.map((a) => resolveArg(a, ctx));
137
+ const expectOutputs = step.expectOutputs.map((p) => resolveSourceOrStagePath(p, ctx));
138
+ if (ctx.mode === "dry-run") {
139
+ return {
140
+ inputs: [],
141
+ outputs: [],
142
+ verified: false,
143
+ plan: `run ${[...(location.interpreter ?? []), command, ...args].join(" ")} (cwd ${cwd})`,
144
+ extras: { command, args, cwd, interpreter: location.interpreter },
145
+ };
146
+ }
147
+ for (const out of expectOutputs)
148
+ await fs.mkdir(path.dirname(out), { recursive: true });
149
+ const result = await runTool(location, { args, cwd, expectOutputs, timeoutMs: step.timeoutMs ?? EXEC_DEFAULT_TIMEOUT_MS });
150
+ if (!result.ok) {
151
+ const output = failureOutput(result, cwd);
152
+ const said = lastLine(output.stderr) ?? lastLine(output.stdout);
153
+ throw new BuildStepError(`exec step failed: ${result.error?.message ?? `exit ${result.exitCode}`}${said ? ` Last output: ${said}` : ""}`, step.id, step.step, output);
154
+ }
155
+ const warnings = (result.staleOutputs ?? []).map((p) => `${p} was not rewritten by this run (same size and mtime as before it); if the command should always produce it, it is stale from an earlier build.`);
156
+ return {
157
+ inputs: [],
158
+ outputs: await pathEntries(expectOutputs),
159
+ verified: false,
160
+ extras: { command, args, cwd, exitCode: result.exitCode, ...(result.envFilled ? { envFilled: result.envFilled } : {}) },
161
+ ...(warnings.length > 0 ? { warnings } : {}),
162
+ };
163
+ }
164
+ function tail(text, cap) {
165
+ if (text.length <= cap)
166
+ return { text, truncated: false };
167
+ return { text: `[...earlier output truncated]\n${text.slice(text.length - cap)}`, truncated: true };
168
+ }
169
+ function lastLine(text) {
170
+ const line = text
171
+ .split(/\r?\n/)
172
+ .map((l) => l.trim())
173
+ .filter((l) => l.length > 0)
174
+ .pop();
175
+ if (!line)
176
+ return undefined;
177
+ return line.length > 300 ? `${line.slice(0, 300)}...` : line;
178
+ }
179
+ function failureOutput(result, cwd) {
180
+ const stdout = tail(result.stdout, STEP_FAILURE_OUTPUT_CAP);
181
+ const stderr = tail(result.stderr, STEP_FAILURE_OUTPUT_CAP);
182
+ return {
183
+ exitCode: result.exitCode,
184
+ errorCode: result.error?.code,
185
+ command: result.command,
186
+ args: result.args,
187
+ cwd,
188
+ stdout: stdout.text,
189
+ stderr: stderr.text,
190
+ stdoutTruncated: stdout.truncated || result.stdoutTruncated,
191
+ stderrTruncated: stderr.truncated || result.stderrTruncated,
192
+ missingOutputs: result.missingOutputs,
193
+ ...(result.envFilled ? { envFilled: result.envFilled } : {}),
194
+ };
195
+ }
@@ -0,0 +1,95 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { pathExists, walk } from "../../fsutil.js";
4
+ import { runTool } from "../../toolchain/run.js";
5
+ import { fileEntry, pathEntries } from "../manifest.js";
6
+ import { resolvedToolNote, resolveOutDirPath, resolveSourceOrStagePath } from "../paths.js";
7
+ import { applyListingRules } from "./listing.js";
8
+ import { BuildStepError } from "../types.js";
9
+ export async function runBlenderExportStep(step, ctx) {
10
+ const blend = resolveSourceOrStagePath(step.blend, ctx);
11
+ const script = resolveSourceOrStagePath(step.script, ctx);
12
+ const out = resolveSourceOrStagePath(step.out, ctx);
13
+ const location = await ctx.locateTool("blender");
14
+ const args = [blend, "--background", "--python", script, "--", out];
15
+ if (ctx.mode === "dry-run") {
16
+ return { inputs: [], outputs: [], verified: true, plan: `blender ${args.join(" ")}${resolvedToolNote(location)}` };
17
+ }
18
+ await fs.mkdir(path.dirname(out), { recursive: true });
19
+ const result = await runTool(location, { args, expectOutputs: [out], requireFreshOutputs: true });
20
+ if (!result.ok) {
21
+ throw new BuildStepError(`blender-export failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
22
+ }
23
+ return {
24
+ inputs: [await fileEntry(blend), await fileEntry(script)],
25
+ outputs: await pathEntries([out]),
26
+ verified: true,
27
+ tool: "blender",
28
+ toolVersion: location.version,
29
+ };
30
+ }
31
+ export async function runWolvenkitImportStep(step, ctx) {
32
+ const glb = resolveSourceOrStagePath(step.glb, ctx);
33
+ const base = resolveSourceOrStagePath(step.base, ctx);
34
+ const outDir = resolveSourceOrStagePath(step.outDir, ctx);
35
+ const outputMesh = path.join(outDir, path.basename(base));
36
+ const location = await ctx.locateTool("wolvenkit-cli");
37
+ const args = ["import", glb, "-o", outDir, "-k"];
38
+ if (ctx.mode === "dry-run") {
39
+ return { inputs: [], outputs: [], verified: true, plan: `wolvenkit-cli ${args.join(" ")}${resolvedToolNote(location)}` };
40
+ }
41
+ const preMtimeMs = (await pathExists(outputMesh)) ? (await fs.stat(outputMesh)).mtimeMs : undefined;
42
+ await fs.mkdir(outDir, { recursive: true });
43
+ const result = await runTool(location, { args, expectOutputs: [outputMesh] });
44
+ if (!result.ok) {
45
+ throw new BuildStepError(`wolvenkit-import failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
46
+ }
47
+ const postMtimeMs = (await fs.stat(outputMesh)).mtimeMs;
48
+ if (preMtimeMs !== undefined && postMtimeMs === preMtimeMs) {
49
+ throw new BuildStepError(`wolvenkit-import: ${outputMesh} mtime unchanged after import — it likely wrote nothing despite exiting 0`, step.id, step.step);
50
+ }
51
+ return {
52
+ inputs: [await fileEntry(glb)],
53
+ outputs: await pathEntries([outputMesh]),
54
+ verified: true,
55
+ tool: "wolvenkit-cli",
56
+ toolVersion: location.version,
57
+ extras: { keepBase: true, baseMeshSha256: (await fileEntry(outputMesh)).sha256 },
58
+ };
59
+ }
60
+ export async function runWolvenkitPackStep(step, ctx) {
61
+ const from = resolveSourceOrStagePath(step.from, ctx);
62
+ const out = resolveOutDirPath(step.out, ctx);
63
+ const location = await ctx.locateTool("wolvenkit-cli");
64
+ const args = ["pack", from, "-o", path.dirname(out), "-v", step.verbosity];
65
+ if (ctx.mode === "dry-run") {
66
+ return { inputs: [], outputs: [], verified: true, plan: `wolvenkit-cli ${args.join(" ")}${resolvedToolNote(location)}` };
67
+ }
68
+ await fs.mkdir(path.dirname(out), { recursive: true });
69
+ const result = await runTool(location, { args, expectOutputs: [out], requireFreshOutputs: true });
70
+ if (!result.ok) {
71
+ throw new BuildStepError(`wolvenkit-pack failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
72
+ }
73
+ const walked = await walk(from, { limit: 200_000 });
74
+ const outputEntry = await fileEntry(out);
75
+ return {
76
+ inputs: [],
77
+ outputs: [outputEntry],
78
+ verified: true,
79
+ tool: "wolvenkit-cli",
80
+ toolVersion: location.version,
81
+ extras: { archiveSha256: outputEntry.sha256, entryCount: walked.files.length },
82
+ };
83
+ }
84
+ export async function runVerifyArchiveListingStep(step, ctx, of) {
85
+ const location = await ctx.locateTool("wolvenkit-cli");
86
+ const args = ["archive", of, "-l"];
87
+ if (ctx.mode === "dry-run") {
88
+ return { inputs: [], outputs: [], verified: true, plan: `wolvenkit-cli ${args.join(" ")}${resolvedToolNote(location)}` };
89
+ }
90
+ const result = await runTool(location, { args });
91
+ if (!result.ok) {
92
+ throw new BuildStepError(`verify-listing failed: ${result.error?.message ?? `exit ${result.exitCode}`}`, step.id, step.step);
93
+ }
94
+ return applyListingRules(step, of, result.stdout, "wolvenkit-cli", location.version);
95
+ }
@@ -0,0 +1,35 @@
1
+ import { globToRegExp } from "../../validate/context.js";
2
+ import { pathEntries } from "../manifest.js";
3
+ import { BuildStepError } from "../types.js";
4
+ export async function applyListingRules(step, of, stdout, tool, toolVersion, options = {}) {
5
+ const listing = stdout
6
+ .split(/\r?\n/)
7
+ .map((line) => (options.firstTabField ? (line.split("\t")[0] ?? line) : line))
8
+ .map((line) => line.trim())
9
+ .filter((line) => line.length > 0);
10
+ const deny = [];
11
+ const exceptions = [];
12
+ for (const raw of step.forbid ?? []) {
13
+ if (raw.startsWith("!"))
14
+ exceptions.push(globToRegExp(raw.slice(1)));
15
+ else
16
+ deny.push(globToRegExp(raw));
17
+ }
18
+ const matched = listing.filter((entry) => deny.some((re) => re.test(entry)) && !exceptions.some((re) => re.test(entry)));
19
+ const requirePatterns = (step.require ?? []).map(globToRegExp);
20
+ const unmetRequire = (step.require ?? []).filter((_, i) => !listing.some((entry) => requirePatterns[i].test(entry)));
21
+ if (matched.length > 0) {
22
+ throw new BuildStepError(`verify-listing: forbidden ${matched.length === 1 ? "entry" : "entries"} in ${of}: ${matched.join(", ")}`, step.id, step.step);
23
+ }
24
+ if (unmetRequire.length > 0) {
25
+ throw new BuildStepError(`verify-listing: required pattern(s) missing from ${of}: ${unmetRequire.join(", ")}`, step.id, step.step);
26
+ }
27
+ return {
28
+ inputs: await pathEntries([of]),
29
+ outputs: [],
30
+ verified: true,
31
+ tool,
32
+ toolVersion,
33
+ extras: { entryCount: listing.length, matched: [] },
34
+ };
35
+ }
@@ -0,0 +1,13 @@
1
+ export const STEP_FAILURE_OUTPUT_CAP = 8_000;
2
+ export class BuildStepError extends Error {
3
+ stepId;
4
+ stepKind;
5
+ output;
6
+ constructor(message, stepId, stepKind, output) {
7
+ super(message);
8
+ this.stepId = stepId;
9
+ this.stepKind = stepKind;
10
+ this.output = output;
11
+ this.name = "BuildStepError";
12
+ }
13
+ }
@@ -0,0 +1,148 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { promisify } from "node:util";
3
+ import { deflateRaw, deflateRawSync } from "node:zlib";
4
+ const deflateRawAsync = promisify(deflateRaw);
5
+ const CRC_TABLE = buildCrcTable();
6
+ function buildCrcTable() {
7
+ const table = new Uint32Array(256);
8
+ for (let n = 0; n < 256; n++) {
9
+ let c = n;
10
+ for (let k = 0; k < 8; k++) {
11
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
12
+ }
13
+ table[n] = c >>> 0;
14
+ }
15
+ return table;
16
+ }
17
+ export function crc32(data) {
18
+ let crc = 0xffffffff;
19
+ for (let i = 0; i < data.length; i++) {
20
+ crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
21
+ }
22
+ return (crc ^ 0xffffffff) >>> 0;
23
+ }
24
+ export const FIXED_DOS_TIME = 0;
25
+ export const FIXED_DOS_DATE = 0x21;
26
+ const VERSION_NEEDED = 20;
27
+ const VERSION_MADE_BY = 0x0314;
28
+ const FLAG_UTF8 = 0x0800;
29
+ const METHOD_DEFLATE = 8;
30
+ const METHOD_STORE = 0;
31
+ function u16(n) {
32
+ const b = Buffer.alloc(2);
33
+ b.writeUInt16LE(n & 0xffff, 0);
34
+ return b;
35
+ }
36
+ function u32(n) {
37
+ const b = Buffer.alloc(4);
38
+ b.writeUInt32LE(n >>> 0, 0);
39
+ return b;
40
+ }
41
+ const MAX_ZIP32 = 0xffffffff;
42
+ const MAX_ZIP_ENTRIES = 0xffff;
43
+ export function createZip(entries) {
44
+ assertEntryCount(entries.length);
45
+ const localParts = [];
46
+ const centralParts = [];
47
+ let offset = 0;
48
+ for (const entry of entries) {
49
+ const records = entryRecords(entry.name, entry.data, deflateRawSync(entry.data), offset);
50
+ localParts.push(records.local);
51
+ centralParts.push(records.central);
52
+ offset += records.local.length;
53
+ }
54
+ const centralDir = Buffer.concat(centralParts);
55
+ return Buffer.concat([...localParts, centralDir, endOfCentralDirectory(entries.length, centralDir.length, offset)]);
56
+ }
57
+ export async function writeZipFile(outPath, entries) {
58
+ assertEntryCount(entries.length);
59
+ const tmp = `${outPath}.${process.pid}.${Date.now()}.tmp`;
60
+ const handle = await fs.open(tmp, "w");
61
+ try {
62
+ const centralParts = [];
63
+ let offset = 0;
64
+ for (const entry of entries) {
65
+ const data = await entry.read();
66
+ const records = entryRecords(entry.name, data, await deflateRawAsync(data), offset);
67
+ await handle.write(records.local);
68
+ centralParts.push(records.central);
69
+ offset += records.local.length;
70
+ }
71
+ const centralDir = Buffer.concat(centralParts);
72
+ await handle.write(centralDir);
73
+ await handle.write(endOfCentralDirectory(entries.length, centralDir.length, offset));
74
+ await handle.close();
75
+ await fs.rename(tmp, outPath);
76
+ }
77
+ catch (err) {
78
+ await handle.close().catch(() => { });
79
+ await fs.rm(tmp, { force: true }).catch(() => { });
80
+ throw err;
81
+ }
82
+ }
83
+ function assertEntryCount(count) {
84
+ if (count > MAX_ZIP_ENTRIES) {
85
+ throw new Error(`zip: ${count} entries exceed the ${MAX_ZIP_ENTRIES} a non-ZIP64 archive can hold`);
86
+ }
87
+ }
88
+ function entryRecords(name, data, deflated, offset) {
89
+ const nameBuf = Buffer.from(name.replace(/\\/g, "/"), "utf8");
90
+ if (data.length > MAX_ZIP32 || nameBuf.length > 0xffff) {
91
+ throw new Error(`zip: ${name} is ${data.length} bytes, past the 4 GiB a non-ZIP64 entry can hold`);
92
+ }
93
+ if (offset > MAX_ZIP32) {
94
+ throw new Error(`zip: the archive would pass 4 GiB, which needs ZIP64 records this writer does not emit`);
95
+ }
96
+ const crc = crc32(data);
97
+ const useStore = deflated.length >= data.length;
98
+ const method = useStore ? METHOD_STORE : METHOD_DEFLATE;
99
+ const payload = useStore ? data : deflated;
100
+ const localHeader = Buffer.concat([
101
+ u32(0x04034b50),
102
+ u16(VERSION_NEEDED),
103
+ u16(FLAG_UTF8),
104
+ u16(method),
105
+ u16(FIXED_DOS_TIME),
106
+ u16(FIXED_DOS_DATE),
107
+ u32(crc),
108
+ u32(payload.length),
109
+ u32(data.length),
110
+ u16(nameBuf.length),
111
+ u16(0),
112
+ ]);
113
+ const centralHeader = Buffer.concat([
114
+ u32(0x02014b50),
115
+ u16(VERSION_MADE_BY),
116
+ u16(VERSION_NEEDED),
117
+ u16(FLAG_UTF8),
118
+ u16(method),
119
+ u16(FIXED_DOS_TIME),
120
+ u16(FIXED_DOS_DATE),
121
+ u32(crc),
122
+ u32(payload.length),
123
+ u32(data.length),
124
+ u16(nameBuf.length),
125
+ u16(0),
126
+ u16(0),
127
+ u16(0),
128
+ u16(0),
129
+ u32(0),
130
+ u32(offset),
131
+ ]);
132
+ return { local: Buffer.concat([localHeader, nameBuf, payload]), central: Buffer.concat([centralHeader, nameBuf]) };
133
+ }
134
+ function endOfCentralDirectory(count, centralSize, centralOffset) {
135
+ if (centralOffset > MAX_ZIP32 || centralSize > MAX_ZIP32) {
136
+ throw new Error(`zip: the archive would pass 4 GiB, which needs ZIP64 records this writer does not emit`);
137
+ }
138
+ return Buffer.concat([
139
+ u32(0x06054b50),
140
+ u16(0),
141
+ u16(0),
142
+ u16(count),
143
+ u16(count),
144
+ u32(centralSize),
145
+ u32(centralOffset),
146
+ u16(0),
147
+ ]);
148
+ }