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,342 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { pathExists, walk } from "../fsutil.js";
4
+ import { normalizeBuildSteps } from "../project/build-steps.js";
5
+ import { locateTool as coreLocateTool, TOOL_SPECS } from "../toolchain/index.js";
6
+ import { buildContext, runValidators } from "../validate/index.js";
7
+ import { runAssertAbsentStep, runAssertPresentStep, runExecStep, runZipStep } from "./steps/core.js";
8
+ import { runBlenderExportStep, runVerifyArchiveListingStep, runWolvenkitImportStep, runWolvenkitPackStep, } from "./steps/cyberpunk.js";
9
+ import { runDivineCreatePackageStep, runDivineLocaStep, runDivineLsxToLsfStep, runVerifyPackageListingStep, } from "./steps/bg3.js";
10
+ import { resolveOutDirPath, resolveSourceOrStagePath, stageStepOutputDir } from "./paths.js";
11
+ import { runStageStep } from "./stage.js";
12
+ import { BuildStepError } from "./types.js";
13
+ function buildStamp() {
14
+ return new Date().toISOString().replace(/[:.]/g, "-");
15
+ }
16
+ function defaultLocateTool(project, mode) {
17
+ const overrides = project.project.toolchain;
18
+ return async (id) => {
19
+ const spec = TOOL_SPECS.find((s) => s.id === id);
20
+ if (!spec) {
21
+ return {
22
+ id,
23
+ label: id,
24
+ found: false,
25
+ usedBy: [],
26
+ hint: `"${id}" is not a known tool id in ModWright's toolchain catalog (see core/toolchain/specs.ts).`,
27
+ };
28
+ }
29
+ return coreLocateTool(spec, { projectOverrides: overrides, probe: mode === "apply" ? "all" : "none" });
30
+ };
31
+ }
32
+ async function buildSourceMap(project) {
33
+ const map = new Map();
34
+ for (const s of project.sources) {
35
+ let exists = false;
36
+ let isFile = false;
37
+ try {
38
+ isFile = (await fs.stat(s.absPath)).isFile();
39
+ exists = true;
40
+ }
41
+ catch {
42
+ exists = false;
43
+ }
44
+ map.set(s.id, { id: s.id, kind: s.kind, path: s.path, absPath: s.absPath, exists, isFile });
45
+ }
46
+ return map;
47
+ }
48
+ function resolveVariant(project, variantName) {
49
+ if (!variantName)
50
+ return undefined;
51
+ const variant = project.project.build?.variants?.[variantName];
52
+ if (!variant) {
53
+ const known = Object.keys(project.project.build?.variants ?? {});
54
+ throw new Error(`Unknown build variant "${variantName}". Known: ${known.length > 0 ? known.join(", ") : "(none declared)"}`);
55
+ }
56
+ return variant;
57
+ }
58
+ function applyVariant(steps, variant) {
59
+ if (!variant)
60
+ return steps;
61
+ const withAddedSources = steps.map((step) => {
62
+ if (step.step !== "stage" || !variant.addSources || variant.addSources.length === 0)
63
+ return step;
64
+ return { ...step, sources: [...step.sources, ...variant.addSources] };
65
+ });
66
+ return [...withAddedSources, ...(variant.addSteps ?? [])];
67
+ }
68
+ function stepName({ step, stepIndex }) {
69
+ return step.id ?? `${step.step}-${stepIndex}`;
70
+ }
71
+ function gateByVariant(steps, variantName) {
72
+ const withIndex = steps.map((step, stepIndex) => ({ step, stepIndex }));
73
+ const runsHere = ({ step }) => !step.variant || step.variant === variantName;
74
+ return { active: withIndex.filter(runsHere), gatedOut: withIndex.filter((s) => !runsHere(s)) };
75
+ }
76
+ function selectSteps(withIndex, only) {
77
+ if (only && only.length > 0) {
78
+ const onlySet = new Set(only);
79
+ const selected = withIndex.filter(({ step }) => step.id !== undefined && onlySet.has(step.id));
80
+ return { selected, skippedOptional: [] };
81
+ }
82
+ const selected = withIndex.filter(({ step }) => !step.optional);
83
+ const skippedOptional = withIndex.filter(({ step }) => step.optional).map(stepName);
84
+ return { selected, skippedOptional };
85
+ }
86
+ async function runOneStep(step, ctx) {
87
+ switch (step.step) {
88
+ case "stage":
89
+ return runStageStep(step, ctx);
90
+ case "assert-absent":
91
+ return runAssertAbsentStep(step, ctx);
92
+ case "assert-present":
93
+ return runAssertPresentStep(step, ctx);
94
+ case "zip":
95
+ return runZipStep(step, ctx);
96
+ case "exec":
97
+ return runExecStep(step, ctx);
98
+ case "blender-export":
99
+ return runBlenderExportStep(step, ctx);
100
+ case "wolvenkit-import":
101
+ return runWolvenkitImportStep(step, ctx);
102
+ case "wolvenkit-pack":
103
+ return runWolvenkitPackStep(step, ctx);
104
+ case "divine-lsx-to-lsf":
105
+ return runDivineLsxToLsfStep(step, ctx);
106
+ case "divine-loca":
107
+ return runDivineLocaStep(step, ctx);
108
+ case "divine-create-package":
109
+ return runDivineCreatePackageStep(step, ctx);
110
+ case "verify-listing": {
111
+ const of = resolveOutDirPath(step.of, ctx);
112
+ return of.toLowerCase().endsWith(".pak")
113
+ ? runVerifyPackageListingStep(step, ctx, of)
114
+ : runVerifyArchiveListingStep(step, ctx, of);
115
+ }
116
+ default: {
117
+ const exhaustive = step;
118
+ throw new Error(`build: unhandled step kind "${exhaustive.step}"`);
119
+ }
120
+ }
121
+ }
122
+ function declaredOutputs(step, ctx) {
123
+ switch (step.step) {
124
+ case "stage":
125
+ return [stageStepOutputDir(ctx.stageRoot, step.id ?? `stage-${ctx.stepIndex}`, step.into)];
126
+ case "wolvenkit-pack":
127
+ case "divine-create-package":
128
+ return [resolveOutDirPath(step.out, ctx)];
129
+ case "zip":
130
+ return [resolveOutDirPath(step.name, ctx)];
131
+ case "blender-export":
132
+ return [resolveSourceOrStagePath(step.out, ctx)];
133
+ case "exec":
134
+ return step.expectOutputs.map((p) => resolveSourceOrStagePath(p, ctx));
135
+ default:
136
+ return [];
137
+ }
138
+ }
139
+ function stepLabel(step, stepIndex) {
140
+ return step.id ? `${step.step} (${step.id})` : `${step.step}#${stepIndex}`;
141
+ }
142
+ const EQUIVALENCE_BY_SURFACE = {
143
+ cyberpunk2077: "listing-plus-content",
144
+ baldursgate3: "byte-identical",
145
+ };
146
+ export async function runBuild(project, surface, options) {
147
+ if (!project.build) {
148
+ throw new Error(`${project.project.name}: project has no "build" configuration — nothing to build.`);
149
+ }
150
+ const startedAt = new Date().toISOString();
151
+ const variant = resolveVariant(project, options.variant);
152
+ const baseSteps = normalizeBuildSteps(project.project);
153
+ const steps = applyVariant(baseSteps, variant);
154
+ const { active, gatedOut } = gateByVariant(steps, options.variant);
155
+ if (options.only && options.only.length > 0) {
156
+ const activeIds = new Set(active.map(({ step }) => step.id).filter(Boolean));
157
+ const gatedOnly = options.only.filter((id) => !activeIds.has(id) && gatedOut.some(({ step }) => step.id === id));
158
+ if (gatedOnly.length > 0) {
159
+ const needs = gatedOnly.map((id) => {
160
+ const gates = [...new Set(gatedOut.filter(({ step }) => step.id === id).map(({ step }) => step.variant))];
161
+ return `"${id}" runs only under variant ${gates.map((g) => `"${g}"`).join(" or ")}`;
162
+ });
163
+ throw new Error(`--only named step(s) the ${options.variant ? `"${options.variant}" variant` : "plain build (no variant)"} gates out: ${needs.join("; ")}. Pass that variant to run them.`);
164
+ }
165
+ }
166
+ const { selected, skippedOptional } = selectSteps(active, options.only);
167
+ if (options.only && options.only.length > 0 && selected.length === 0) {
168
+ throw new Error(`--only named no matching step id(s): ${options.only.join(", ")}. Known ids: ${active.map(({ step }) => step.id).filter(Boolean).join(", ") || "(none declared)"}`);
169
+ }
170
+ const skippedVariant = gatedOut.map(stepName);
171
+ const version = options.version ?? project.project.version ?? "0.0.0";
172
+ const stagingDir = project.build.staging
173
+ ? path.resolve(project.root, project.build.staging)
174
+ : path.join(project.root, ".modwright", "cache", "build");
175
+ const stageRoot = path.join(stagingDir, buildStamp());
176
+ const outDirAbs = project.build.absOutDir;
177
+ const locateTool = options.locateTool ?? defaultLocateTool(project, options.mode);
178
+ const projectExclude = project.build.exclude ?? [];
179
+ const dropSet = new Set(variant?.dropExclude ?? []);
180
+ const defaultExclude = projectExclude.filter((g) => !dropSet.has(g));
181
+ if (options.mode === "apply") {
182
+ await fs.mkdir(outDirAbs, { recursive: true });
183
+ }
184
+ const producedOutputs = [];
185
+ const plannedExecOutputs = [];
186
+ const manifestSteps = [];
187
+ const dryRunPlan = [];
188
+ let cheapGateRan = false;
189
+ let validated = false;
190
+ let failed = false;
191
+ let failureReason;
192
+ let failedStep;
193
+ const runCheapGate = async () => {
194
+ cheapGateRan = true;
195
+ if (options.noValidate || !options.validatorRegistry)
196
+ return undefined;
197
+ const stagedFiles = (await pathExists(stageRoot))
198
+ ? (await walk(stageRoot, { limit: 200_000 })).files.map((f) => path.join(stageRoot, f))
199
+ : [];
200
+ const vctx = buildContext({
201
+ project,
202
+ surface,
203
+ install: options.install,
204
+ store: options.store,
205
+ listFilesOverride: stagedFiles,
206
+ });
207
+ const report = await runValidators(options.validatorRegistry, vctx, { cheapOnly: true });
208
+ if (report.blocking) {
209
+ const blockers = report.findings.filter((f) => f.severity === "block" && !f.suppressedBy);
210
+ return `cheap-validate gate refused the build: ${blockers.map((f) => `${f.message}`).join("; ")}`;
211
+ }
212
+ validated = true;
213
+ return undefined;
214
+ };
215
+ for (const { step, stepIndex } of selected) {
216
+ const sources = await buildSourceMap(project);
217
+ const ctx = {
218
+ mode: options.mode,
219
+ projectRoot: project.root,
220
+ stageRoot,
221
+ outDirAbs,
222
+ version,
223
+ sources,
224
+ defaultExclude,
225
+ variant: options.variant,
226
+ producedOutputs,
227
+ ...(options.mode === "dry-run" ? { plannedExecOutputs } : {}),
228
+ locateTool,
229
+ stepIndex,
230
+ };
231
+ const isPackagingStep = step.step === "wolvenkit-pack" || step.step === "divine-create-package" || step.step === "zip";
232
+ if (options.mode === "apply" && isPackagingStep && !cheapGateRan) {
233
+ const refusal = await runCheapGate();
234
+ if (refusal) {
235
+ failed = true;
236
+ failureReason = refusal;
237
+ break;
238
+ }
239
+ }
240
+ const t0 = Date.now();
241
+ let result;
242
+ try {
243
+ result = await runOneStep(step, ctx);
244
+ }
245
+ catch (err) {
246
+ failed = true;
247
+ failureReason =
248
+ err instanceof BuildStepError
249
+ ? `${stepLabel(step, stepIndex)}: ${err.message}`
250
+ : `${stepLabel(step, stepIndex)}: ${err.message}`;
251
+ failedStep = {
252
+ step: step.step,
253
+ id: step.id,
254
+ label: stepLabel(step, stepIndex),
255
+ ...(err instanceof BuildStepError && err.output ? { output: err.output } : {}),
256
+ };
257
+ break;
258
+ }
259
+ const durationMs = Date.now() - t0;
260
+ producedOutputs.push(...(result.outputs.length > 0 ? result.outputs.map((o) => o.path) : declaredOutputs(step, ctx)));
261
+ if (options.mode === "dry-run") {
262
+ if (step.step === "exec")
263
+ plannedExecOutputs.push(...declaredOutputs(step, ctx));
264
+ dryRunPlan.push({ step: step.step, id: step.id, optional: step.optional, description: result.plan ?? "(no plan produced)" });
265
+ }
266
+ else {
267
+ manifestSteps.push({
268
+ step: step.step,
269
+ id: step.id,
270
+ tool: result.tool,
271
+ toolVersion: result.toolVersion,
272
+ inputs: result.inputs,
273
+ outputs: result.outputs,
274
+ durationMs,
275
+ verified: result.verified,
276
+ optional: step.optional,
277
+ extras: result.extras,
278
+ warnings: result.warnings,
279
+ });
280
+ }
281
+ }
282
+ if (!failed && options.mode === "apply" && !cheapGateRan) {
283
+ const refusal = await runCheapGate();
284
+ if (refusal) {
285
+ failed = true;
286
+ failureReason = refusal;
287
+ }
288
+ }
289
+ if (failed) {
290
+ return {
291
+ mode: options.mode,
292
+ project: project.project.name,
293
+ variant: options.variant,
294
+ stageRoot,
295
+ failed: true,
296
+ failureReason,
297
+ ...(failedStep ? { failedStep } : {}),
298
+ skippedOptional,
299
+ ...(options.mode === "dry-run" ? { skippedVariant } : {}),
300
+ outDirAbs,
301
+ };
302
+ }
303
+ if (options.mode === "dry-run") {
304
+ return {
305
+ mode: "dry-run",
306
+ project: project.project.name,
307
+ variant: options.variant,
308
+ stageRoot,
309
+ plan: dryRunPlan,
310
+ failed: false,
311
+ skippedOptional,
312
+ skippedVariant,
313
+ outDirAbs,
314
+ };
315
+ }
316
+ const manifest = {
317
+ project: project.project.name,
318
+ stageRoot,
319
+ variant: options.variant,
320
+ validated,
321
+ equivalence: EQUIVALENCE_BY_SURFACE[surface.id] ?? null,
322
+ startedAt,
323
+ finishedAt: new Date().toISOString(),
324
+ steps: manifestSteps,
325
+ };
326
+ const manifestPath = path.join(outDirAbs, ".modwright-build-manifest.json");
327
+ await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
328
+ if (!options.keepStage) {
329
+ await fs.rm(stageRoot, { recursive: true, force: true });
330
+ }
331
+ return {
332
+ mode: "apply",
333
+ project: project.project.name,
334
+ variant: options.variant,
335
+ stageRoot,
336
+ manifest,
337
+ manifestPath,
338
+ failed: false,
339
+ skippedOptional,
340
+ outDirAbs,
341
+ };
342
+ }
@@ -0,0 +1,265 @@
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, isExcludedByGlobs } from "../validate/context.js";
5
+ import { directoryEntry } from "./manifest.js";
6
+ import { resolveOutDirPath, stageStepOutputDir } from "./paths.js";
7
+ import { BuildStepError } from "./types.js";
8
+ function toPosix(p) {
9
+ return p.split(path.sep).join("/");
10
+ }
11
+ function isWithin(parent, child) {
12
+ const rel = path.relative(parent, child);
13
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
14
+ }
15
+ function isUnderModwright(abs, projectRoot) {
16
+ return isWithin(path.join(projectRoot, ".modwright"), abs);
17
+ }
18
+ function stagingGuardDir(ctx) {
19
+ const stagingDir = path.dirname(ctx.stageRoot);
20
+ const proper = stagingDir !== ctx.projectRoot && isWithin(ctx.projectRoot, stagingDir);
21
+ return proper ? stagingDir : ctx.stageRoot;
22
+ }
23
+ export function isGuardedPath(abs, projectRoot, outDirAbs) {
24
+ return isUnderModwright(abs, projectRoot) || isWithin(outDirAbs, abs);
25
+ }
26
+ function asEntry(raw) {
27
+ return typeof raw === "string" ? { source: raw } : raw;
28
+ }
29
+ function describeEntry(entry) {
30
+ return entry.output !== undefined ? `output "${entry.output}"` : `source "${entry.source}"`;
31
+ }
32
+ function normalizePrefix(raw) {
33
+ return toPosix(raw)
34
+ .split("/")
35
+ .filter((segment) => segment.length > 0 && segment !== ".")
36
+ .join("/");
37
+ }
38
+ function defaultPrefixForSource(source, ctx) {
39
+ const declared = source.path && !path.isAbsolute(source.path) ? source.path : path.relative(ctx.projectRoot, source.absPath);
40
+ const rel = normalizePrefix(declared);
41
+ if (rel === "" || rel.startsWith("../") || rel === ".." || path.isAbsolute(rel)) {
42
+ return source.isFile ? "" : path.basename(source.absPath);
43
+ }
44
+ return source.isFile ? normalizePrefix(path.posix.dirname(rel)) : rel;
45
+ }
46
+ function joinDest(prefix, rel) {
47
+ return prefix === "" ? rel : `${prefix}/${rel}`;
48
+ }
49
+ function resolveOutputEntry(entry, step, ctx) {
50
+ const abs = resolveOutDirPath(entry.output, ctx);
51
+ const known = ctx.producedOutputs.some((p) => path.resolve(p) === path.resolve(abs));
52
+ if (!known) {
53
+ throw new BuildStepError(`stage step's output "${entry.output}" was not produced by any earlier step in this build ` +
54
+ `(produced so far: ${ctx.producedOutputs.length > 0 ? ctx.producedOutputs.map((p) => path.basename(p)).join(", ") : "nothing"}). ` +
55
+ `An "output" entry may only name an artifact an earlier step of the same run wrote.`, step.id, step.step);
56
+ }
57
+ return abs;
58
+ }
59
+ function plannedFilesUnder(source, ctx) {
60
+ if (ctx.mode !== "dry-run" || source.exists)
61
+ return [];
62
+ return (ctx.plannedExecOutputs ?? []).filter((p) => isWithin(source.absPath, p));
63
+ }
64
+ async function planFiles(step, ctx) {
65
+ const files = [];
66
+ const plannedSources = [];
67
+ const skippedLinks = [];
68
+ const walkAll = async (dir) => {
69
+ const walked = await walk(dir, { limit: 200_000, skipDirs: [".modwright"], stat: false });
70
+ if (walked.truncated) {
71
+ throw new BuildStepError(`stage: ${dir} holds more than 200000 files; the listing was cut short, so nothing was staged`, step.id, step.step);
72
+ }
73
+ skippedLinks.push(...(walked.links ?? []).map((rel) => path.join(dir, rel)));
74
+ return walked;
75
+ };
76
+ const claimed = new Map();
77
+ let excludedCount = 0;
78
+ const matchedGlobs = new Set();
79
+ for (const raw of step.sources) {
80
+ const entry = asEntry(raw);
81
+ if (entry.variant && entry.variant !== ctx.variant)
82
+ continue;
83
+ const excludeGlobs = [...ctx.defaultExclude, ...(step.exclude ?? []), ...(entry.exclude ?? [])];
84
+ const excludeRes = excludeGlobs.map(globToRegExp);
85
+ const includeRes = (entry.include ?? []).map(globToRegExp);
86
+ const origin = describeEntry(entry);
87
+ const isExcluded = (relSource, abs) => {
88
+ const relProject = abs ? toPosix(path.relative(ctx.projectRoot, abs)) : undefined;
89
+ for (let i = 0; i < excludeRes.length; i++) {
90
+ if (isExcludedByGlobs([excludeRes[i]], relSource, relProject)) {
91
+ matchedGlobs.add(excludeGlobs[i]);
92
+ return true;
93
+ }
94
+ }
95
+ return false;
96
+ };
97
+ const isIncluded = (relForMatch) => includeRes.length === 0 || includeRes.some((re) => re.test(relForMatch));
98
+ const add = (abs, relDest) => {
99
+ const existing = claimed.get(relDest);
100
+ if (existing !== undefined && existing !== origin) {
101
+ throw new BuildStepError(`stage: ${existing} and ${origin} both write "${relDest}" — one would silently overlay the other. ` +
102
+ `Give one of them its own "into" prefix.`, step.id, step.step);
103
+ }
104
+ claimed.set(relDest, origin);
105
+ files.push({ abs, relDest, origin });
106
+ };
107
+ if (entry.output !== undefined) {
108
+ const abs = resolveOutputEntry(entry, step, ctx);
109
+ const prefix = normalizePrefix(entry.into ?? "");
110
+ let stat;
111
+ try {
112
+ stat = await fs.stat(abs);
113
+ }
114
+ catch {
115
+ stat = undefined;
116
+ }
117
+ if (!stat) {
118
+ if (ctx.mode === "dry-run") {
119
+ add(abs, joinDest(prefix, path.basename(abs)));
120
+ continue;
121
+ }
122
+ throw new BuildStepError(`stage step's output "${entry.output}" is missing on disk (${abs})`, step.id, step.step);
123
+ }
124
+ if (stat.isDirectory()) {
125
+ const walked = await walkAll(abs);
126
+ for (const rel of walked.files) {
127
+ const relPosix = toPosix(rel);
128
+ if (!isIncluded(relPosix))
129
+ continue;
130
+ if (isExcluded(relPosix, path.join(abs, rel))) {
131
+ excludedCount++;
132
+ continue;
133
+ }
134
+ add(path.join(abs, rel), joinDest(prefix, relPosix));
135
+ }
136
+ }
137
+ else {
138
+ const base = path.basename(abs);
139
+ if (isIncluded(base) && !isExcluded(base, abs))
140
+ add(abs, joinDest(prefix, base));
141
+ else
142
+ excludedCount += isIncluded(base) ? 1 : 0;
143
+ }
144
+ continue;
145
+ }
146
+ const source = ctx.sources.get(entry.source);
147
+ if (!source)
148
+ continue;
149
+ if (isUnderModwright(source.absPath, ctx.projectRoot) || source.absPath === ctx.outDirAbs)
150
+ continue;
151
+ if (isWithin(stagingGuardDir(ctx), source.absPath))
152
+ continue;
153
+ if (!source.exists) {
154
+ const planned = plannedFilesUnder(source, ctx);
155
+ if (planned.length === 0)
156
+ continue;
157
+ plannedSources.push(entry.source);
158
+ const prefix = normalizePrefix(entry.into ?? (step.into ? "" : defaultPrefixForSource(source, ctx)));
159
+ for (const abs of planned) {
160
+ const relPosix = abs === source.absPath ? path.basename(abs) : toPosix(path.relative(source.absPath, abs));
161
+ if (!isIncluded(relPosix))
162
+ continue;
163
+ if (isExcluded(relPosix, abs)) {
164
+ excludedCount++;
165
+ continue;
166
+ }
167
+ add(abs, joinDest(prefix, relPosix));
168
+ }
169
+ continue;
170
+ }
171
+ const prefix = normalizePrefix(entry.into ?? (step.into ? "" : defaultPrefixForSource(source, ctx)));
172
+ const outDirIsInsideThisSource = isWithin(source.absPath, ctx.outDirAbs);
173
+ if (source.isFile) {
174
+ const rel = path.basename(source.absPath);
175
+ if (!isIncluded(rel))
176
+ continue;
177
+ if (isExcluded(rel, source.absPath)) {
178
+ excludedCount++;
179
+ continue;
180
+ }
181
+ add(source.absPath, joinDest(prefix, rel));
182
+ continue;
183
+ }
184
+ const walked = await walkAll(source.absPath);
185
+ for (const rel of walked.files) {
186
+ const abs = path.join(source.absPath, rel);
187
+ if (isUnderModwright(abs, ctx.projectRoot) || isWithin(stagingGuardDir(ctx), abs))
188
+ continue;
189
+ if (outDirIsInsideThisSource && isWithin(ctx.outDirAbs, abs))
190
+ continue;
191
+ const relPosix = toPosix(rel);
192
+ if (!isIncluded(relPosix))
193
+ continue;
194
+ if (isExcluded(relPosix, abs)) {
195
+ excludedCount++;
196
+ continue;
197
+ }
198
+ add(abs, joinDest(prefix, relPosix));
199
+ }
200
+ }
201
+ return { files, excludedCount, matchedGlobs, skippedLinks, plannedSources };
202
+ }
203
+ export async function runStageStep(step, ctx) {
204
+ const warnings = [];
205
+ const entries = step.sources.map(asEntry).filter((entry) => !entry.variant || entry.variant === ctx.variant);
206
+ const sourceIds = entries.filter((entry) => entry.source !== undefined).map((entry) => entry.source);
207
+ for (const id of sourceIds) {
208
+ if (!ctx.sources.has(id)) {
209
+ throw new BuildStepError(`stage step references unknown source id "${id}"`, step.id, step.step);
210
+ }
211
+ }
212
+ for (const id of sourceIds) {
213
+ const source = ctx.sources.get(id);
214
+ if (!source.exists && plannedFilesUnder(source, ctx).length === 0) {
215
+ throw new BuildStepError(`stage step's source "${id}" does not exist on disk (${source.absPath})`, step.id, step.step);
216
+ }
217
+ }
218
+ const { files, excludedCount, matchedGlobs, skippedLinks, plannedSources } = await planFiles(step, ctx);
219
+ if (skippedLinks.length > 0) {
220
+ warnings.push(`${skippedLinks.length} symbolic link(s)/junction(s) under the staged sources were not followed and not staged: ${skippedLinks.slice(0, 10).join(", ")}${skippedLinks.length > 10 ? ", ..." : ""}. Copy the real files in if they must ship.`);
221
+ }
222
+ const declaredGlobs = [
223
+ ...ctx.defaultExclude,
224
+ ...(step.exclude ?? []),
225
+ ...entries.flatMap((entry) => entry.exclude ?? []),
226
+ ];
227
+ for (const glob of new Set(declaredGlobs)) {
228
+ if (!matchedGlobs.has(glob)) {
229
+ warnings.push(`exclude glob "${glob}" matched nothing — a stale exclusion is how a dev file returns`);
230
+ }
231
+ }
232
+ const id = step.id ?? `stage-${ctx.stepIndex}`;
233
+ const destRoot = stageStepOutputDir(ctx.stageRoot, id, step.into);
234
+ const label = entries.map((entry) => entry.output ?? entry.source).join(", ");
235
+ if (files.length === 0) {
236
+ throw new BuildStepError(`stage step staged zero files from [${label}] — check excludes and source paths`, step.id, step.step);
237
+ }
238
+ if (ctx.mode === "dry-run") {
239
+ return {
240
+ inputs: [],
241
+ outputs: [],
242
+ verified: true,
243
+ warnings,
244
+ extras: { stageRoot: destRoot, fileCount: files.length, excludedCount, excluded: [...matchedGlobs] },
245
+ plan: `stage ${files.length} file(s) from [${label}] into ${destRoot}` +
246
+ (plannedSources.length > 0
247
+ ? `; ${plannedSources.map((id) => `"${id}"`).join(", ")} not on disk yet: planned from the files an earlier exec step declares in expectOutputs (the full tree is known once it runs)`
248
+ : ""),
249
+ };
250
+ }
251
+ await fs.mkdir(destRoot, { recursive: true });
252
+ for (const file of files) {
253
+ const dest = path.join(destRoot, ...file.relDest.split("/"));
254
+ await fs.mkdir(path.dirname(dest), { recursive: true });
255
+ await fs.copyFile(file.abs, dest);
256
+ }
257
+ const outputEntry = (await isDirectory(destRoot)) ? await directoryEntry(destRoot) : undefined;
258
+ return {
259
+ inputs: [],
260
+ outputs: outputEntry ? [outputEntry] : [],
261
+ verified: true,
262
+ warnings,
263
+ extras: { stageRoot: destRoot, fileCount: files.length, excludedCount, excluded: [...matchedGlobs] },
264
+ };
265
+ }