Skip to content

Latest commit

 

History

History
409 lines (311 loc) · 14.5 KB

File metadata and controls

409 lines (311 loc) · 14.5 KB

mash

Lint status

A Git-based CLI addon manager for old WoW clients

mash (manage addons via Bash) is a CLI addon manager targeting older WoW clients where addons are primarily distributed via Git repositories. Existing addon managers are either cumbersome to install on some systems (a language-specific package manager or runtime just for a single program is bloat, and on atomic distros like SteamOS or Bazzite often not easily feasible) or lacking features I need. Bash runs everywhere I care about out-of-the-box.1

mash offers a range of features that simplify managing WoW addons:

  • Multiple independent profiles: manage multiple client installations side by side (for example, Vanilla and TBC), each with its own configuration and installed addons.
  • Support for all common repository layouts: a single addon at the root, addons in subdirectories, or multiple .toc per directory; see the Core concepts section for details.
  • Preview updates before applying: see which tracked repositories have updates available upstream without touching the working state.
  • Pinning: freeze a repository at a specific commit so updates skip it, useful for sticking to a known-good version when an addon's latest commit is broken or when newer commits target different interface versions.
  • Batch installation: add many repositories at once from a list file or from stdin (for piping from curl and similar).

Table of contents

Install

Dependencies

  • Bash 3.2 or later (chosen for out-of-the-box macOS compatibility; no need to brew install bash)
  • Git
  • Standard Unix utilities (present on every system that ships Bash)

Via Homebrew (recommended)

A HEAD-only Homebrew formula is included in this repository. Tap it first, then install the formula:

brew tap mserajnik/mash https://github.com/mserajnik/mash
brew install --HEAD mash

To update later:

brew upgrade --fetch-HEAD mash

Manual

Clone the repository and symlink the script into a directory on your $PATH (~/.local/bin is a common choice; substitute another directory if it is not on your $PATH):

git clone https://github.com/mserajnik/mash ~/mash
ln -s ~/mash/bin/mash ~/.local/bin/mash

To update later:

git -C ~/mash pull

Usage

Core concepts

A WoW addon is a directory under Interface/AddOns that contains a .toc ("table of contents") file alongside the addon's Lua code and XML files. The .toc file name matches the directory name, and its ## Interface: directive declares which client version the addon targets.

Any Git repository that contains one or more such directories is, in mash's terminology, an addon repository. mash clones the repository, finds the .toc files whose interface version matches your configured one, and symlinks the matching addon directories into Interface/AddOns.

In flag names and messages you will see .toc referenced explicitly (for example, --toc, "multiple matching .toc files") because that is what mash scans on disk. Each installed .toc corresponds to one addon directory, so for end users the two terms are effectively interchangeable.

Most server emulators run the latest patch of an expansion, so the interface version is usually fixed by your expansion choice (Vanilla 1.12.1 is 11200, TBC 2.4.3 is 20400, WotLK 3.3.5a is 30300). In those cases, picking the expansion and picking the interface version describe the same decision.

mash only supports addons distributed via Git and cannot install addons hosted on other platforms.

The most common repository structures are covered:

  • Single .toc file in repository root (the simplest case)
  • Single or multiple .toc files in subdirectories (the actual addon is in a subdirectory or the repository contains multiple addons)
  • Multiple .toc files in the same directory are resolved automatically via interface version where possible (e.g., shagu/pfQuest); where not possible, mash prompts for selection

Initial setup

mash init <client-directory> # Path to the client directory.

mash init validates the client directory, creates Interface/AddOns if it does not already exist, prompts for the TOC interface version your client expects (defaults are listed; you can also type a custom version or shortcut), and creates a configuration profile set as active.

By default the new profile is named default. To pick a different name, pass --profile <name> (see Profiles for the management commands).

For non-interactive usage, both inputs can be passed as flags:

mash init <client-directory> --profile tbc --interface tbc

Tip

--interface accepts the shortcuts vanilla / tbc / wotlk (resolving to 11200 / 20400 / 30300 respectively) or any 1-6 digit integer.

Profiles

mash supports multiple independent setups via named profiles, each with its own client directory, interface version, and tracked repositories. This is useful when running more than one expansion in parallel (for example a Vanilla client and a TBC client).

mash profile # List profiles (active marked with *).
mash use <name> # Switch to an existing profile.
mash drop <name> # Delete a profile and the addons it manages.

All other commands (everything except init, use, drop, and profile) operate on the active profile. Switch with mash use <name> to work on a different one.

New profiles are created by passing --profile <name> to mash init:

mash init /path/to/wotlk-client --profile wotlk --interface wotlk

mash drop refuses to delete the active profile (switch first) or the only remaining profile (create another one first).

Adding addon repositories

mash add shagu/pfQuest
mash add The-Kludge-Bureau/Bagshui 1.5.16
mash add DennisWG/BetterAlign 8840ee2dad218d73e5ae8b23979f552f3c2c56cd

The second argument is optional. If omitted, mash tracks the repository's default branch. A 7-40 character lower-case hex string is treated as a commit hash and pins the repository. Anything else is probed at the remote as a branch first, then a tag.

