fix(release): ship prebuilt assets and bump fallback version

Two release-blocking bugs traced to the same root cause: the unanchored
`data/` rule in .gitignore matched server/src/ledgrab/data/, which is
where shipped package assets live (prebuilt sounds, game adapters).
The files were never `git add`-able without -f, so they never reached
the v0.4.2 tag and CI builds couldn't include them.

- .gitignore: anchor /data/ and /server/data/ so nested package data
  dirs are not ignored.
- Track previously-excluded shipped assets:
  - server/src/ledgrab/data/prebuilt_sounds/{alert,bell,chime,ping,pop}.wav
  - server/src/ledgrab/data/game_adapters/{minecraft,rocket_league,valorant}.yaml
- Bump _FALLBACK_VERSION 0.3.0 -> 0.4.2 to match pyproject.toml.
  The Windows installer strips ledgrab-*.dist-info, so
  importlib.metadata falls back to this literal — which is why
  v0.4.2 reports v0.3.0 in the WebUI.
- Patch _FALLBACK_VERSION at bundle time in build-common.sh and
  build-dist.ps1 so future drift is auto-corrected by the build.
This commit is contained in:
2026-04-22 20:17:10 +03:00
parent a8a4296a56
commit 5db6eddcf8
12 changed files with 296 additions and 5 deletions
+6 -2
View File
@@ -62,8 +62,12 @@ htmlcov/
logs/ logs/
*.log.* *.log.*
# Runtime data # Runtime data — anchor to repo root so nested package data dirs
data/ # (server/src/ledgrab/data/prebuilt_sounds, game_adapters) are NOT ignored.
# An unanchored `data/` rule silently broke the v0.4.2 release by keeping
# shipped sound assets out of the CI tag checkout.
/data/
/server/data/
*.db *.db
*.sqlite *.sqlite
*.json.bak *.json.bak
+10
View File
@@ -69,6 +69,16 @@ copy_app_files() {
# Clean up source maps and __pycache__ # Clean up source maps and __pycache__
find "$APP_DIR" -name "*.map" -delete 2>/dev/null || true find "$APP_DIR" -name "*.map" -delete 2>/dev/null || true
find "$APP_DIR" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true find "$APP_DIR" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
# Patch the fallback version in the bundled __init__.py. Bundled installs
# strip ledgrab-*.dist-info from site-packages, so importlib.metadata
# falls back to this literal at runtime — and a stale literal is what
# silently shipped v0.4.2 reporting "0.3.0" in the WebUI.
local bundled_init="$APP_DIR/src/ledgrab/__init__.py"
if [ -f "$bundled_init" ] && [ -n "${VERSION_CLEAN:-}" ]; then
sed -i "s/_FALLBACK_VERSION = \"[^\"]*\"/_FALLBACK_VERSION = \"${VERSION_CLEAN}\"/" "$bundled_init"
echo " Patched _FALLBACK_VERSION -> ${VERSION_CLEAN}"
fi
} }
# ── Site-packages cleanup ──────────────────────────────────── # ── Site-packages cleanup ────────────────────────────────────
+11
View File
@@ -196,6 +196,17 @@ New-Item -ItemType Directory -Path (Join-Path $DistDir "logs") -Force | Out-Null
Get-ChildItem -Path $srcDest -Recurse -Filter "*.map" | Remove-Item -Force -ErrorAction SilentlyContinue Get-ChildItem -Path $srcDest -Recurse -Filter "*.map" | Remove-Item -Force -ErrorAction SilentlyContinue
Get-ChildItem -Path $srcDest -Recurse -Directory -Filter "__pycache__" | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue Get-ChildItem -Path $srcDest -Recurse -Directory -Filter "__pycache__" | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
# Patch the fallback version in the bundled __init__.py so the WebUI always
# reports the release version — the installer strips ledgrab-*.dist-info from
# site-packages (above), so importlib.metadata falls back to this literal.
$bundledInit = Join-Path $srcDest "ledgrab\__init__.py"
if (Test-Path $bundledInit) {
$initContent = Get-Content $bundledInit -Raw
$patched = [regex]::Replace($initContent, '_FALLBACK_VERSION\s*=\s*"[^"]*"', "_FALLBACK_VERSION = `"$VersionClean`"")
Set-Content -Path $bundledInit -Value $patched -NoNewline
Write-Host " Patched _FALLBACK_VERSION -> $VersionClean"
}
# ── Create launcher ──────────────────────────────────────────── # ── Create launcher ────────────────────────────────────────────
Write-Host "[8/8] Creating launcher..." Write-Host "[8/8] Creating launcher..."
+7 -3
View File
@@ -2,10 +2,14 @@
from importlib.metadata import version, PackageNotFoundError from importlib.metadata import version, PackageNotFoundError
# Fallback version — kept in sync with pyproject.toml. # Fallback version — kept in sync with pyproject.toml. MUST match the
# version declared there on every release. The Windows installer build
# (build/build-dist.ps1) also patches this literal to the resolved build
# version, so any drift here is corrected for bundled distributions.
# Used when the package isn't pip-installed (e.g. embedded via Chaquopy # Used when the package isn't pip-installed (e.g. embedded via Chaquopy
# on Android, where the source is included directly via source sets). # on Android, where the source is included directly via source sets, or
_FALLBACK_VERSION = "0.3.0" # in the Windows bundle where the installed dist-info is stripped).
_FALLBACK_VERSION = "0.4.2"
try: try:
__version__ = version("ledgrab") __version__ = version("ledgrab")
@@ -0,0 +1,78 @@
# Minecraft community adapter
# Requires a server-side mod that sends game state via webhook
# (e.g., GameStateIntegration mod or custom Fabric/Forge mod)
#
# Configure your mod to POST JSON to:
# http://<WLED_IP>:8080/api/v1/game-integrations/<ID>/event
name: minecraft
game: Minecraft
protocol: webhook
mappings:
- source_path: player.health
event: health
min: 0
max: 20
trigger: on_change
- source_path: player.armor
event: armor
min: 0
max: 20
trigger: on_change
- source_path: player.food_level
event: energy
min: 0
max: 20
trigger: on_change
- source_path: player.experience_level
event: speed
min: 0
max: 100
trigger: on_change
- source_path: player.deaths
event: death
trigger: on_increase
min: 0
max: 100
- source_path: stats.kills
event: kill
trigger: on_increase
min: 0
max: 100
auth:
type: header
header: X-Minecraft-Auth
setup_instructions: |
## Minecraft Integration Setup
This adapter requires a server-side mod that sends game state data as JSON.
**Recommended mods:**
- [GameStateIntegration](https://github.com/example/gsi-mod) (Fabric)
- Custom Forge mod using `PlayerTickEvent`
**Expected JSON format:**
```json
{
"player": {
"health": 20.0,
"armor": 10,
"food_level": 18,
"experience_level": 30
},
"stats": {
"kills": 5
}
}
```
Configure the mod to POST to the event endpoint with the auth token
in the `X-Minecraft-Auth` header.
@@ -0,0 +1,99 @@
# Rocket League community adapter
# Uses the SOS (Rocket League Overlay System) plugin
# https://gitlab.com/bakkesplugins/sos/sos-plugin
#
# SOS sends game state via WebSocket, but you can use a bridge
# to forward events as HTTP POST to:
# http://<WLED_IP>:8080/api/v1/game-integrations/<ID>/event
name: rocket_league
game: Rocket League
protocol: webhook
mappings:
- source_path: player.boost
event: energy
min: 0
max: 100
trigger: on_change
- source_path: player.speed
event: speed
min: 0
max: 2300
trigger: on_value
- source_path: match.goals_scored
event: kill
trigger: on_increase
min: 0
max: 20
- source_path: match.goals_conceded
event: death
trigger: on_increase
min: 0
max: 20
- source_path: match.time_remaining
event: objective_progress
min: 0
max: 300
trigger: on_value
- source_path: game.started
event: match_start
trigger: on_change
min: 0
max: 1
- source_path: game.ended
event: match_end
trigger: on_change
min: 0
max: 1
- source_path: team.score_blue
event: team_a
min: 0
max: 10
trigger: on_change
- source_path: team.score_orange
event: team_b
min: 0
max: 10
trigger: on_change
setup_instructions: |
## Rocket League Integration Setup
This adapter works with the SOS (Rocket League Overlay System) plugin.
**Setup:**
1. Install BakkesMod: https://bakkesmod.com
2. Install the SOS plugin from the BakkesMod plugin manager
3. Use a WebSocket-to-HTTP bridge to forward SOS events
**Bridge tool:**
A small script that connects to SOS WebSocket (ws://localhost:49122)
and forwards events as HTTP POST to the WLED event endpoint.
**Expected JSON format:**
```json
{
"player": {
"boost": 75,
"speed": 1500
},
"match": {
"goals_scored": 2,
"goals_conceded": 1,
"time_remaining": 180
},
"team": {
"score_blue": 2,
"score_orange": 1
}
}
```
@@ -0,0 +1,85 @@
# Valorant community adapter
# Uses Overwolf/Insights API or third-party overlay tool
# that exposes game state via webhook
#
# Configure your overlay to POST JSON to:
# http://<WLED_IP>:8080/api/v1/game-integrations/<ID>/event
name: valorant
game: Valorant
protocol: webhook
mappings:
- source_path: player.health
event: health
min: 0
max: 100
trigger: on_change
- source_path: player.shield
event: shield
min: 0
max: 50
trigger: on_change
- source_path: player.money
event: gold
min: 0
max: 9000
trigger: on_change
- source_path: match.kills
event: kill
trigger: on_increase
min: 0
max: 50
- source_path: match.deaths
event: death
trigger: on_increase
min: 0
max: 50
- source_path: match.round_phase
event: round_start
trigger: on_change
min: 0
max: 1
- source_path: match.spike_planted
event: objective_captured
trigger: on_change
min: 0
max: 1
auth:
type: header
header: X-Valorant-Auth
setup_instructions: |
## Valorant Integration Setup
Valorant does not have a native Game State Integration API.
You need a third-party tool to capture and forward game data.
**Options:**
- Overwolf with a game events plugin
- Insights.gg capture API
- Custom screen-reading overlay
**Expected JSON format:**
```json
{
"player": {
"health": 100,
"shield": 50,
"money": 3900
},
"match": {
"kills": 12,
"deaths": 5,
"round_phase": 1,
"spike_planted": 0
}
}
```
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.