feat: release packaging — electron-builder installers + release pipeline

- electron-builder config: AppImage/deb (linux), dmg (mac), nsis (win)
- app icon, desktopName sync, deb maintainer metadata
- move electron to devDependencies; add author/license/repo metadata
- pin @noble/hashes 1.8.0 (electron-builder 26 ESM conflict)
- .github/workflows/release.yml: tag-triggered multi-OS build + release
- scripts/publish-release.sh: upload dist/ assets to a Gitea release
- README: installation + installer build instructions
- add missing .env.example
- verified locally: AppImage + deb build, deb structure checks out,
  118 JS tests still green
This commit is contained in:
Jarian Cottingham 2026-08-20 20:46:35 +00:00
parent e674e1f96d
commit 43114cfe3c
8 changed files with 2888 additions and 12 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
# TheTVDB v4 API token — https://www.thetvdb.com/docs
TVDB_API_KEY=

56
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: write
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
args: --linux
upload: "dist/*.AppImage dist/*.deb"
- os: macos-latest
args: --mac
upload: "dist/*.dmg"
- os: windows-latest
args: --win
upload: "dist/*.exe"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx electron-builder ${{ matrix.args }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.os }}
path: |
${{ matrix.upload }}
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish release
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
gh release create "$TAG" --title "$TAG" \
--notes "MovieMapper $TAG release." \
dist/*

View File

@ -86,9 +86,35 @@ directory scan ──▶ ffprobe metadata ──▶ tag (extra/commentary)
└── docs/ # Design docs, phase summaries, plans
```
## Installation
Installers are published on the [releases page](https://git.home.ms/jarianc/MovieMapper/releases)
(AppImage + .deb for Linux, .dmg for macOS, .exe for Windows).
### Linux — AppImage
```bash
chmod +x MovieMapper-*.AppImage
./MovieMapper-*.AppImage
```
### Linux — Debian/Ubuntu
```bash
sudo dpkg -i MovieMapper_*_amd64.deb
```
### Rust CLI
```bash
cargo install --path Rust
```
Requires Rust and `ffmpeg`/`ffprobe` on PATH (used for metadata extraction).
## Getting started
### Desktop app (Electron)
### Desktop app (Electron) — from source
Prerequisites: Node.js 20+, npm, `ffmpeg`/`ffprobe` on PATH.
@ -98,6 +124,15 @@ cp .env.example .env # add your TVDB_API_KEY
npm start
```
### Building installers
```bash
npm run dist # current platform
npm run dist:linux # AppImage + deb
npm run dist:mac # dmg (macOS only)
npm run dist:win # nsis exe
```
### Rust core
```bash

BIN
buildResources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

57
electron-builder.yml Normal file
View File

@ -0,0 +1,57 @@
appId: ms.jarian.moviemapper
productName: MovieMapper
copyright: Copyright © 2026 Jarian Cottingham
directories:
output: dist
buildResources: buildResources
files:
- main.js
- preload.js
- renderer.js
- index.html
- utils/**/*
- package.json
asar: true
# The app shells out to ffprobe/ffmpeg for metadata extraction. It expects
# them on PATH (documented in the README) rather than bundling them.
linux:
syncDesktopName: true
target:
- target: AppImage
arch: [x64]
- target: deb
arch: [x64]
category: Utility
synopsis: Organize movie and TV collections
description: Desktop app for organizing movie and TV show collections with Jellyfin-compatible naming, TheTVDB metadata, and audit logging.
maintainer: jarianc@proton.me
icon: buildResources/icon.png
mac:
target:
- target: dmg
arch: [x64, arm64]
category: public.app-category.video
icon: buildResources/icon.png
# Notarization requires an Apple ID + team; configured via env in CI.
win:
target:
- target: nsis
arch: [x64]
icon: buildResources/icon.png
nsis:
oneClick: false
perMachine: false
allowToChangeInstallationDirectory: true
createDesktopShortcut: true
# Publishing is driven by the release workflow, which uploads the built
# artifacts to a Gitea/GitHub release. Keep the default off for local builds.
publish: null

2675
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,20 +2,36 @@
"name": "MovieMapper",
"version": "1.0.0",
"description": "A desktop application for organizing and managing movie and TV show collections",
"author": "Jarian Cottingham <jarianc@proton.me>",
"license": "MIT",
"homepage": "https://git.home.ms/jarianc/MovieMapper",
"desktopName": "MovieMapper",
"repository": {
"type": "git",
"url": "https://git.home.ms/jarianc/MovieMapper.git"
},
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "vitest run --coverage"
"test": "vitest run --coverage",
"dist": "electron-builder",
"dist:linux": "electron-builder --linux",
"dist:mac": "electron-builder --mac",
"dist:win": "electron-builder --win"
},
"dependencies": {
"axios": "^1.13.5",
"dotenv": "^17.3.1",
"electron": "^40.4.1",
"fluent-ffmpeg": "^2.1.3"
},
"devDependencies": {
"@vitest/coverage-v8": "^3.2.6",
"electron": "^40.4.1",
"electron-builder": "^26.0.12",
"jsdom": "^25.0.1",
"vitest": "^3.2.6"
},
"overrides": {
"@noble/hashes": "1.8.0"
}
}

53
scripts/publish-release.sh Executable file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# Publish built artifacts (dist/) as a Gitea release with attached assets.
#
# Usage:
# GITEA_PASS=... ./scripts/publish-release.sh v1.0.0
#
# Environment:
# GITEA_URL (default: https://git.home.ms)
# GITEA_USER (default: jarianc)
# GITEA_PASS (required)
# GITEA_REPO (default: jarianc/MovieMapper)
set -euo pipefail
TAG="${1:?usage: publish-release.sh <tag> (e.g. v1.0.0)}"
GITEA_URL="${GITEA_URL:-https://git.home.ms}"
GITEA_USER="${GITEA_USER:-jarianc}"
GITEA_PASS="${GITEA_PASS:?GITEA_PASS is required}"
GITEA_REPO="${GITEA_REPO:-jarianc/MovieMapper}"
AUTH=("${GITEA_USER}:${GITEA_PASS}")
API="$GITEA_URL/api/v1/repos/$GITEA_REPO"
# Create the release if it does not exist yet.
existing="$(curl -sf -u "${AUTH[@]}" "$API/releases/tags/$TAG" 2>/dev/null \
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' || true)"
if [ -z "${existing}" ]; then
curl -sf -u "${AUTH[@]}" -X POST "$API/releases" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"MovieMapper $TAG release. Linux: AppImage + deb. See README for install instructions.\"}" \
> /dev/null
echo "created release $TAG"
fi
uploaded=0
for f in dist/*.AppImage dist/*.deb dist/*.dmg dist/*.exe dist/*.zip; do
[ -e "$f" ] || continue
if curl -sf -u "${AUTH[@]}" -X POST "$API/releases/$TAG/assets" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$f" > /dev/null; then
echo "uploaded $(basename "$f")"
uploaded=$((uploaded + 1))
else
echo "FAILED to upload $f" >&2
exit 1
fi
done
if [ "$uploaded" -eq 0 ]; then
echo "no artifacts found in dist/ — run npm run dist first" >&2
exit 1
fi
echo "done: $uploaded assets on release $TAG"