GitHub repositories can be referenced as owner/repository; other hosts need the full URL. The shorthand always expands to the HTTPS URL (https://github.com/owner/repository.git); if you need SSH (for example to clone private repositories), pass the full git@github.com:owner/repository.git form instead.

mash add accepts an optional --toc <name> flag to disambiguate the case where an addon directory contains more than one matching .toc file (rare, because the interface version filter usually resolves to a single match). When this happens during an interactive run, mash prompts for the chosen name; for non-interactive runs, pass --toc <name> to make the choice explicit.

Batch add from a list file

mash add --file addons.txt # Read entries from a file.
curl -sSL https://example.com/addons.txt | mash add --file - # Read from stdin.

mash add --file <file> reads one entry per line, each with the same shape as positional mash add arguments. Blank lines and lines starting with # are ignored. Repositories that are already added are skipped; other failures are reported and the batch continues. A summary line at the end tallies how many were added, skipped, and failed.

Example list file:

# 1.12.1 addons
shagu/pfQuest
The-Kludge-Bureau/Bagshui 1.5.16
DennisWG/BetterAlign 8840ee2dad218d73e5ae8b23979f552f3c2c56cd

Tip

Pass - as the file to read from stdin (useful for piping from curl, cat, or other tools).

Pinning and unpinning addon repositories

mash pin shagu/pfQuest # Pin at the clone's current HEAD.
mash pin DennisWG/BetterAlign 8840ee2 # Pin at a specific commit.
mash unpin DennisWG/BetterAlign # Unpin and switch to the origin's default branch.
mash unpin shagu/pfQuest master # Unpin and switch to a specific branch or tag.

mash pin freezes a repository at a specific commit so subsequent mash update runs skip it. With no commit argument, it freezes at the clone's current HEAD; with a commit argument, it checks out that commit first (fetching from upstream if it is not in the local clone yet) and then freezes. A repeat pin against the same already-pinned commit is a no-op success.

mash unpin reverses this: it switches the repository back to a branch or tag and clears the pinned flag. With no ref argument, it switches to the origin's default branch; with a ref argument, it treats the value as a branch first, then as a tag (same probing as mash add).

Checking for addon repository updates

mash status # Check every tracked repository.
mash status shagu/pfQuest # Check one repository.

mash status fetches each tracked repository and reports whether updates are available upstream, without touching the working state. Pinned repositories are skipped with a message; missing clones emit a warning. When mash status runs against more than one repository, it emits a summary line at the end.

Apply available updates with mash update.

Updating addon repositories

mash update # Update every tracked repository.
mash update shagu/pfQuest # Update one repository.

mash update fetches and resets the working tree to the recorded ref for branches and tags, and skips pinned commits with a message. If the upstream layout has changed in a way that breaks a recorded addon, mash refuses and tells you to run mash refresh to re-detect the addons.

Re-detecting addons in existing clones

mash refresh shagu/pfQuest
mash refresh shagu/pfQuest --toc pfQuest-tbc # Pick a .toc when multiple match.

mash refresh re-runs addon detection against the existing clone and reconciles the installed symlinks to match. It does not fetch, checkout, or reset the clone; pair it with mash update if you also need to advance the Git state.

The typical use case is the layout drift that mash update refuses to handle (a .toc was added, removed, renamed, or moved upstream).

Listing addon repositories

mash list

mash list prints each tracked repository with its tracked ref, current commit, and its addons. Pinned repositories show pinned: <commit-hash> in place of the ref:

shagu/pfQuest (branch: master, abc1234).
  pfQuest -> ~/.local/share/mash/default/clones/shagu/pfQuest
DennisWG/BetterAlign (pinned: 8840ee2).
  BetterAlign -> ~/.local/share/mash/default/clones/DennisWG/BetterAlign

Removing addon repositories

mash remove shagu/pfQuest

mash remove removes the repository and deletes the symlinks mash created for its addons. Addons that are not managed by mash are left alone.

Global flags

-v, --verbose # Stream Git output.
-q, --quiet # Suppress non-error output.
-h, --help # Show usage.

mash --help shows the top-level summary; append --help to any subcommand (for example, mash add --help) for per-command details.

Configuration

Each profile's configuration lives at ${XDG_CONFIG_HOME:-~/.config}/mash/<profile>/config.ini and is in INI format:

[main]
client_dir=/path/to/wow-client
interface_version=11200

[repo:shagu/pfQuest]
url=https://github.com/shagu/pfQuest.git
ref=master
ref_kind=branch
pinned=false
addons=pfQuest:.

[repo:DennisWG/BetterAlign]
url=https://github.com/DennisWG/BetterAlign.git
ref=8840ee2dad218d73e5ae8b23979f552f3c2c56cd
ref_kind=commit
pinned=true
addons=BetterAlign:.

ref_kind is one of branch, tag, or commit. addons is a comma-separated list of <name>:<reldir> pairs, where <name> is the addon directory name (the chosen .toc basename) and <reldir> is the addon's directory inside the clone (. for the repository root).

mash rewrites the file on every init, add, pin, unpin, update, refresh, and remove. You can edit it by hand for one-off changes (e.g., switch a repository to track a different branch), but the easier path is usually to use mash remove and re-add.

The persistent clone cache for each profile lives at ${XDG_DATA_HOME:-~/.local/share}/mash/<profile>/clones.

The active profile is tracked in a single-line state file at ${XDG_CONFIG_HOME:-~/.config}/mash/active containing the profile's name. mash use and mash init write this file atomically.

Maintainer

Michael Serajnik

Contribute

You are welcome to help out!

Open an issue or make a pull request.

Licenses

This project follows the REUSE specification.

Footnotes

  1. Would this be faster in a proper language like Go, with parallel Git fetches and prebuilt binaries shipped via GitHub Actions? Probably. But if Bash is good enough for pass, it is certainly good enough for an addon manager for a 20+ year-old game.