feat: add release build script

- scripts/build-release.sh: builds, signs, and copies .app to dist/release/
- Bundle targets narrowed to app + dmg
- npm run release convenience script
- README documents the release build workflow

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 13:33:54 -04:00
parent 30b04b42d4
commit 1ea7f19668
5 changed files with 96 additions and 2 deletions

1
.gitignore vendored
View File

@@ -9,5 +9,6 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
dist/
.cursor/
.vscode/

View File

@@ -33,6 +33,16 @@ npm run tauri dev
npm run tauri build
```
## Release Build
```bash
npm run release
```
This runs the full Tauri production build, ad-hoc code signs the `.app` bundle, and copies the output to `dist/release/`.
The resulting `GUI Video Clipper.app` can be launched directly or dragged to `/Applications`.
## Architecture
GUI Video Clipper is built with [Tauri v2](https://v2.tauri.app/) (Rust backend) and [Svelte 5](https://svelte.dev/) (TypeScript frontend).

View File

@@ -9,7 +9,8 @@
"preview": "vite preview",
"test": "vitest run",
"check": "svelte-check --tsconfig ./tsconfig.json",
"tauri": "tauri"
"tauri": "tauri",
"release": "bash scripts/build-release.sh"
},
"license": "MIT",
"dependencies": {

82
scripts/build-release.sh Executable file
View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION_FILE="$PROJECT_ROOT/VERSION"
# ── Read and validate version ──────────────────────────────────────
if [ ! -f "$VERSION_FILE" ]; then
echo "ERROR: VERSION file not found" >&2
exit 1
fi
VERSION=$(cat "$VERSION_FILE" | sed 's/^[vV]//' | tr -d '[:space:]')
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "ERROR: '$VERSION' is not valid semver (expected X.Y.Z)" >&2
exit 1
fi
echo "=== Building GUI Video Clipper v$VERSION ==="
echo ""
# ── Install frontend dependencies ──────────────────────────────────
echo "→ Installing dependencies…"
cd "$PROJECT_ROOT"
npm install --silent
# ── Run tauri build ────────────────────────────────────────────────
echo "→ Building release (this may take a few minutes)…"
BUILD_LOG=$(mktemp)
npx tauri build 2>&1 | tee "$BUILD_LOG" | tail -10
# ── Locate built artifacts ─────────────────────────────────────────
# Parse .app path from tauri build output (handles custom CARGO_TARGET_DIR)
APP_SRC=$(grep '\.app$' "$BUILD_LOG" | head -1 | sed 's/^[[:space:]]*//')
DMG_SRC=$(grep '\.dmg$' "$BUILD_LOG" | head -1 | sed 's/^[[:space:]]*//')
rm -f "$BUILD_LOG"
# Fall back to standard location if parsing failed
if [ -z "$APP_SRC" ] || [ ! -d "$APP_SRC" ]; then
BUNDLE_DIR="$PROJECT_ROOT/src-tauri/target/release/bundle"
APP_SRC="$BUNDLE_DIR/macos/GUI Video Clipper.app"
fi
if [ ! -d "$APP_SRC" ]; then
echo "ERROR: .app bundle not found" >&2
echo "Check the build output above for errors." >&2
exit 1
fi
# ── Ad-hoc code sign ──────────────────────────────────────────────
echo "→ Ad-hoc signing .app…"
codesign --force --deep -s - "$APP_SRC"
# ── Copy to dist/release/ ─────────────────────────────────────────
DIST_DIR="$PROJECT_ROOT/dist/release"
mkdir -p "$DIST_DIR"
echo "→ Copying to dist/release/…"
rm -rf "$DIST_DIR/GUI Video Clipper.app"
cp -R "$APP_SRC" "$DIST_DIR/"
# Copy DMG if it was built
DMG_FILE="$DMG_SRC"
if [ -n "$DMG_FILE" ] && [ -f "$DMG_FILE" ]; then
cp "$DMG_FILE" "$DIST_DIR/"
echo " ✓ DMG: $(basename "$DMG_FILE")"
fi
# ── Summary ────────────────────────────────────────────────────────
APP_SIZE=$(du -sh "$DIST_DIR/GUI Video Clipper.app" | cut -f1)
echo ""
echo "=== Build Complete ==="
echo " Version: v$VERSION"
echo " App: dist/release/GUI Video Clipper.app ($APP_SIZE)"
if [ -n "$DMG_FILE" ] && [ -f "$DIST_DIR/$(basename "$DMG_FILE")" ]; then
DMG_SIZE=$(du -sh "$DIST_DIR/$(basename "$DMG_FILE")" | cut -f1)
echo " DMG: dist/release/$(basename "$DMG_FILE") ($DMG_SIZE)"
fi
echo ""
echo "To run: open \"dist/release/GUI Video Clipper.app\""

View File

@@ -34,7 +34,7 @@
},
"bundle": {
"active": true,
"targets": "all",
"targets": ["app", "dmg"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",