feat: add patch series, build scripts and release automation

This commit is contained in:
Felitendo committed 2026-09-14 11:03:47 +02:00
1 parent 8f4e5e0521
commit 02e18e3765
18 files changed
+23026

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Build Modrinth Enhanced from the patched checkout and collect the installers.
#
# Expects scripts/prepare.sh to have run. Finished bundles are copied to
# build/artifacts.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
require_worktree
ARTIFACTS="${ARTIFACTS:-$REPO_ROOT/build/artifacts}"
# Upstream keeps the API endpoints out of the sources and picks one at build
# time; production is the only sensible choice for a release build.
log "Selecting the production environment"
cp "$WORKTREE/packages/app-lib/.env.prod" "$WORKTREE/packages/app-lib/.env"
"$REPO_ROOT/scripts/set-version.sh"
log "Installing JavaScript dependencies"
(cd "$WORKTREE" && pnpm install --frozen-lockfile)
tauri_args=()
case "$(uname -s)" in
MINGW* | MSYS* | CYGWIN* | Windows_NT)
platform=windows
tauri_args+=(--bundles nsis)
;;
Darwin)
platform=macos
tauri_args+=(--target universal-apple-darwin)
;;
*)
platform=linux
;;
esac
log "Building for $platform"
(cd "$WORKTREE" && pnpm --filter=@modrinth/app run tauri build "${tauri_args[@]}")
log "Collecting bundles into $ARTIFACTS"
rm -rf "$ARTIFACTS"
mkdir -p "$ARTIFACTS"
if [ "$platform" = macos ]; then
bundle_dir="$WORKTREE/target/universal-apple-darwin/release/bundle"
else
bundle_dir="$WORKTREE/target/release/bundle"
fi
found=0
while IFS= read -r -d '' artifact; do
cp "$artifact" "$ARTIFACTS/"
found=1
done < <(find "$bundle_dir" -maxdepth 2 -type f \
\( -name '*.AppImage' -o -name '*.deb' -o -name '*.rpm' \
-o -name '*.dmg' -o -name '*.app.tar.gz' -o -name '*-setup.exe' \) -print0)
[ "$found" = 1 ] || die "No bundles were produced under $bundle_dir"
ls -la "$ARTIFACTS"
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Verify that the patched checkout still is Modrinth Enhanced.
#
# A patch can apply cleanly and still stop doing its job - upstream may move
# the thing it was holding down. These checks assert the outcome rather than
# the diff: the app is named correctly, offline accounts exist, and no
# telemetry endpoint survives into the built frontend.
#
# Checks that need the built frontend are skipped when it is not there yet, so
# this is useful both before and after scripts/build.sh.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
require_worktree
failures=0
check() {
local description="$1"
shift
if "$@" >/dev/null 2>&1; then
printf ' \033[1;32mok\033[0m %s\n' "$description"
else
printf ' \033[1;31mFAIL\033[0m %s\n' "$description"
failures=$((failures + 1))
fi
}
contains() {
grep -qF "$2" "$1"
}
missing() {
! grep -qrF "$2" "$1"
}
log "Branding"
check "tauri.conf.json is named Modrinth Enhanced" \
contains "$WORKTREE/apps/app/tauri.conf.json" '"productName": "Modrinth Enhanced"'
check "the window is titled Modrinth Enhanced" \
contains "$WORKTREE/apps/app/tauri.conf.json" '"title": "Modrinth Enhanced"'
check "the icon set was replaced" \
test -f "$WORKTREE/apps/app/icons/modrinth-enhanced.svg"
log "Offline accounts"
check "app-lib exposes login_offline" \
contains "$WORKTREE/packages/app-lib/src/api/minecraft_auth.rs" 'pub async fn login_offline'
check "the Tauri command is registered" \
contains "$WORKTREE/apps/app/src/api/auth.rs" 'login_offline,'
check "the frontend can reach it" \
contains "$WORKTREE/apps/app-frontend/src/helpers/auth.js" "plugin:auth|login_offline"
log "No telemetry in the sources"
# Quoted, so that the module names being mentioned in a comment explaining why
# they are gone does not count as importing them.
check "nothing imports posthog-js" \
missing "$WORKTREE/apps/app-frontend/src" "'posthog-js'"
check "nothing imports @sentry/vue" \
missing "$WORKTREE/apps/app-frontend/src" "'@sentry/vue'"
check "the Tally embed is gone" \
missing "$WORKTREE/apps/app-frontend/index.html" "tally.so"
check "playtime is not reported" \
missing "$WORKTREE/packages/app-lib/src/api/instance/run.rs" "analytics/playtime"
check "server plays are not reported" \
missing "$WORKTREE/packages/app-lib/src/api/instance/run.rs" "analytics/minecraft-server-play"
check "the CSP does not allow PostHog" \
missing "$WORKTREE/apps/app/tauri.conf.json" "posthog"
check "the CSP does not allow Sentry" \
missing "$WORKTREE/apps/app/tauri.conf.json" "sentry.io"
dist="$WORKTREE/apps/app-frontend/dist"
if [ -d "$dist" ]; then
log "No telemetry in the built frontend"
check "no PostHog endpoint in the bundle" missing "$dist" "posthog.modrinth.com"
check "no Sentry endpoint in the bundle" missing "$dist" "ingest.us.sentry.io"
check "no Tally embed in the bundle" missing "$dist" "tally.so"
else
warn "Skipping bundle checks: $dist does not exist yet"
fi
artifacts="${ARTIFACTS:-$REPO_ROOT/build/artifacts}"
if [ -d "$artifacts" ] && [ -n "$(ls -A "$artifacts" 2>/dev/null)" ]; then
log "Installers"
for artifact in "$artifacts"/*; do
name="$(basename "$artifact")"
case "$name" in
"Modrinth Enhanced"* | ModrinthEnhanced* | "Modrinth_Enhanced"*)
printf ' \033[1;32mok\033[0m %s\n' "$name"
;;
*)
printf ' \033[1;31mFAIL\033[0m %s is not named after Modrinth Enhanced\n' "$name"
failures=$((failures + 1))
;;
esac
done
fi
if [ "$failures" -gt 0 ]; then
die "$failures check(s) failed"
fi
log "All checks passed"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Shared settings for every script in this repository.
#
# Source this, do not run it.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Where the Modrinth App sources come from, and which release of them the
# patches in this repository are written against.
UPSTREAM_REPO="${UPSTREAM_REPO:-https://github.com/modrinth/code.git}"
UPSTREAM_REF="${UPSTREAM_REF:-$(tr -d '[:space:]' <"$REPO_ROOT/upstream.txt")}"
# The patched checkout. Everything in here is disposable: it is recreated from
# the upstream tag plus patches/ and is never committed to this repository.
WORKTREE="${WORKTREE:-$REPO_ROOT/build/upstream}"
# Branch the patches are applied on top of the upstream tag as.
PATCH_BRANCH=enhanced
PATCH_DIR="$REPO_ROOT/patches"
log() {
printf '\033[1;32m==>\033[0m %s\n' "$*"
}
warn() {
printf '\033[1;33m==>\033[0m %s\n' "$*" >&2
}
die() {
printf '\033[1;31m==>\033[0m %s\n' "$*" >&2
exit 1
}
# Version the built app reports, derived from the upstream tag it is built
# from: `v0.20.5` becomes `0.20.5`.
app_version() {
printf '%s\n' "${UPSTREAM_REF#v}"
}
require_worktree() {
[ -d "$WORKTREE/.git" ] || die "No patched checkout at $WORKTREE. Run scripts/prepare.sh first."
}
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Regenerate patches/ from the commits in build/upstream.
#
# Run this after changing anything in the patched checkout: every commit on top
# of the upstream tag becomes one patch file, and patch files that no longer
# have a commit behind them are deleted.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
require_worktree
if [ -n "$(git -C "$WORKTREE" status --porcelain)" ]; then
die "$WORKTREE has uncommitted changes. Commit them first: each commit becomes one patch."
fi
count="$(git -C "$WORKTREE" rev-list --count "$UPSTREAM_REF..HEAD")"
[ "$count" -gt 0 ] || die "No commits on top of $UPSTREAM_REF to export."
log "Exporting $count patches"
rm -f "$PATCH_DIR"/*.patch
git -C "$WORKTREE" format-patch \
--binary \
--zero-commit \
--no-signature \
--no-numbered \
--output-directory "$PATCH_DIR" \
"$UPSTREAM_REF..HEAD"
log "patches/ now contains:"
ls -1 "$PATCH_DIR"
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Print the newest Modrinth App release tag upstream has published.
#
# With --write, also store it in upstream.txt. Exits with status 0 either way;
# compare the output with upstream.txt to find out whether anything moved.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
latest="$(
git ls-remote --tags --refs "$UPSTREAM_REPO" 'v*' |
sed 's#.*refs/tags/##' |
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' |
sort -V |
tail -1
)"
[ -n "$latest" ] || die "Could not determine the latest upstream release tag"
if [ "${1:-}" = --write ]; then
printf '%s\n' "$latest" >"$REPO_ROOT/upstream.txt"
fi
printf '%s\n' "$latest"
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Check out the pinned Modrinth App release and apply every patch on top of it.
#
# The result lands in build/upstream on the `enhanced` branch and is what all
# other scripts build from. Running this again always starts from a clean
# upstream tree, so it is safe to repeat.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
log "Upstream $UPSTREAM_REF from $UPSTREAM_REPO"
if [ ! -d "$WORKTREE/.git" ]; then
log "Cloning into $WORKTREE"
mkdir -p "$(dirname "$WORKTREE")"
git clone --depth 1 --branch "$UPSTREAM_REF" "$UPSTREAM_REPO" "$WORKTREE"
else
log "Fetching $UPSTREAM_REF into the existing checkout"
git -C "$WORKTREE" remote set-url origin "$UPSTREAM_REPO"
git -C "$WORKTREE" fetch --depth 1 --force origin "refs/tags/$UPSTREAM_REF:refs/tags/$UPSTREAM_REF"
fi
# `git am` refuses to run with a rebase or merge in progress, and a previous
# run may have stopped on a conflict.
git -C "$WORKTREE" am --abort 2>/dev/null || true
log "Resetting to $UPSTREAM_REF"
git -C "$WORKTREE" checkout --detach --force "$UPSTREAM_REF"
git -C "$WORKTREE" branch -f "$PATCH_BRANCH" "$UPSTREAM_REF"
git -C "$WORKTREE" checkout --force "$PATCH_BRANCH"
git -C "$WORKTREE" reset --hard "$UPSTREAM_REF"
git -C "$WORKTREE" clean -fdx -e node_modules -e target
# `git am` needs an identity for the commits it creates.
git -C "$WORKTREE" config user.name "Modrinth Enhanced"
git -C "$WORKTREE" config user.email "patches@modrinth-enhanced.invalid"
git -C "$WORKTREE" config commit.gpgsign false
shopt -s nullglob
patches=("$PATCH_DIR"/*.patch)
shopt -u nullglob
[ ${#patches[@]} -gt 0 ] || die "No patches found in $PATCH_DIR"
log "Applying ${#patches[@]} patches"
if ! git -C "$WORKTREE" am --3way --whitespace=nowarn "${patches[@]}"; then
cat >&2 <<EOF
A patch did not apply to $UPSTREAM_REF.
The failed patch is left staged in $WORKTREE so it can be fixed by hand:
cd $WORKTREE
git status # see the conflicts
# ...resolve them, then:
git add -A && git am --continue
# once every patch is in:
$REPO_ROOT/scripts/export-patches.sh
EOF
exit 1
fi
log "Patched checkout ready at $WORKTREE"
git -C "$WORKTREE" --no-pager log --oneline "$UPSTREAM_REF..$PATCH_BRANCH"
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# Stamp the build version into the patched checkout.
#
# Upstream leaves `1.0.0-local` in the sources and injects the real version in
# its release workflow; this does the same. Without an argument the version is
# derived from the upstream tag in upstream.txt.
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
require_worktree
version="${1:-$(app_version)}"
version="${version#v}"
log "Setting version to $version"
python3 - "$WORKTREE" "$version" <<'PY'
import json
import pathlib
import re
import sys
worktree = pathlib.Path(sys.argv[1])
version = sys.argv[2]
for relative in ("apps/app/Cargo.toml", "packages/app-lib/Cargo.toml"):
path = worktree / relative
text = path.read_text(encoding="utf-8")
# Only the `version` key of the leading [package] table, never a dependency.
patched, count = re.subn(
r'(?m)^version = "[^"]*"$', f'version = "{version}"', text, count=1
)
if count != 1:
sys.exit(f"Could not find a package version in {relative}")
path.write_text(patched, encoding="utf-8")
print(f" {relative}")
path = worktree / "apps/app-frontend/package.json"
data = json.loads(path.read_text(encoding="utf-8"))
data["version"] = version
# Keep the file byte-compatible with the tab indentation upstream uses so that
# `prettier --check` stays happy.
path.write_text(json.dumps(data, indent="\t", ensure_ascii=False) + "\n", encoding="utf-8")
print(" apps/app-frontend/package.json")
PY