claude-timemachine 438b1f7c65 diff-based mod removal via --previous-manifest
Adds removal-detection: when --previous-manifest <path> is given,
the converter diffs the previous publish against current packwiz state
and emits modify[].type=remove entries for mods/resourcepacks/etc that
disappeared, using simple-mod-sync's on-disk naming convention as the
regex pattern.

Reverse-engineered from upstream source:
- simple-mod-sync writes <sanitized_name>-<sanitized_version>.<ext>
- StringUtils.sanitize strips [^a-zA-Z0-9.\-_]
- GetOlderVersion() finds files starting with <name>- and auto-deletes
  on version bumps. So version upgrades need no converter handling;
  only full removals do.

8 new tests including end-to-end CLI verification with a synthetic
previous manifest. 23/23 pass.
2026-06-02 11:50:51 +02:00

packwiz-to-sms

Convert a packwiz pack to a simple-mod-sync manifest (sync_version: 3).

Why

Use packwiz to author the modpack (Modrinth/CurseForge integration, git-friendly TOML, optional/side-aware mods, .mrpack export for free), and simple-mod-sync for delivery to clients that can't or won't use Prism/MMC pre-launch hooks (vanilla launcher, TLauncher, cracked players).

One source of truth, two distribution channels — .mrpack export and simple-mod-sync manifest are both produced from the same packwiz repo.

Install

Requires Python 3.11+ (uses tomllib). Zero runtime deps.

git clone https://git.timemachine.center/Timemachine/packwiz-to-sms.git
cd packwiz-to-sms
python3 packwiz_to_sms.py --help

Usage

# Minimal — emit manifest to stdout
python3 packwiz_to_sms.py /path/to/packwiz/pack

# Write to file
python3 packwiz_to_sms.py /path/to/packwiz/pack -o manifest.json

# Bundle non-mod files (config/, options.txt, servers.dat) into a zip
# and add a 'packed' entry pointing at where you'll host it
python3 packwiz_to_sms.py /path/to/packwiz/pack \
    -o manifest.json \
    --bundle-non-mods overrides.zip \
    --bundle-url https://packs.example.com/overrides.zip

# Generate removal entries for mods dropped since the previous publish
python3 packwiz_to_sms.py /path/to/packwiz/pack \
    -o manifest.json \
    --previous-manifest /path/to/last-published-manifest.json

Mod removal

Two cases, handled differently:

What changed Who handles cleanup
Mod version bump (Sodium 0.5 → 0.6) simple-mod-sync itself — it writes downloaded files as <sanitized_name>-<sanitized_version>.<ext> and on update looks for any prior file starting with <name>- and deletes it. No converter intervention needed.
Mod removed entirely (no longer in pack) Converter emits an explicit modify[].type=remove entry with a regex matching simple-mod-sync's on-disk naming. Triggered by --previous-manifest <path> flag.

Without --previous-manifest, removed mods stay on player disk forever. In CI, keep the last-published manifest and feed it in as the previous one on every run.

The regex follows simple-mod-sync's StringUtils.sanitize() rules: [^a-zA-Z0-9.\-_] characters are stripped. So "Fabric API (Old)" → pattern ^mods/FabricAPIOld-.*\.jar$.

What gets emitted

Packwiz path Becomes simple-mod-sync type
mods/*.pw.toml (side=client or both) mod
mods/*.pw.toml (side=server) dropped
resourcepacks/*.pw.toml resourcepack
shaderpacks/*.pw.toml shader
**/datapacks/*.pw.toml datapack
Any non-metafile (e.g. options.txt, config/*) bundled into zip via --bundle-non-mods, emitted as one packed entry pointing at directory: "."

CurseForge mods that use mode = "metadata:curseforge" (no direct URL) are skipped with a warning. Either switch to Modrinth equivalents or run packwiz cf reexport first to inline resolved URLs.

What's not handled

  • Optional mods — simple-mod-sync has no per-client toggle UI. All non-server mods are emitted unconditionally. Ship two manifests (with/without optional) if you need this.
  • Rename / regex transforms — packwiz has no equivalent concept, so we don't generate modify.rename entries.

How it works

  1. Reads pack.toml + index.toml.
  2. For each entry marked metafile = true: reads the .pw.toml, pulls download.url, picks the simple-mod-sync type from the parent directory.
  3. Drops side = "server".
  4. Drops entries where download.url is missing (CF metadata mode).
  5. Optionally zips non-metafile files into a single archive for packed distribution.

Output schema matches sync_version: 3 exactly (see simple-mod-sync DOCS.md).

Tests

python3 -m pytest tests/

Covers conversion logic + CLI + bundle pipeline + a network integration test that converts the official packwiz-example-pack (auto-skipped if offline).

Upstream contribution

This tool fits the model used by other simple-mod-sync translators. It can be PR'd upstream as translators/packwiz.py.

License

MIT.

S
Description
Convert a packwiz pack to a simple-mod-sync (sync_version:3) manifest. One source of truth for modpack authoring (packwiz), two delivery channels (simple-mod-sync + .mrpack).
Readme MIT 45 KiB
Languages
Python 100%