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.
- package/LICENSE +35 -0
- package/README.md +212 -0
- package/assets/claude/agents/mw-explore.md +46 -0
- package/assets/claude/agents/mw-fixture.md +33 -0
- package/assets/claude/agents/mw-rule-author.md +51 -0
- package/assets/claude/agents/mw-scaffold-author.md +48 -0
- package/assets/claude/skills/mw-author.md +59 -0
- package/assets/claude/skills/mw-new-item.md +51 -0
- package/assets/claude/skills/mw-new-spell.md +50 -0
- package/assets/claude/skills/mw-scaffold.md +59 -0
- package/assets/claude/skills/mw-ship.md +57 -0
- package/assets/claude/skills/mw-validate.md +51 -0
- package/assets/claude/skills/mw-verify.md +59 -0
- package/assets/nivalisnights/extract_resources.py +177 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
- package/bridges/bg3-se/README.md +99 -0
- package/bridges/bg3-se/bridge.json +68 -0
- package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
- package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
- package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
- package/bridges/cp2077-cet/README.md +165 -0
- package/bridges/cp2077-cet/bridge.json +53 -0
- package/dist/core/bridge/arm.js +175 -0
- package/dist/core/bridge/cet-console.js +26 -0
- package/dist/core/bridge/cet-log.js +362 -0
- package/dist/core/bridge/index.js +11 -0
- package/dist/core/bridge/locate.js +35 -0
- package/dist/core/bridge/paths.js +34 -0
- package/dist/core/bridge/pending.js +89 -0
- package/dist/core/bridge/poll.js +155 -0
- package/dist/core/bridge/stage.js +100 -0
- package/dist/core/bridge/status.js +97 -0
- package/dist/core/bridge/types.js +145 -0
- package/dist/core/bridge/validate.js +107 -0
- package/dist/core/build/index.js +6 -0
- package/dist/core/build/manifest.js +47 -0
- package/dist/core/build/paths.js +32 -0
- package/dist/core/build/run.js +342 -0
- package/dist/core/build/stage.js +265 -0
- package/dist/core/build/steps/bg3.js +129 -0
- package/dist/core/build/steps/core.js +195 -0
- package/dist/core/build/steps/cyberpunk.js +95 -0
- package/dist/core/build/steps/listing.js +35 -0
- package/dist/core/build/types.js +13 -0
- package/dist/core/build/zip.js +148 -0
- package/dist/core/compat/check.js +113 -0
- package/dist/core/compat/pe.js +220 -0
- package/dist/core/compat/types.js +1 -0
- package/dist/core/compat/version.js +70 -0
- package/dist/core/deploy/guards.js +292 -0
- package/dist/core/deploy/index.js +5 -0
- package/dist/core/deploy/plan.js +696 -0
- package/dist/core/deploy/reload-apply.js +361 -0
- package/dist/core/deploy/reload.js +358 -0
- package/dist/core/deploy/sources.js +169 -0
- package/dist/core/deploy/types.js +1 -0
- package/dist/core/epic.js +54 -0
- package/dist/core/fsutil.js +191 -0
- package/dist/core/home.js +70 -0
- package/dist/core/knowledge/facts.js +229 -0
- package/dist/core/knowledge/index.js +4 -0
- package/dist/core/knowledge/search.js +112 -0
- package/dist/core/knowledge/store.js +123 -0
- package/dist/core/knowledge/types.js +1 -0
- package/dist/core/ledger/check.js +42 -0
- package/dist/core/ledger/hooks.js +80 -0
- package/dist/core/ledger/index.js +7 -0
- package/dist/core/ledger/query.js +17 -0
- package/dist/core/ledger/record.js +84 -0
- package/dist/core/ledger/status.js +53 -0
- package/dist/core/ledger/store.js +98 -0
- package/dist/core/ledger/types.js +108 -0
- package/dist/core/logs/index.js +4 -0
- package/dist/core/logs/registry.js +50 -0
- package/dist/core/logs/scan.js +195 -0
- package/dist/core/logs/triage.js +160 -0
- package/dist/core/logs/types.js +1 -0
- package/dist/core/lsx.js +10 -0
- package/dist/core/lz4.js +66 -0
- package/dist/core/mods.js +229 -0
- package/dist/core/process.js +49 -0
- package/dist/core/project/build-steps.js +83 -0
- package/dist/core/project/describe.js +74 -0
- package/dist/core/project/index.js +5 -0
- package/dist/core/project/load.js +146 -0
- package/dist/core/project/schema.js +264 -0
- package/dist/core/project/types.js +3 -0
- package/dist/core/prompts.js +72 -0
- package/dist/core/registry.js +35 -0
- package/dist/core/safety/backup.js +206 -0
- package/dist/core/safety/execute.js +188 -0
- package/dist/core/safety/index.js +5 -0
- package/dist/core/safety/plan.js +122 -0
- package/dist/core/safety/restore.js +172 -0
- package/dist/core/safety/types.js +1 -0
- package/dist/core/scaffold/bootstrap.js +7 -0
- package/dist/core/scaffold/catalog.js +28 -0
- package/dist/core/scaffold/claude-assets.js +271 -0
- package/dist/core/scaffold/collisions.js +64 -0
- package/dist/core/scaffold/emit.js +356 -0
- package/dist/core/scaffold/engine.js +680 -0
- package/dist/core/scaffold/ids.js +78 -0
- package/dist/core/scaffold/index.js +10 -0
- package/dist/core/scaffold/jsonpath.js +121 -0
- package/dist/core/scaffold/load.js +81 -0
- package/dist/core/scaffold/render.js +85 -0
- package/dist/core/scaffold/sequence.js +396 -0
- package/dist/core/scaffold/types.js +309 -0
- package/dist/core/steam.js +63 -0
- package/dist/core/surfaceutil.js +36 -0
- package/dist/core/testplan/check.js +67 -0
- package/dist/core/testplan/index.js +5 -0
- package/dist/core/testplan/load.js +100 -0
- package/dist/core/testplan/registry.js +317 -0
- package/dist/core/testplan/run.js +389 -0
- package/dist/core/testplan/types.js +82 -0
- package/dist/core/text/converters/bg3.js +50 -0
- package/dist/core/text/converters/cyberpunk.js +36 -0
- package/dist/core/text/converters/index.js +261 -0
- package/dist/core/text/diff/cr2w.js +180 -0
- package/dist/core/text/diff/index.js +16 -0
- package/dist/core/text/diff/loca.js +43 -0
- package/dist/core/text/diff/lsx.js +126 -0
- package/dist/core/text/handles.js +86 -0
- package/dist/core/text/index.js +6 -0
- package/dist/core/text/json.js +46 -0
- package/dist/core/text/templates/cache.js +71 -0
- package/dist/core/text/templates/index.js +3 -0
- package/dist/core/text/templates/library.js +245 -0
- package/dist/core/text/templates/types.js +51 -0
- package/dist/core/text/toolchain.js +16 -0
- package/dist/core/text/types.js +1 -0
- package/dist/core/text/validate.js +149 -0
- package/dist/core/text/xml.js +58 -0
- package/dist/core/toolchain/datafetch.js +151 -0
- package/dist/core/toolchain/env.js +63 -0
- package/dist/core/toolchain/features.js +170 -0
- package/dist/core/toolchain/index.js +8 -0
- package/dist/core/toolchain/install.js +191 -0
- package/dist/core/toolchain/locate.js +275 -0
- package/dist/core/toolchain/remember.js +84 -0
- package/dist/core/toolchain/run.js +318 -0
- package/dist/core/toolchain/specs.js +351 -0
- package/dist/core/toolchain/types.js +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/userconfig.js +66 -0
- package/dist/core/validate/context.js +139 -0
- package/dist/core/validate/index.js +5 -0
- package/dist/core/validate/registry.js +58 -0
- package/dist/core/validate/run.js +131 -0
- package/dist/core/validate/suppressions.js +91 -0
- package/dist/core/validate/types.js +2 -0
- package/dist/core/yamledit.js +51 -0
- package/dist/index.js +389 -0
- package/dist/server.js +1914 -0
- package/dist/surfaces/baldursgate3/compat.js +164 -0
- package/dist/surfaces/baldursgate3/identity.js +110 -0
- package/dist/surfaces/baldursgate3/index/build.js +331 -0
- package/dist/surfaces/baldursgate3/index/index.js +17 -0
- package/dist/surfaces/baldursgate3/index/info.js +40 -0
- package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
- package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
- package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
- package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
- package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
- package/dist/surfaces/baldursgate3/index/queries.js +327 -0
- package/dist/surfaces/baldursgate3/index/schema.js +62 -0
- package/dist/surfaces/baldursgate3/index.js +3 -0
- package/dist/surfaces/baldursgate3/logs.js +278 -0
- package/dist/surfaces/baldursgate3/modsettings.js +115 -0
- package/dist/surfaces/baldursgate3/pak.js +153 -0
- package/dist/surfaces/baldursgate3/settings.js +49 -0
- package/dist/surfaces/baldursgate3/surface.js +232 -0
- package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
- package/dist/surfaces/baldursgate3/validators/files.js +476 -0
- package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
- package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
- package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
- package/dist/surfaces/baldursgate3/validators/story.js +790 -0
- package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/compat.js +223 -0
- package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
- package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
- package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
- package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
- package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
- package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
- package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
- package/dist/surfaces/cyberpunk2077/index.js +3 -0
- package/dist/surfaces/cyberpunk2077/logs.js +742 -0
- package/dist/surfaces/cyberpunk2077/paths.js +12 -0
- package/dist/surfaces/cyberpunk2077/surface.js +218 -0
- package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
- package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
- package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
- package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
- package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
- package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
- package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
- package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
- package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
- package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
- package/dist/surfaces/eldenring/binders.js +111 -0
- package/dist/surfaces/eldenring/compat.js +166 -0
- package/dist/surfaces/eldenring/dcx.js +49 -0
- package/dist/surfaces/eldenring/index.js +13 -0
- package/dist/surfaces/eldenring/loaders.js +132 -0
- package/dist/surfaces/eldenring/logs.js +67 -0
- package/dist/surfaces/eldenring/paramdef.js +256 -0
- package/dist/surfaces/eldenring/params.js +129 -0
- package/dist/surfaces/eldenring/surface.js +121 -0
- package/dist/surfaces/eldenring/tools.js +52 -0
- package/dist/surfaces/eldenring/validators/binders.js +193 -0
- package/dist/surfaces/eldenring/validators/packages.js +159 -0
- package/dist/surfaces/eldenring/validators/params.js +334 -0
- package/dist/surfaces/eldenring/validators/profiles.js +287 -0
- package/dist/surfaces/index.js +34 -0
- package/dist/surfaces/nivalisnights/compat.js +229 -0
- package/dist/surfaces/nivalisnights/index/build.js +310 -0
- package/dist/surfaces/nivalisnights/index/index.js +5 -0
- package/dist/surfaces/nivalisnights/index/queries.js +231 -0
- package/dist/surfaces/nivalisnights/index.js +4 -0
- package/dist/surfaces/nivalisnights/logs.js +473 -0
- package/dist/surfaces/nivalisnights/surface.js +195 -0
- package/dist/surfaces/skyrimse/archives.js +229 -0
- package/dist/surfaces/skyrimse/compat.js +163 -0
- package/dist/surfaces/skyrimse/index.js +12 -0
- package/dist/surfaces/skyrimse/loadorder.js +174 -0
- package/dist/surfaces/skyrimse/logs.js +241 -0
- package/dist/surfaces/skyrimse/plugins.js +149 -0
- package/dist/surfaces/skyrimse/records.js +94 -0
- package/dist/surfaces/skyrimse/surface.js +240 -0
- package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
- package/dist/surfaces/skyrimse/validators/archives.js +110 -0
- package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
- package/dist/surfaces/skyrimse/validators/records.js +117 -0
- package/dist/surfaces/stardewvalley/compat.js +190 -0
- package/dist/surfaces/stardewvalley/config.js +42 -0
- package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
- package/dist/surfaces/stardewvalley/index/index.js +229 -0
- package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
- package/dist/surfaces/stardewvalley/index.js +12 -0
- package/dist/surfaces/stardewvalley/logs.js +307 -0
- package/dist/surfaces/stardewvalley/mods.js +182 -0
- package/dist/surfaces/stardewvalley/surface.js +244 -0
- package/dist/surfaces/stardewvalley/validators/content.js +321 -0
- package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
- package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
- package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
- package/dist/surfaces/subnautica2/compat.js +138 -0
- package/dist/surfaces/subnautica2/index.js +5 -0
- package/dist/surfaces/subnautica2/logs.js +129 -0
- package/dist/surfaces/subnautica2/paks.js +173 -0
- package/dist/surfaces/subnautica2/surface.js +151 -0
- package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
- package/dist/surfaces/valheim/compat.js +171 -0
- package/dist/surfaces/valheim/index/index.js +301 -0
- package/dist/surfaces/valheim/index/tables.js +103 -0
- package/dist/surfaces/valheim/index.js +11 -0
- package/dist/surfaces/valheim/logs.js +197 -0
- package/dist/surfaces/valheim/packaging.js +131 -0
- package/dist/surfaces/valheim/plugins.js +270 -0
- package/dist/surfaces/valheim/references.js +121 -0
- package/dist/surfaces/valheim/surface.js +223 -0
- package/dist/surfaces/valheim/validators/packaging.js +218 -0
- package/dist/surfaces/valheim/validators/plugins.js +287 -0
- package/dist/surfaces/valheim/validators/references.js +169 -0
- package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
- package/knowledge/baldursgate3/audio.voice.yaml +182 -0
- package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
- package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
- package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
- package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
- package/knowledge/baldursgate3/crash.causes.yaml +90 -0
- package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
- package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
- package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
- package/knowledge/baldursgate3/dialogue.yaml +122 -0
- package/knowledge/baldursgate3/feats.classes.yaml +164 -0
- package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
- package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
- package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
- package/knowledge/baldursgate3/journal.quests.yaml +214 -0
- package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
- package/knowledge/baldursgate3/localization.handles.yaml +92 -0
- package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
- package/knowledge/baldursgate3/origins.companions.yaml +144 -0
- package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
- package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
- package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
- package/knowledge/baldursgate3/process.launch.yaml +34 -0
- package/knowledge/baldursgate3/progressions.yaml +92 -0
- package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
- package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
- package/knowledge/baldursgate3/races.progression.yaml +228 -0
- package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
- package/knowledge/baldursgate3/scriptextender.yaml +252 -0
- package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
- package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
- package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
- package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
- package/knowledge/baldursgate3/stats.spells.yaml +293 -0
- package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
- package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
- package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
- package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
- package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
- package/knowledge/baldursgate3/ui.icons.yaml +412 -0
- package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
- package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
- package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
- package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
- package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
- package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
- package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
- package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
- package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
- package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
- package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
- package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
- package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
- package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
- package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
- package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
- package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
- package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
- package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
- package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
- package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
- package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
- package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
- package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
- package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
- package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
- package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
- package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
- package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
- package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
- package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
- package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
- package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
- package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
- package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
- package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
- package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
- package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
- package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
- package/knowledge/cyberpunk2077/player.control.yaml +918 -0
- package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
- package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
- package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
- package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
- package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
- package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
- package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
- package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
- package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
- package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
- package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
- package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
- package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
- package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
- package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
- package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
- package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
- package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
- package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
- package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
- package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
- package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
- package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
- package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
- package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
- package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
- package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
- package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
- package/knowledge/eldenring/formats.binders.yaml +237 -0
- package/knowledge/eldenring/launcher.me3.yaml +230 -0
- package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
- package/knowledge/eldenring/params.paramdef.yaml +149 -0
- package/knowledge/eldenring/params.regulation.yaml +142 -0
- package/knowledge/eldenring/saves.online.yaml +118 -0
- package/knowledge/eldenring/toolchain.cli.yaml +256 -0
- package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
- package/knowledge/skyrimse/archives.bsa.yaml +212 -0
- package/knowledge/skyrimse/install.detection.yaml +41 -0
- package/knowledge/skyrimse/loadorder.files.yaml +242 -0
- package/knowledge/skyrimse/plugins.esl.yaml +189 -0
- package/knowledge/skyrimse/plugins.format.yaml +224 -0
- package/knowledge/skyrimse/skse.runtime.yaml +283 -0
- package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
- package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
- package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
- package/knowledge/stardewvalley/game.versions.yaml +202 -0
- package/knowledge/stardewvalley/install.layout.yaml +277 -0
- package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
- package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
- package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
- package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
- package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
- package/knowledge/subnautica2/game.build.yaml +331 -0
- package/knowledge/subnautica2/modding.state.yaml +210 -0
- package/knowledge/subnautica2/ue5.containers.yaml +72 -0
- package/knowledge/subnautica2/ue5.paks.yaml +203 -0
- package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
- package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
- package/knowledge/valheim/bepinex.loader.yaml +247 -0
- package/knowledge/valheim/bepinex.plugins.yaml +251 -0
- package/knowledge/valheim/game.versions.yaml +321 -0
- package/knowledge/valheim/install.layout.yaml +216 -0
- package/knowledge/valheim/jotunn.library.yaml +334 -0
- package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
- package/knowledge/valheim/toolchain.build.yaml +111 -0
- package/package.json +29 -0
- package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
- package/scaffolds/baldursgate3/new-item.yaml +264 -0
- package/scaffolds/baldursgate3/new-mod.yaml +174 -0
- package/scaffolds/baldursgate3/new-passive.yaml +100 -0
- package/scaffolds/baldursgate3/new-project.yaml +118 -0
- package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
- package/scaffolds/baldursgate3/new-spell.yaml +102 -0
- package/scaffolds/baldursgate3/new-status.yaml +119 -0
- package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
- package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
- package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
- package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
- package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
- package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
- package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
- package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
- package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
- package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
- package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
- package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
- package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
- package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
- package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
- package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
- package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
- package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
- package/scaffolds/stardewvalley/new-project.yaml +184 -0
- package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
- package/scripts/mw-tool.mjs +184 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const TAG_RE = /<([a-zA-Z][\w:-]*)((?:\s+[\w:-]+\s*=\s*"[^"]*")*)\s*(\/?)>|<\/([a-zA-Z][\w:-]*)\s*>/g;
|
|
2
|
+
const ATTR_RE = /([\w:-]+)\s*=\s*"([^"]*)"/g;
|
|
3
|
+
function decodeEntities(s) {
|
|
4
|
+
return s
|
|
5
|
+
.replace(/</g, "<")
|
|
6
|
+
.replace(/>/g, ">")
|
|
7
|
+
.replace(/"/g, '"')
|
|
8
|
+
.replace(/'/g, "'")
|
|
9
|
+
.replace(/&/g, "&");
|
|
10
|
+
}
|
|
11
|
+
function parseAttrs(raw) {
|
|
12
|
+
const attrs = {};
|
|
13
|
+
let m;
|
|
14
|
+
ATTR_RE.lastIndex = 0;
|
|
15
|
+
while ((m = ATTR_RE.exec(raw))) {
|
|
16
|
+
attrs[m[1]] = decodeEntities(m[2]);
|
|
17
|
+
}
|
|
18
|
+
return attrs;
|
|
19
|
+
}
|
|
20
|
+
export function parseXml(xml) {
|
|
21
|
+
const cleaned = xml
|
|
22
|
+
.replace(/^/, "")
|
|
23
|
+
.replace(/<\?xml[^?]*\?>/, "")
|
|
24
|
+
.replace(/<!--[\s\S]*?-->/g, "");
|
|
25
|
+
const root = { tag: "#document", attrs: {}, children: [], text: "" };
|
|
26
|
+
const stack = [root];
|
|
27
|
+
let lastIndex = 0;
|
|
28
|
+
let m;
|
|
29
|
+
TAG_RE.lastIndex = 0;
|
|
30
|
+
while ((m = TAG_RE.exec(cleaned))) {
|
|
31
|
+
const between = cleaned.slice(lastIndex, m.index);
|
|
32
|
+
if (between.trim().length > 0) {
|
|
33
|
+
stack[stack.length - 1].text += decodeEntities(between);
|
|
34
|
+
}
|
|
35
|
+
lastIndex = TAG_RE.lastIndex;
|
|
36
|
+
const [, openTag, attrRaw, selfClose, closeTag] = m;
|
|
37
|
+
if (closeTag) {
|
|
38
|
+
if (stack.length > 1)
|
|
39
|
+
stack.pop();
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const el = { tag: openTag, attrs: parseAttrs(attrRaw ?? ""), children: [], text: "" };
|
|
43
|
+
stack[stack.length - 1].children.push(el);
|
|
44
|
+
if (!selfClose)
|
|
45
|
+
stack.push(el);
|
|
46
|
+
}
|
|
47
|
+
return root.children[0] ?? root;
|
|
48
|
+
}
|
|
49
|
+
export function findAll(root, tag) {
|
|
50
|
+
const out = [];
|
|
51
|
+
(function walk(el) {
|
|
52
|
+
if (el.tag === tag)
|
|
53
|
+
out.push(el);
|
|
54
|
+
for (const child of el.children)
|
|
55
|
+
walk(child);
|
|
56
|
+
})(root);
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { executePlan } from "../safety/index.js";
|
|
4
|
+
import { userDataDir } from "../home.js";
|
|
5
|
+
import { planDataMemory } from "./remember.js";
|
|
6
|
+
export function defaultDataFolder(id) {
|
|
7
|
+
return path.join(userDataDir(), "data", id);
|
|
8
|
+
}
|
|
9
|
+
export function planDataFetch(dataId, known, destination) {
|
|
10
|
+
const need = known.find((k) => k.id === dataId);
|
|
11
|
+
if (!need)
|
|
12
|
+
return { ok: false, error: `Unknown data folder "${dataId}". Known: ${known.map((k) => k.id).join(", ") || "none"}.` };
|
|
13
|
+
if (!need.fetch) {
|
|
14
|
+
return { ok: false, error: `ModWright cannot fetch ${need.id} (${need.what}); point at it once with check_toolchain action=remember data=${need.id} dataPath=<folder>.` };
|
|
15
|
+
}
|
|
16
|
+
if (destination !== undefined && !path.isAbsolute(destination)) {
|
|
17
|
+
return { ok: false, error: `dataPath must be absolute ("${destination}" is relative).` };
|
|
18
|
+
}
|
|
19
|
+
const spec = need.fetch;
|
|
20
|
+
const origins = [...new Set(spec.files.map((f) => new URL(f.url).origin))];
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
plan: {
|
|
24
|
+
data: need.id,
|
|
25
|
+
destination: destination ?? defaultDataFolder(need.id),
|
|
26
|
+
source: spec.source,
|
|
27
|
+
license: spec.license,
|
|
28
|
+
delayMs: spec.delayMs,
|
|
29
|
+
userAgent: spec.userAgent,
|
|
30
|
+
robotsToken: spec.robotsToken,
|
|
31
|
+
files: spec.files,
|
|
32
|
+
robots: origins.map((o) => `${o}/robots.txt`),
|
|
33
|
+
estimatedSeconds: Math.ceil((spec.files.length * (spec.delayMs + 500)) / 1000),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function robotsRules(text, token) {
|
|
38
|
+
const groups = [];
|
|
39
|
+
let current;
|
|
40
|
+
let lastWasAgent = false;
|
|
41
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
42
|
+
const line = raw.replace(/#.*/, "").trim();
|
|
43
|
+
const m = /^([A-Za-z-]+)\s*:\s*(.*)$/.exec(line);
|
|
44
|
+
if (!m)
|
|
45
|
+
continue;
|
|
46
|
+
const key = (m[1] ?? "").toLowerCase();
|
|
47
|
+
const value = (m[2] ?? "").trim();
|
|
48
|
+
if (key === "user-agent") {
|
|
49
|
+
if (!current || !lastWasAgent) {
|
|
50
|
+
current = { agents: [], rules: [] };
|
|
51
|
+
groups.push(current);
|
|
52
|
+
}
|
|
53
|
+
current.agents.push(value.toLowerCase());
|
|
54
|
+
lastWasAgent = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
lastWasAgent = false;
|
|
58
|
+
if (!current)
|
|
59
|
+
continue;
|
|
60
|
+
if ((key === "allow" || key === "disallow") && value !== "")
|
|
61
|
+
current.rules.push({ allow: key === "allow", pattern: value });
|
|
62
|
+
}
|
|
63
|
+
const t = token.toLowerCase();
|
|
64
|
+
const named = groups.filter((g) => g.agents.some((a) => a !== "*" && t.includes(a)));
|
|
65
|
+
const chosen = named.length > 0 ? named : groups.filter((g) => g.agents.includes("*"));
|
|
66
|
+
return chosen.flatMap((g) => g.rules);
|
|
67
|
+
}
|
|
68
|
+
function patternMatches(pattern, target) {
|
|
69
|
+
const anchored = pattern.endsWith("$");
|
|
70
|
+
const body = anchored ? pattern.slice(0, -1) : pattern;
|
|
71
|
+
const re = new RegExp("^" + body.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*") + (anchored ? "$" : ""));
|
|
72
|
+
return re.test(target);
|
|
73
|
+
}
|
|
74
|
+
export function robotsAllows(rules, pathAndQuery) {
|
|
75
|
+
let best;
|
|
76
|
+
for (const rule of rules) {
|
|
77
|
+
if (!patternMatches(rule.pattern, pathAndQuery))
|
|
78
|
+
continue;
|
|
79
|
+
if (!best || rule.pattern.length > best.pattern.length || (rule.pattern.length === best.pattern.length && rule.allow))
|
|
80
|
+
best = rule;
|
|
81
|
+
}
|
|
82
|
+
return best ? best.allow : true;
|
|
83
|
+
}
|
|
84
|
+
export async function applyDataFetch(plan, ctx = {}) {
|
|
85
|
+
const fetchImpl = ctx.fetchImpl ?? fetch;
|
|
86
|
+
const delayMs = ctx.delayMs ?? plan.delayMs;
|
|
87
|
+
const headers = { "User-Agent": plan.userAgent };
|
|
88
|
+
const rulesByOrigin = new Map();
|
|
89
|
+
for (const robotsUrl of plan.robots) {
|
|
90
|
+
const origin = new URL(robotsUrl).origin;
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetchImpl(robotsUrl, { headers });
|
|
93
|
+
if (res.status === 404)
|
|
94
|
+
rulesByOrigin.set(origin, []);
|
|
95
|
+
else if (!res.ok)
|
|
96
|
+
rulesByOrigin.set(origin, undefined);
|
|
97
|
+
else
|
|
98
|
+
rulesByOrigin.set(origin, robotsRules(await res.text(), plan.robotsToken));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
rulesByOrigin.set(origin, undefined);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
await fs.mkdir(plan.destination, { recursive: true });
|
|
105
|
+
const saved = [];
|
|
106
|
+
const failed = [];
|
|
107
|
+
const refused = [];
|
|
108
|
+
let first = true;
|
|
109
|
+
for (const { url, file } of plan.files) {
|
|
110
|
+
const u = new URL(url);
|
|
111
|
+
const rules = rulesByOrigin.get(u.origin);
|
|
112
|
+
if (rules === undefined) {
|
|
113
|
+
refused.push(`${file}: ${u.origin}/robots.txt could not be read`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!robotsAllows(rules, u.pathname + u.search)) {
|
|
117
|
+
refused.push(`${file}: disallowed by ${u.origin}/robots.txt`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!first && delayMs > 0)
|
|
121
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
122
|
+
first = false;
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetchImpl(url, { headers: { ...headers, Accept: "text/html" }, redirect: "follow" });
|
|
125
|
+
if (!res.ok)
|
|
126
|
+
throw new Error(`HTTP ${res.status}`);
|
|
127
|
+
const body = await res.text();
|
|
128
|
+
const target = path.join(plan.destination, file);
|
|
129
|
+
const temp = `${target}.part`;
|
|
130
|
+
await fs.writeFile(temp, body, "utf8");
|
|
131
|
+
await fs.rename(temp, target);
|
|
132
|
+
saved.push(file);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
failed.push({ file, error: error.message });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
let remembered = false;
|
|
139
|
+
if (saved.length > 0) {
|
|
140
|
+
const known = ctx.known ?? [{ id: plan.data, envVar: "", what: plan.source }];
|
|
141
|
+
const memory = await planDataMemory("remember", plan.data, plan.destination, known, { file: ctx.configFile });
|
|
142
|
+
if (memory.ok) {
|
|
143
|
+
const report = await executePlan(memory.plan, {
|
|
144
|
+
mode: "apply",
|
|
145
|
+
confidence: { level: "high", assumptions: ["The per-user config is ModWright's own file."] },
|
|
146
|
+
});
|
|
147
|
+
remembered = report.applied;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { ok: true, destination: plan.destination, saved, failed, refused, remembered };
|
|
151
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
function lookup(env, name) {
|
|
4
|
+
const wanted = name.toLowerCase();
|
|
5
|
+
for (const [key, value] of Object.entries(env)) {
|
|
6
|
+
if (key.toLowerCase() === wanted && value !== undefined && value !== "")
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
function defaultHost() {
|
|
12
|
+
const arch = os.arch();
|
|
13
|
+
return {
|
|
14
|
+
is64Bit: arch === "x64" || arch === "arm64" || Boolean(process.env.PROCESSOR_ARCHITEW6432),
|
|
15
|
+
cpuCount: os.cpus().length,
|
|
16
|
+
hostname: os.hostname(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function missingWindowsEnv(env, host = defaultHost()) {
|
|
20
|
+
const win = path.win32;
|
|
21
|
+
const systemRootRaw = lookup(env, "SystemRoot") ?? lookup(env, "windir");
|
|
22
|
+
const systemDrive = lookup(env, "SystemDrive") ?? (systemRootRaw ? systemRootRaw.slice(0, 2) : "C:");
|
|
23
|
+
const systemRoot = systemRootRaw ?? win.join(`${systemDrive}\\`, "Windows");
|
|
24
|
+
const programFiles = lookup(env, "ProgramW6432") ?? lookup(env, "ProgramFiles") ?? win.join(`${systemDrive}\\`, "Program Files");
|
|
25
|
+
const programFilesX86 = win.join(`${systemDrive}\\`, "Program Files (x86)");
|
|
26
|
+
const programData = win.join(`${systemDrive}\\`, "ProgramData");
|
|
27
|
+
const wanted = [
|
|
28
|
+
["SystemDrive", systemDrive],
|
|
29
|
+
["SystemRoot", systemRoot],
|
|
30
|
+
["windir", systemRoot],
|
|
31
|
+
["ComSpec", win.join(systemRoot, "System32", "cmd.exe")],
|
|
32
|
+
["PATHEXT", ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC"],
|
|
33
|
+
["OS", "Windows_NT"],
|
|
34
|
+
["ProgramFiles", programFiles],
|
|
35
|
+
["CommonProgramFiles", win.join(programFiles, "Common Files")],
|
|
36
|
+
["ProgramFiles(x86)", host.is64Bit ? programFilesX86 : undefined],
|
|
37
|
+
["CommonProgramFiles(x86)", host.is64Bit ? win.join(programFilesX86, "Common Files") : undefined],
|
|
38
|
+
["ProgramW6432", host.is64Bit ? programFiles : undefined],
|
|
39
|
+
["CommonProgramW6432", host.is64Bit ? win.join(programFiles, "Common Files") : undefined],
|
|
40
|
+
["ProgramData", programData],
|
|
41
|
+
["ALLUSERSPROFILE", programData],
|
|
42
|
+
["PUBLIC", win.join(`${systemDrive}\\`, "Users", "Public")],
|
|
43
|
+
["TMP", lookup(env, "TEMP")],
|
|
44
|
+
["NUMBER_OF_PROCESSORS", host.cpuCount > 0 ? String(host.cpuCount) : undefined],
|
|
45
|
+
["COMPUTERNAME", host.hostname ? host.hostname.toUpperCase() : undefined],
|
|
46
|
+
];
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [name, value] of wanted) {
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
if (lookup(env, name) !== undefined)
|
|
52
|
+
continue;
|
|
53
|
+
out[name] = value;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
export function toolEnvironment(extra, base = process.env, platform = process.platform, host) {
|
|
58
|
+
const merged = { ...base, ...(extra ?? {}) };
|
|
59
|
+
if (platform !== "win32")
|
|
60
|
+
return { env: merged, filled: [] };
|
|
61
|
+
const missing = missingWindowsEnv(merged, host);
|
|
62
|
+
return { env: { ...merged, ...missing }, filled: Object.keys(missing) };
|
|
63
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { normalizeBuildSteps } from "../project/build-steps.js";
|
|
2
|
+
import { TOOL_SPECS } from "./specs.js";
|
|
3
|
+
export function toolsForStep(step) {
|
|
4
|
+
switch (step.step) {
|
|
5
|
+
case "blender-export":
|
|
6
|
+
return ["blender"];
|
|
7
|
+
case "wolvenkit-import":
|
|
8
|
+
case "wolvenkit-pack":
|
|
9
|
+
return ["wolvenkit-cli"];
|
|
10
|
+
case "divine-lsx-to-lsf":
|
|
11
|
+
case "divine-loca":
|
|
12
|
+
case "divine-create-package":
|
|
13
|
+
return ["divine"];
|
|
14
|
+
case "verify-listing":
|
|
15
|
+
return [step.of.toLowerCase().endsWith(".pak") ? "divine" : "wolvenkit-cli"];
|
|
16
|
+
case "exec":
|
|
17
|
+
return TOOL_SPECS.some((s) => s.id === step.command) ? [step.command] : [];
|
|
18
|
+
default:
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function toolsToLocate(surface, options = {}) {
|
|
23
|
+
const ids = new Set();
|
|
24
|
+
for (const spec of TOOL_SPECS)
|
|
25
|
+
if (spec.usedBy.includes(surface.id))
|
|
26
|
+
ids.add(spec.id);
|
|
27
|
+
for (const need of surface.toolNeeds ?? [])
|
|
28
|
+
for (const t of need.tools ?? [])
|
|
29
|
+
ids.add(t);
|
|
30
|
+
for (const v of surface.validators ?? [])
|
|
31
|
+
for (const t of v.needs?.toolchain ?? [])
|
|
32
|
+
ids.add(t);
|
|
33
|
+
if (options.project)
|
|
34
|
+
for (const step of normalizeBuildSteps(options.project))
|
|
35
|
+
for (const t of toolsForStep(step))
|
|
36
|
+
ids.add(t);
|
|
37
|
+
return [...ids];
|
|
38
|
+
}
|
|
39
|
+
function stateOf(tools, data = []) {
|
|
40
|
+
if (tools.some((t) => !t.found && !t.optional) || data.some((d) => !d.set))
|
|
41
|
+
return "blocked";
|
|
42
|
+
if (tools.some((t) => !t.found))
|
|
43
|
+
return "degraded";
|
|
44
|
+
return "ready";
|
|
45
|
+
}
|
|
46
|
+
export function featureReadiness(surface, locations, options = {}) {
|
|
47
|
+
const env = options.env ?? process.env;
|
|
48
|
+
const byId = new Map(locations.map((l) => [l.id, l]));
|
|
49
|
+
const tool = (id, optional) => {
|
|
50
|
+
const l = byId.get(id);
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
found: l?.found ?? false,
|
|
54
|
+
optional,
|
|
55
|
+
...(l?.path ? { path: l.path } : {}),
|
|
56
|
+
...(l?.source ? { source: l.source } : {}),
|
|
57
|
+
...(l && !l.found && l.hint ? { hint: l.hint } : {}),
|
|
58
|
+
...(!(l?.found ?? false) && TOOL_SPECS.find((s) => s.id === id)?.managed ? { installable: true } : {}),
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
const fromNeed = (need) => {
|
|
62
|
+
const tools = (need.tools ?? []).map((id) => tool(id, need.optional ?? false));
|
|
63
|
+
const data = (need.data ?? []).map(({ fetch: fetchSpec, ...d }) => {
|
|
64
|
+
const base = { ...d, ...(fetchSpec ? { fetchable: true } : {}) };
|
|
65
|
+
const fromEnv = env[d.envVar];
|
|
66
|
+
const remembered = options.userData?.[d.id]?.path;
|
|
67
|
+
if (fromEnv)
|
|
68
|
+
return { ...base, set: true, source: "env", path: fromEnv };
|
|
69
|
+
if (remembered)
|
|
70
|
+
return { ...base, set: true, source: "user", path: remembered };
|
|
71
|
+
return { ...base, set: false };
|
|
72
|
+
});
|
|
73
|
+
return { feature: need.feature, state: stateOf(tools, data), tools, ...(data.length > 0 ? { data } : {}), note: need.note };
|
|
74
|
+
};
|
|
75
|
+
const declared = (feature) => (surface.toolNeeds ?? []).filter((n) => n.feature === feature);
|
|
76
|
+
const merge = (feature, needs, fallbackNote) => {
|
|
77
|
+
if (needs.length === 0)
|
|
78
|
+
return { feature, state: "ready", tools: [], note: fallbackNote };
|
|
79
|
+
const parts = needs.map(fromNeed);
|
|
80
|
+
const tools = parts.flatMap((p) => p.tools);
|
|
81
|
+
const data = parts.flatMap((p) => p.data ?? []);
|
|
82
|
+
return { feature, state: stateOf(tools, data), tools, ...(data.length > 0 ? { data } : {}), note: parts.map((p) => p.note).join(" ") };
|
|
83
|
+
};
|
|
84
|
+
const rows = [];
|
|
85
|
+
const covered = new Set();
|
|
86
|
+
const cover = (r) => {
|
|
87
|
+
for (const t of r.tools)
|
|
88
|
+
covered.add(t.id);
|
|
89
|
+
rows.push(r);
|
|
90
|
+
};
|
|
91
|
+
if (surface.logs)
|
|
92
|
+
cover({ feature: "logs", state: "ready", tools: [], note: "triage_logs and read_log need no external tool." });
|
|
93
|
+
if (surface.compat)
|
|
94
|
+
cover({ feature: "compat", state: "ready", tools: [], note: "check_compat reads the install directly; no external tool." });
|
|
95
|
+
if (surface.index)
|
|
96
|
+
cover(merge("index", declared("index"), "build_index needs no external tool for this game."));
|
|
97
|
+
const validators = surface.validators ?? [];
|
|
98
|
+
if (validators.length > 0) {
|
|
99
|
+
const perTool = new Map();
|
|
100
|
+
let indexRules = 0;
|
|
101
|
+
for (const v of validators) {
|
|
102
|
+
for (const t of v.needs?.toolchain ?? [])
|
|
103
|
+
perTool.set(t, (perTool.get(t) ?? 0) + 1);
|
|
104
|
+
if (v.needs?.index)
|
|
105
|
+
indexRules++;
|
|
106
|
+
}
|
|
107
|
+
const tools = [...perTool.keys()].map((id) => tool(id, true));
|
|
108
|
+
const missingTools = tools.filter((t) => !t.found);
|
|
109
|
+
const indexMissing = indexRules > 0 && options.indexBuilt === false;
|
|
110
|
+
const skipped = missingTools.map((t) => `${perTool.get(t.id)} need ${t.id}`);
|
|
111
|
+
if (indexMissing)
|
|
112
|
+
skipped.push(`${indexRules} need the vanilla index (build_index)`);
|
|
113
|
+
cover({
|
|
114
|
+
feature: "validate",
|
|
115
|
+
state: missingTools.length > 0 || indexMissing ? "degraded" : "ready",
|
|
116
|
+
tools,
|
|
117
|
+
note: `${validators.length} rules.` +
|
|
118
|
+
(skipped.length > 0 ? ` Skipped until available: ${skipped.join("; ")}; the rest run now.` : indexRules > 0 && options.indexBuilt === undefined ? ` ${indexRules} of them need the vanilla index.` : " All of them can run."),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (options.project) {
|
|
122
|
+
const steps = normalizeBuildSteps(options.project);
|
|
123
|
+
const ids = [...new Set(steps.flatMap(toolsForStep))];
|
|
124
|
+
const tools = ids.map((id) => tool(id, false));
|
|
125
|
+
const kinds = [...new Set(steps.map((s) => s.step))];
|
|
126
|
+
cover({
|
|
127
|
+
feature: "build",
|
|
128
|
+
state: steps.length === 0 ? "ready" : stateOf(tools),
|
|
129
|
+
tools,
|
|
130
|
+
note: steps.length === 0 ? `${options.project.name} declares no build steps.` : `${options.project.name}'s ${steps.length} build steps (${kinds.join(", ")}).`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else if (declared("build").length > 0) {
|
|
134
|
+
const row = merge("build", declared("build"), "");
|
|
135
|
+
cover({ ...row, note: `${row.note} With a project (projectPath), its own build steps decide this row exactly.` });
|
|
136
|
+
}
|
|
137
|
+
for (const need of surface.toolNeeds ?? []) {
|
|
138
|
+
if (need.feature === "index" || need.feature === "build")
|
|
139
|
+
continue;
|
|
140
|
+
cover(fromNeed(need));
|
|
141
|
+
}
|
|
142
|
+
const rest = TOOL_SPECS.filter((s) => s.usedBy.includes(surface.id) && !covered.has(s.id)).map((s) => tool(s.id, true));
|
|
143
|
+
if (rest.length > 0) {
|
|
144
|
+
rows.push({
|
|
145
|
+
feature: "exec-steps",
|
|
146
|
+
state: "optional",
|
|
147
|
+
tools: rest,
|
|
148
|
+
note: "Run only by exec build steps a project declares (or by hand); none is needed until a project names one.",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return rows;
|
|
152
|
+
}
|
|
153
|
+
export function readinessSummary(rows) {
|
|
154
|
+
const ready = rows.filter((r) => r.state === "ready").map((r) => r.feature);
|
|
155
|
+
const waiting = rows
|
|
156
|
+
.filter((r) => r.state === "blocked" || r.state === "degraded")
|
|
157
|
+
.map((r) => {
|
|
158
|
+
const missing = [...r.tools.filter((t) => !t.found).map((t) => t.id), ...(r.data ?? []).filter((d) => !d.set).map((d) => d.what)];
|
|
159
|
+
return `${r.feature} ${r.state === "blocked" ? "needs" : "works without"} ${missing.length > 0 ? missing.join(", ") : "the index"}`;
|
|
160
|
+
});
|
|
161
|
+
const installable = [...new Set(rows.filter((r) => r.state === "blocked" || r.state === "degraded").flatMap((r) => r.tools.filter((t) => t.installable).map((t) => t.id)))];
|
|
162
|
+
const unsetData = rows.flatMap((r) => (r.data ?? []).filter((d) => !d.set));
|
|
163
|
+
const offer = (installable.length > 0 ? ` ModWright can install ${installable.join(" and ")} itself: check_toolchain action=install tool=<id>.` : "") +
|
|
164
|
+
unsetData
|
|
165
|
+
.map((d) => d.fetchable
|
|
166
|
+
? ` ModWright can fetch ${d.id} itself: check_toolchain action=install data=${d.id}.`
|
|
167
|
+
: ` Remember that folder once: check_toolchain action=remember data=${d.id} dataPath=<folder>.`)
|
|
168
|
+
.join("");
|
|
169
|
+
return `Ready now: ${ready.length > 0 ? ready.join(", ") : "nothing"}.${waiting.length > 0 ? ` ${waiting.join("; ")}.${offer}` : " Nothing else to install."}`;
|
|
170
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./specs.js";
|
|
3
|
+
export * from "./locate.js";
|
|
4
|
+
export * from "./run.js";
|
|
5
|
+
export { planDataMemory, planToolMemory } from "./remember.js";
|
|
6
|
+
export { featureReadiness, readinessSummary, toolsForStep, toolsToLocate } from "./features.js";
|
|
7
|
+
export { applyManagedInstall, findBasePython, planManagedInstall } from "./install.js";
|
|
8
|
+
export { applyDataFetch, defaultDataFolder, planDataFetch, robotsAllows, robotsRules } from "./datafetch.js";
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { userDataDir } from "../home.js";
|
|
6
|
+
import { executePlan, planFile, planMkdir } from "../safety/index.js";
|
|
7
|
+
import { loadUserConfig, userConfigFile, userConfigWritePlan } from "../userconfig.js";
|
|
8
|
+
import { runTool } from "./run.js";
|
|
9
|
+
import { TOOL_SPECS } from "./specs.js";
|
|
10
|
+
const CONFIDENCE = { level: "high", assumptions: ["Writes only under ModWright's own per-user directories."] };
|
|
11
|
+
function sha256Hex(bytes) {
|
|
12
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
async function fileSha256(file) {
|
|
15
|
+
try {
|
|
16
|
+
return sha256Hex(await fs.readFile(file));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function findBasePython(min) {
|
|
23
|
+
const candidates = process.platform === "win32" ? [["py", ["-3"]], ["python", []], ["python3", []]] : [["python3", []], ["python", []]];
|
|
24
|
+
for (const [command, args] of candidates) {
|
|
25
|
+
const r = spawnSync(command, [...args, "-c", "import sys; print(sys.version_info[0], sys.version_info[1], sys.executable)"], { encoding: "utf8", timeout: 15_000, windowsHide: true });
|
|
26
|
+
const m = /^(\d+) (\d+) (.+)$/m.exec(r.stdout ?? "");
|
|
27
|
+
if (r.status !== 0 || !m)
|
|
28
|
+
continue;
|
|
29
|
+
const version = [Number(m[1]), Number(m[2])];
|
|
30
|
+
if (version[0] > min[0] || (version[0] === min[0] && version[1] >= min[1]))
|
|
31
|
+
return { command, args, version, executable: m[3].trim() };
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function pickAsset(assets, platform, arch) {
|
|
36
|
+
return assets.find((a) => a.platform === platform && a.arch === arch);
|
|
37
|
+
}
|
|
38
|
+
function venvPython(dir, platform) {
|
|
39
|
+
return platform === "win32" ? path.join(dir, "Scripts", "python.exe") : path.join(dir, "bin", "python");
|
|
40
|
+
}
|
|
41
|
+
export async function planManagedInstall(toolId, ctx = {}) {
|
|
42
|
+
const specs = ctx.specs ?? TOOL_SPECS;
|
|
43
|
+
const spec = specs.find((s) => s.id === toolId);
|
|
44
|
+
if (!spec)
|
|
45
|
+
return { ok: false, error: `Unknown tool "${toolId}". Known: ${specs.map((s) => s.id).join(", ")}.` };
|
|
46
|
+
const managed = spec.managed;
|
|
47
|
+
if (!managed) {
|
|
48
|
+
const installable = specs.filter((s) => s.managed).map((s) => s.id);
|
|
49
|
+
return { ok: false, error: `ModWright does not install ${spec.label}: install it yourself${spec.homepage ? ` (${spec.homepage})` : ""}, then check_toolchain action=remember. Installable today: ${installable.join(", ")}.` };
|
|
50
|
+
}
|
|
51
|
+
const platform = ctx.platform ?? process.platform;
|
|
52
|
+
const arch = ctx.arch ?? process.arch;
|
|
53
|
+
const configFile = ctx.configFile ?? userConfigFile();
|
|
54
|
+
const loaded = await loadUserConfig(configFile);
|
|
55
|
+
if (loaded.problems.length > 0)
|
|
56
|
+
return { ok: false, error: `${loaded.problems.join(" ")} Fix or remove ${configFile} first; an install records itself there.` };
|
|
57
|
+
const targetDir = path.join(ctx.dataDir ?? userDataDir(), "tools", spec.id, managed.version);
|
|
58
|
+
const now = (ctx.now ?? (() => new Date()))().toISOString();
|
|
59
|
+
if (managed.kind === "download") {
|
|
60
|
+
const asset = pickAsset(managed.assets, platform, arch);
|
|
61
|
+
if (!asset) {
|
|
62
|
+
return { ok: false, error: `${spec.label} ${managed.version} has no pinned release for ${platform}/${arch}. Pinned: ${managed.assets.map((a) => `${a.platform}/${a.arch}`).join(", ")}.` };
|
|
63
|
+
}
|
|
64
|
+
const executable = path.join(targetDir, platform === "win32" ? managed.fileName.win32 : managed.fileName.other);
|
|
65
|
+
const alreadyInstalled = (await fileSha256(executable)) === asset.sha256;
|
|
66
|
+
const steps = alreadyInstalled
|
|
67
|
+
? []
|
|
68
|
+
: [{ kind: "download", url: asset.url, sizeBytes: asset.sizeBytes, sha256: asset.sha256, to: executable }];
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
plan: {
|
|
72
|
+
tool: spec.id,
|
|
73
|
+
label: spec.label,
|
|
74
|
+
version: managed.version,
|
|
75
|
+
license: managed.license,
|
|
76
|
+
kind: "download",
|
|
77
|
+
targetDir,
|
|
78
|
+
executable,
|
|
79
|
+
steps,
|
|
80
|
+
download: alreadyInstalled ? 0 : asset.sizeBytes,
|
|
81
|
+
alreadyInstalled,
|
|
82
|
+
configFile,
|
|
83
|
+
configEntry: { path: executable, installedBy: "modwright", version: managed.version, sha256: asset.sha256, source: asset.url, rememberedAt: now },
|
|
84
|
+
notes: alreadyInstalled
|
|
85
|
+
? [`The pinned ${spec.label} is already at ${executable} (sha256 matches); only the config entry is written.`]
|
|
86
|
+
: [`The file is checked against the pinned size and sha256 before anything is written; a mismatch leaves the disk untouched.`],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const base = await (ctx.findPython ?? (() => findBasePython(managed.pythonMin)))();
|
|
91
|
+
if (!base) {
|
|
92
|
+
const install = platform === "win32" ? "winget install Python.Python.3.12" : platform === "darwin" ? "brew install python" : "your distribution's python3 and python3-venv packages";
|
|
93
|
+
return { ok: false, error: `${spec.label} is built from a Python you already have, and none ${managed.pythonMin.join(".")}+ was found on PATH. Install one (${install}; ModWright does not install Python itself), then repeat.` };
|
|
94
|
+
}
|
|
95
|
+
const executable = venvPython(targetDir, platform);
|
|
96
|
+
const constraints = path.join(targetDir, "modwright-constraints.txt");
|
|
97
|
+
const pipArgs = ["-m", "pip", "install", "--disable-pip-version-check", "--no-input", "--index-url", managed.index, "-c", constraints, ...managed.packages];
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
plan: {
|
|
101
|
+
tool: spec.id,
|
|
102
|
+
label: spec.label,
|
|
103
|
+
version: managed.version,
|
|
104
|
+
license: managed.license,
|
|
105
|
+
kind: "python-venv",
|
|
106
|
+
targetDir,
|
|
107
|
+
executable,
|
|
108
|
+
steps: [
|
|
109
|
+
{ kind: "run", what: `create a virtual environment from your Python ${base.version.join(".")} (${base.executable})`, command: base.executable, args: ["-m", "venv", targetDir] },
|
|
110
|
+
{ kind: "write", what: `pin the dependency closure (${managed.constraints.length} packages)`, path: constraints },
|
|
111
|
+
{ kind: "run", what: `install ${managed.packages.join(" and ")} from PyPI`, command: executable, args: pipArgs },
|
|
112
|
+
{ kind: "run", what: "check the packages import and report their version", command: executable, args: ["-c", "import UnityPy, TypeTreeGeneratorAPI; print('UnityPy', UnityPy.__version__)"] },
|
|
113
|
+
],
|
|
114
|
+
download: `decided by pip from ${managed.index} (about 10 MB of wheels on Windows); pip checks each file against the index's sha256`,
|
|
115
|
+
alreadyInstalled: false,
|
|
116
|
+
configFile,
|
|
117
|
+
configEntry: { path: executable, installedBy: "modwright", version: managed.version, source: `${managed.index} (${managed.packages.join(", ")}; dependencies pinned by version)`, rememberedAt: now },
|
|
118
|
+
notes: [
|
|
119
|
+
"Top-level packages and their dependencies are pinned by version; they are not hash-locked.",
|
|
120
|
+
...(existsSync(targetDir) ? [`${targetDir} exists already; the venv is updated in place.`] : []),
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function recordInConfig(plan) {
|
|
126
|
+
const loaded = await loadUserConfig(plan.configFile);
|
|
127
|
+
if (loaded.problems.length > 0)
|
|
128
|
+
return loaded.problems.join(" ");
|
|
129
|
+
const config = { ...loaded.config, tools: { ...loaded.config.tools, [plan.tool]: plan.configEntry } };
|
|
130
|
+
const report = await executePlan(userConfigWritePlan(config, `record the managed ${plan.label} in the per-user config`, plan.configFile), { mode: "apply", confidence: CONFIDENCE });
|
|
131
|
+
return report.applied ? undefined : "the config write did not apply";
|
|
132
|
+
}
|
|
133
|
+
export async function applyManagedInstall(plan, ctx = {}) {
|
|
134
|
+
const spec = (ctx.specs ?? TOOL_SPECS).find((s) => s.id === plan.tool);
|
|
135
|
+
const backupRoot = path.join(ctx.dataDir ?? userDataDir(), "backups");
|
|
136
|
+
const verified = [];
|
|
137
|
+
const fetchImpl = ctx.fetchImpl ?? fetch;
|
|
138
|
+
const pythonAt = ctx.pythonLocation ?? ((p) => ({ id: plan.tool, label: plan.label, found: true, path: p, usedBy: spec.usedBy }));
|
|
139
|
+
for (const step of plan.steps) {
|
|
140
|
+
if (step.kind === "download") {
|
|
141
|
+
let bytes;
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetchImpl(step.url, { redirect: "follow" });
|
|
144
|
+
if (!response.ok)
|
|
145
|
+
return { ok: false, error: `Download failed: ${step.url} answered ${response.status} ${response.statusText}. Nothing was written.` };
|
|
146
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
return { ok: false, error: `Download failed: ${step.url}: ${error.message}. Nothing was written.` };
|
|
150
|
+
}
|
|
151
|
+
if (bytes.byteLength !== step.sizeBytes)
|
|
152
|
+
return { ok: false, error: `Size mismatch for ${step.url}: got ${bytes.byteLength} bytes, pinned ${step.sizeBytes}. Nothing was written.` };
|
|
153
|
+
const hash = sha256Hex(bytes);
|
|
154
|
+
if (hash !== step.sha256)
|
|
155
|
+
return { ok: false, error: `sha256 mismatch for ${step.url}: got ${hash}, pinned ${step.sha256}. Nothing was written.` };
|
|
156
|
+
const written = await executePlan({ purpose: `install ${plan.label} ${plan.version}`, operations: [planMkdir(path.dirname(step.to)), planFile(step.to, bytes)], backupRoot }, { mode: "apply", confidence: CONFIDENCE });
|
|
157
|
+
if (!written.applied)
|
|
158
|
+
return { ok: false, error: `Writing ${step.to} did not apply.` };
|
|
159
|
+
if ((ctx.platform ?? process.platform) !== "win32")
|
|
160
|
+
await fs.chmod(step.to, 0o755);
|
|
161
|
+
verified.push(`${path.basename(step.to)}: ${bytes.byteLength} bytes, sha256 ${hash} (matches the pin)`);
|
|
162
|
+
}
|
|
163
|
+
else if (step.kind === "write") {
|
|
164
|
+
const managed = spec.managed;
|
|
165
|
+
if (managed?.kind !== "python-venv")
|
|
166
|
+
return { ok: false, error: `Plan step "${step.what}" does not fit ${plan.tool}.` };
|
|
167
|
+
const written = await executePlan({ purpose: step.what, operations: [planMkdir(path.dirname(step.path)), planFile(step.path, managed.constraints.join("\n") + "\n")], backupRoot }, { mode: "apply", confidence: CONFIDENCE });
|
|
168
|
+
if (!written.applied)
|
|
169
|
+
return { ok: false, error: `Writing ${step.path} did not apply.` };
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const location = pythonAt(step.command);
|
|
173
|
+
const result = await runTool(location, { args: step.args, timeoutMs: 20 * 60_000, maxCaptureBytes: 4_000_000 });
|
|
174
|
+
if (!result.ok)
|
|
175
|
+
return { ok: false, error: `${step.what} failed: ${result.error?.message ?? `exit ${result.exitCode}`}\n${(result.stderr || result.stdout).slice(-2000)}` };
|
|
176
|
+
if (step.args.includes("import UnityPy, TypeTreeGeneratorAPI; print('UnityPy', UnityPy.__version__)")) {
|
|
177
|
+
const m = spec.versionPattern?.exec(result.stdout);
|
|
178
|
+
if (!m || m[1] !== plan.version)
|
|
179
|
+
return { ok: false, error: `The check after install read "${result.stdout.trim()}", not UnityPy ${plan.version}. The config was not changed.` };
|
|
180
|
+
verified.push(`import check: ${result.stdout.trim()}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!existsSync(plan.executable))
|
|
185
|
+
return { ok: false, error: `${plan.executable} is missing after the install; the config was not changed.` };
|
|
186
|
+
const configProblem = await recordInConfig(plan);
|
|
187
|
+
if (configProblem)
|
|
188
|
+
return { ok: false, error: `Installed, but the config was not updated: ${configProblem}` };
|
|
189
|
+
verified.push(`recorded in ${plan.configFile}`);
|
|
190
|
+
return { ok: true, executable: plan.executable, verified };
|
|
191
|
+
}
|