8.9 KiB
Refactor and harden all shell wrapper scripts across the module and assets repositories.
Status Snapshot
Current state as of 2026-05-13:
- Argument passthrough is solved for the active root wrappers.
module/andassets/now use repo-root launchers plusscripts/run-nwn-tool.*passthrough shims.- Wrapper regression coverage already exists in
module/tests/andassets/tests/. - Active repo-root
.shand.ps1wrappers are intentionally thin launchers. - The previously stale JSON-config assumption shown in this contract is no
longer current; active repos use
nwn-tool.yaml.
Scope Status
This contract is complete for active root wrappers as of 2026-05-13.
Completed:
- root
.shwrappers are thincdplusscripts/run-nwn-tool.shlaunchers - root
.ps1wrappers are thinscripts/run-nwn-tool.ps1launchers - all active root wrappers forward arbitrary arguments transparently
- shell and PowerShell wrapper regression coverage includes every active root
wrapper in
module/andassets/ - stale documentation references to unsupported root wrappers were removed
Current Findings
No active root-wrapper thinning work remains.
The scripts below are intentionally not root wrappers:
module/scripts/release-all.shandassets/scripts/release-haks.shboth parse effective config and own release publishing orchestration.scripts/run-nwn-tool.*still own tool installation/update bootstrap. This is shared repo plumbing, not build business logic.
These scripts remain acceptable under this wrapper contract because they are release/bootstrap plumbing, not user-facing build/extract wrappers. Future release automation cleanup should happen under release or logging contracts, not by adding logic to root wrappers.
Maintenance Rules
- Keep paired
.shand.ps1root wrappers thin; do not add new wrappers unless a command is intentionally promoted to a user-facing entrypoint. - Root wrappers must not parse config, inspect source trees, preflight build products, or duplicate toolkit command decisions.
scripts/run-nwn-tool.*may keep tool install/update bootstrap, but must not perform command-specific build logic.- Release-entrypoint orchestration belongs in release contracts; do not treat it as a reason to thicken root wrappers.
- If a root wrapper is added or removed, update README command lists and wrapper contract tests in the same change.
Historical Problem
Earlier .sh and .ps1 wrapper scripts did not properly forward arbitrary CLI arguments to the underlying toolkit commands.
Example:
--verbose--debug--dry-run--config--dataset--force- future flags
That passthrough issue is now fixed for active root wrappers.
Additionally, after recent refactors — including:
- YAML configuration migration
- configuration hardening
- toolkit restructuring
- command refactors
- repository abstraction changes
— several wrapper scripts became outdated and no longer correctly reflected toolkit behavior.
Example of a recent error produced:
$ ./build-haks.sh
run-nwn-tool.sh: Updating sow-toolkit to match latest sow-tools release...
installed: ${HOME}/Projects/nwnee-shadowsoverwestgate/assets/tools/sow-toolkit
run-nwn-tool.sh: Refreshing credits cache before build-haks...
Traceback (most recent call last):
File "${HOME}/Projects/nwnee-shadowsoverwestgate/assets/./scripts/sync-credits-cache.py", line 61, in <module>
main()
~~~~^^
File "${HOME}/Projects/nwnee-shadowsoverwestgate/assets/./scripts/sync-credits-cache.py", line 46, in main
music_configs = load_music_configs()
File "${HOME}/Projects/nwnee-shadowsoverwestgate/assets/./scripts/sync-credits-cache.py", line 16, in load_music_configs
config = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/pathlib/__init__.py", line 787, in read_text
with self.open(mode='r', encoding=encoding, errors=errors, newline=newline) as f:
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/pathlib/__init__.py", line 771, in open
return io.open(self, mode, buffering, encoding, errors, newline)
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '${HOME}/Projects/nwnee-shadowsoverwestgate/assets/nwn-tool.json'
Completed Goal
Active root wrapper scripts are now:
- consistent
- argument-transparent
- future-safe
- deterministic
- repository-agnostic where possible
Core requirements
Global argument forwarding
All wrappers must forward arbitrary arguments transparently to the underlying command.
Examples:
./run-tool.sh build-haks --verbose
./run-tool.sh music build --dataset westgate --dry-run
./extract-module.sh --debug --force
Must correctly pass all additional arguments through unchanged.
PowerShell wrappers must behave equivalently:
.\run-tool.ps1 build-haks --verbose
No wrapper should:
- silently swallow arguments
- hardcode known flags only
- require updating for every new CLI option
- partially parse arguments unless absolutely necessary
Wrappers should behave as thin passthrough layers.
Wrapper audit
Audit all .sh and .ps1 scripts in:
- module repositories
- assets repositories
- toolkit-related repositories
Identify wrappers that:
- no longer match current toolkit commands
- reference outdated config paths
- reference old JSON config names
- assume old directory layouts
- assume deprecated commands
- hardcode obsolete arguments
- bypass the new normalized YAML config system
- duplicate logic now handled in the toolkit
Consistency requirements
All wrappers should follow consistent conventions:
- argument forwarding
- environment setup
- working directory resolution
- config resolution
- logging behavior
- error handling
- exit code propagation
- path normalization
- quoting/escaping
Behavior requirements
Argument passthrough
Shell wrappers should use safe passthrough semantics.
Examples:
- Bash:
"$@" - PowerShell:
@args
Arguments must preserve:
- ordering
- quoting
- spaces
- special characters
Exit codes
Wrappers must propagate underlying command exit codes correctly.
No swallowing failures.
Logging
Wrappers should:
- print concise execution summaries
- show the underlying toolkit command being executed
- optionally support wrapper-level debug logging
- avoid duplicating toolkit logs
Configuration awareness
Wrappers must support:
- YAML config paths
- overridden config locations
- repository-local config resolution
- future config expansion
No wrapper should assume:
config.json- fixed build paths
- fixed repository names
- fixed datasets
- fixed HAK names
Repository hardening alignment
Wrappers must align with the broader configuration hardening effort:
- no repository-specific hardcoding
- no hidden defaults
- no implicit assumptions
- no duplicated business logic
Wrappers should invoke toolkit functionality, not reimplement it.
Desired architecture
Wrappers should become:
- thin launchers
- environment bootstrap layers
- argument passthrough adapters
The toolkit itself should contain:
- validation
- command parsing
- config loading
- business logic
- deterministic behavior
Cross-platform behavior
Ensure .sh and .ps1 wrappers behave equivalently where possible.
Validate:
- quoting
- argument escaping
- relative path handling
- environment variable handling
- error propagation
- working directory behavior
Migration tasks
- Inventory all wrappers.
- Classify active vs obsolete wrappers.
- Update wrappers to support transparent argument forwarding.
- Remove outdated assumptions.
- Align wrappers with YAML config loading.
- Update docs/examples/help text.
- Add regression tests.
- Ensure compatibility with current toolkit commands.
- Remove duplicated wrapper logic where possible.
- Consolidate shared wrapper behavior if appropriate.
Testing requirements
Add tests for:
- argument passthrough
- quoted arguments
- multi-argument forwarding
- verbose/debug forwarding
- config override forwarding
- dataset override forwarding
- exit code propagation
- invalid command handling
- PowerShell parity
- shell parity
- wrapper execution from different working directories
Examples:
--verbose--debug--dry-run--config path/to/config.yaml--dataset westgate--force- unknown future flags
Acceptance criteria
- All wrappers forward arbitrary arguments correctly.
- Wrappers reflect the current toolkit architecture.
- No wrappers assume old JSON config behavior.
- No wrappers contain obsolete repository-specific logic.
- Wrapper behavior is deterministic and cross-platform.
- Exit codes propagate correctly.
- Wrapper maintenance burden is minimized.
- New CLI flags work automatically without wrapper updates.
- The toolkit, not wrappers, owns application logic.