Prepare 1.0.0 release workflow
This commit is contained in:
1 parent
80fe2e3867
commit
0d2a981525
3 files changed
+62
-10
No files matched your search
@@ -40,10 +40,10 @@ FPCALC_BINARY='/path/to/universal/fpcalc' \
|
|||||||
./scripts/build-release.sh
|
./scripts/build-release.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Publish the resulting release from a clean `main` checkout after setting `APP_VERSION`, `APP_BUILD`, and a Gitea API token in `GITEA_TOKEN`:
|
Publish the resulting release from a clean `main` checkout after setting `APP_VERSION` and `APP_BUILD`. The publisher uses a configured Git credential helper for Gitea; alternatively, provide an API token in `GITEA_TOKEN`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
APP_VERSION=1.0.1 APP_BUILD=2 GITEA_TOKEN='…' ./scripts/publish-release.sh
|
APP_VERSION=1.0.0 APP_BUILD=1 ./scripts/publish-release.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The publisher creates a Gitea release, signs the Sparkle archive with the private key in Keychain, updates and pushes `appcast.xml`, and deploys the DMG and download page to `https://tonuino.maddin.app`. The website itself is static and has no JavaScript runtime dependency.
|
The publisher creates a Gitea release, signs the Sparkle archive with the private key in Keychain, updates and pushes `appcast.xml`, and deploys the DMG and download page to `https://tonuino.maddin.app`. The website itself is static and has no JavaScript runtime dependency.
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
## Tonuino Manager 1.0.0
|
||||||
|
|
||||||
|
Native macOS-App zur Verwaltung von TonUINO-Musikbibliotheken, lokalen Box-Profilen und NFC-Karten.
|
||||||
|
|
||||||
|
- Musik-SD-Karten direkt oder als lokale Profile verwalten, vergleichen und synchronisieren.
|
||||||
|
- Ordner und Tracks organisieren, Metadaten manuell pflegen oder MusicBrainz und AcoustID verwenden.
|
||||||
|
- Audio konvertieren und YouTube-Audio als MP3 importieren.
|
||||||
|
- TonUINO-Ordnerkarten, Sonderkarten und Emulator-Dumps erstellen.
|
||||||
|
- Musik in der App abspielen und TonUINO-Einstellungen über USB auslesen oder ändern, soweit die Firmware dies unterstützt.
|
||||||
|
- Sparkle-Updates manuell oder automatisch abrufen.
|
||||||
|
|
||||||
|
Benötigt macOS 13 oder neuer. Universal-App für Apple Silicon und Intel.
|
||||||
@@ -6,7 +6,7 @@ BUILD_DIR="$ROOT_DIR/.build"
|
|||||||
RELEASE_DIR="$BUILD_DIR/release"
|
RELEASE_DIR="$BUILD_DIR/release"
|
||||||
APP_VERSION="${APP_VERSION:?Set APP_VERSION, for example 1.0.1}"
|
APP_VERSION="${APP_VERSION:?Set APP_VERSION, for example 1.0.1}"
|
||||||
APP_BUILD="${APP_BUILD:?Set APP_BUILD to a monotonically increasing build number}"
|
APP_BUILD="${APP_BUILD:?Set APP_BUILD to a monotonically increasing build number}"
|
||||||
GITEA_TOKEN="${GITEA_TOKEN:?Set GITEA_TOKEN with permission to create releases}"
|
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||||
GITEA_API="${GITEA_API:-https://git.maddin.app/api/v1}"
|
GITEA_API="${GITEA_API:-https://git.maddin.app/api/v1}"
|
||||||
GITEA_REPO="${GITEA_REPO:-maddin/Tonuino-Manager}"
|
GITEA_REPO="${GITEA_REPO:-maddin/Tonuino-Manager}"
|
||||||
WEBSITE_HOST="${WEBSITE_HOST:-mln}"
|
WEBSITE_HOST="${WEBSITE_HOST:-mln}"
|
||||||
@@ -16,6 +16,16 @@ TAG="v$APP_VERSION"
|
|||||||
ZIP_PATH="$RELEASE_DIR/Tonuino-Manager-$APP_VERSION.zip"
|
ZIP_PATH="$RELEASE_DIR/Tonuino-Manager-$APP_VERSION.zip"
|
||||||
DMG_PATH="$RELEASE_DIR/Tonuino-Manager-$APP_VERSION-universal.dmg"
|
DMG_PATH="$RELEASE_DIR/Tonuino-Manager-$APP_VERSION-universal.dmg"
|
||||||
SIGN_UPDATE="${SPARKLE_SIGN_UPDATE:-$BUILD_DIR/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update}"
|
SIGN_UPDATE="${SPARKLE_SIGN_UPDATE:-$BUILD_DIR/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update}"
|
||||||
|
APPCAST_TMP=""
|
||||||
|
SITE_STAGE=""
|
||||||
|
GITEA_CREDENTIALS_FILE=""
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
[[ -z "$APPCAST_TMP" ]] || rm -f "$APPCAST_TMP"
|
||||||
|
[[ -z "$SITE_STAGE" ]] || rm -rf "$SITE_STAGE"
|
||||||
|
[[ -z "$GITEA_CREDENTIALS_FILE" ]] || rm -f "$GITEA_CREDENTIALS_FILE"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
[[ -f "$ZIP_PATH" ]] || { echo "Missing Sparkle archive: $ZIP_PATH" >&2; exit 1; }
|
[[ -f "$ZIP_PATH" ]] || { echo "Missing Sparkle archive: $ZIP_PATH" >&2; exit 1; }
|
||||||
[[ -f "$DMG_PATH" ]] || { echo "Missing installer: $DMG_PATH" >&2; exit 1; }
|
[[ -f "$DMG_PATH" ]] || { echo "Missing installer: $DMG_PATH" >&2; exit 1; }
|
||||||
@@ -29,6 +39,32 @@ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "Run this from a G
|
|||||||
[[ -z "$(git status --porcelain)" ]] || { echo "Commit or discard local changes before publishing" >&2; exit 1; }
|
[[ -z "$(git status --porcelain)" ]] || { echo "Commit or discard local changes before publishing" >&2; exit 1; }
|
||||||
git remote get-url origin >/dev/null 2>&1 || { echo "Configure origin to the Gitea repository first" >&2; exit 1; }
|
git remote get-url origin >/dev/null 2>&1 || { echo "Configure origin to the Gitea repository first" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [[ -n "$GITEA_TOKEN" ]]; then
|
||||||
|
GITEA_AUTH_ARGS=(-H "Authorization: token $GITEA_TOKEN")
|
||||||
|
else
|
||||||
|
GIT_CREDENTIAL="$(printf 'protocol=https\nhost=git.maddin.app\npath=%s.git\n\n' "$GITEA_REPO" | git credential fill 2>/dev/null)" || {
|
||||||
|
echo "Set GITEA_TOKEN or configure Git credentials for git.maddin.app" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
GITEA_USERNAME="$(sed -n 's/^username=//p' <<<"$GIT_CREDENTIAL")"
|
||||||
|
GITEA_PASSWORD="$(sed -n 's/^password=//p' <<<"$GIT_CREDENTIAL")"
|
||||||
|
[[ -n "$GITEA_USERNAME" && -n "$GITEA_PASSWORD" ]] || {
|
||||||
|
echo "Git credential helper did not return Gitea credentials" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
GITEA_CREDENTIALS_FILE="$(mktemp)"
|
||||||
|
chmod 600 "$GITEA_CREDENTIALS_FILE"
|
||||||
|
python3 - "$GITEA_CREDENTIALS_FILE" "$GITEA_USERNAME" "$GITEA_PASSWORD" <<'PY'
|
||||||
|
import sys
|
||||||
|
def netrc_quote(value):
|
||||||
|
return '"' + value.replace('\\', '\\\\').replace('"', '\\"') + '"'
|
||||||
|
path, username, password = sys.argv[1:]
|
||||||
|
with open(path, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write("machine git.maddin.app login " + netrc_quote(username) + " password " + netrc_quote(password) + "\n")
|
||||||
|
PY
|
||||||
|
GITEA_AUTH_ARGS=(--netrc-file "$GITEA_CREDENTIALS_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
|
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
|
||||||
echo "Release tag $TAG already exists" >&2
|
echo "Release tag $TAG already exists" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -38,7 +74,6 @@ SIGNATURE="$("$SIGN_UPDATE" -p "$ZIP_PATH")"
|
|||||||
ZIP_LENGTH="$(stat -f '%z' "$ZIP_PATH")"
|
ZIP_LENGTH="$(stat -f '%z' "$ZIP_PATH")"
|
||||||
RELEASE_URL="https://git.maddin.app/$GITEA_REPO/releases/download/$TAG/$(basename "$ZIP_PATH")"
|
RELEASE_URL="https://git.maddin.app/$GITEA_REPO/releases/download/$TAG/$(basename "$ZIP_PATH")"
|
||||||
APPCAST_TMP="$(mktemp)"
|
APPCAST_TMP="$(mktemp)"
|
||||||
trap 'rm -f "$APPCAST_TMP"' EXIT
|
|
||||||
python3 - "$APPCAST_TMP" "$APP_VERSION" "$APP_BUILD" "$RELEASE_URL" "$ZIP_LENGTH" "$SIGNATURE" <<'PY'
|
python3 - "$APPCAST_TMP" "$APP_VERSION" "$APP_BUILD" "$RELEASE_URL" "$ZIP_LENGTH" "$SIGNATURE" <<'PY'
|
||||||
import datetime, html, sys
|
import datetime, html, sys
|
||||||
path, version, build, url, length, signature = sys.argv[1:]
|
path, version, build, url, length, signature = sys.argv[1:]
|
||||||
@@ -64,21 +99,27 @@ xml = f'''<?xml version="1.0" encoding="utf-8"?>
|
|||||||
open(path, "w", encoding="utf-8").write(xml)
|
open(path, "w", encoding="utf-8").write(xml)
|
||||||
PY
|
PY
|
||||||
|
|
||||||
RELEASE_JSON="$(python3 - "$TAG" <<'PY'
|
NOTES_FILE="$ROOT_DIR/release-notes/$TAG.md"
|
||||||
|
RELEASE_JSON="$(python3 - "$TAG" "$NOTES_FILE" <<'PY'
|
||||||
import json, sys
|
import json, sys
|
||||||
tag = sys.argv[1]
|
tag, notes_path = sys.argv[1:]
|
||||||
print(json.dumps({"tag_name": tag, "target_commitish": "main", "name": "Tonuino Manager " + tag, "body": "Universal macOS-Version mit automatischen Sparkle-Updates.", "draft": False, "prerelease": False}))
|
try:
|
||||||
|
with open(notes_path, encoding="utf-8") as handle:
|
||||||
|
body = handle.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
body = "Universal macOS-Version mit automatischen Sparkle-Updates."
|
||||||
|
print(json.dumps({"tag_name": tag, "target_commitish": "main", "name": "Tonuino Manager " + tag, "body": body, "draft": False, "prerelease": False}))
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
RELEASE_RESPONSE="$(curl --fail-with-body --silent --show-error \
|
RELEASE_RESPONSE="$(curl --fail-with-body --silent --show-error \
|
||||||
-H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
|
"${GITEA_AUTH_ARGS[@]}" -H 'Content-Type: application/json' \
|
||||||
-d "$RELEASE_JSON" "$GITEA_API/repos/$GITEA_REPO/releases")"
|
-d "$RELEASE_JSON" "$GITEA_API/repos/$GITEA_REPO/releases")"
|
||||||
RELEASE_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$RELEASE_RESPONSE")"
|
RELEASE_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$RELEASE_RESPONSE")"
|
||||||
|
|
||||||
upload_asset() {
|
upload_asset() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
curl --fail-with-body --silent --show-error -X POST \
|
curl --fail-with-body --silent --show-error -X POST \
|
||||||
-H "Authorization: token $GITEA_TOKEN" \
|
"${GITEA_AUTH_ARGS[@]}" \
|
||||||
-F "attachment=@$file" \
|
-F "attachment=@$file" \
|
||||||
"$GITEA_API/repos/$GITEA_REPO/releases/$RELEASE_ID/assets?name=$(basename "$file")" >/dev/null
|
"$GITEA_API/repos/$GITEA_REPO/releases/$RELEASE_ID/assets?name=$(basename "$file")" >/dev/null
|
||||||
}
|
}
|
||||||
@@ -90,7 +131,6 @@ git commit -m "Publish Sparkle appcast for $TAG"
|
|||||||
git push origin main
|
git push origin main
|
||||||
|
|
||||||
SITE_STAGE="$(mktemp -d)"
|
SITE_STAGE="$(mktemp -d)"
|
||||||
trap 'rm -f "$APPCAST_TMP"; rm -rf "$SITE_STAGE"' EXIT
|
|
||||||
cp "$ROOT_DIR/website/icon.png" "$SITE_STAGE/icon.png"
|
cp "$ROOT_DIR/website/icon.png" "$SITE_STAGE/icon.png"
|
||||||
python3 - "$ROOT_DIR/website/index.html.template" "$SITE_STAGE/index.html" "$APP_VERSION" <<'PY'
|
python3 - "$ROOT_DIR/website/index.html.template" "$SITE_STAGE/index.html" "$APP_VERSION" <<'PY'
|
||||||
import html, sys
|
import html, sys
|
||||||
|
|||||||
Reference in new issue
Block a user