Skip to content

Commit 2552bc6

Browse files
committed
feat: add DMG installer and GitHub Release workflow
- Add build-dmg.sh script using create-dmg - Volume icon from app icon via iconutil - Clean Finder layout with app and Applications shortcut - Add release.yml workflow triggered on version tags (v*) - Add 'make dmg' command to Makefile
1 parent 13393df commit 2552bc6

4 files changed

Lines changed: 174 additions & 2 deletions

File tree

.github/workflows/release.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
build-and-release:
13+
name: Build DMG & Create Release
14+
runs-on: macos-15
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Install create-dmg
20+
run: brew install create-dmg
21+
22+
- name: Build DMG
23+
run: |
24+
chmod +x Scripts/build-dmg.sh
25+
./Scripts/build-dmg.sh
26+
27+
- name: Get version from tag
28+
id: version
29+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
30+
31+
- name: Create GitHub Release
32+
uses: softprops/action-gh-release@v2
33+
with:
34+
name: "QuotaBar v${{ steps.version.outputs.version }}"
35+
draft: false
36+
prerelease: false
37+
generate_release_notes: true
38+
files: build/QuotaBar-*.dmg

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ Package.resolved
3535
*.p12
3636
*.key
3737
Secrets.swift
38+
39+
# ─── Build Artifacts ─────────────────────────────────────────────────────────
40+
*.dmg

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: setup lint fix build clean
1+
.PHONY: setup lint fix build release dmg clean
22

33
# One-time setup: installs SwiftLint and git hooks
44
setup:
@@ -21,8 +21,13 @@ build:
2121
release:
2222
@xcodebuild -scheme QuotaBar -configuration Release build | tail -5
2323

24+
# Build DMG installer
25+
dmg:
26+
@chmod +x Scripts/build-dmg.sh
27+
@./Scripts/build-dmg.sh
28+
2429
# Clean build artifacts
2530
clean:
2631
@xcodebuild -scheme QuotaBar clean 2>/dev/null || true
27-
@rm -rf DerivedData
32+
@rm -rf DerivedData build
2833
@echo "✅ Cleaned"

Scripts/build-dmg.sh

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
#───────────────────────────────────────────────────────────────────────────────
5+
# QuotaBar — DMG Installer Builder
6+
#
7+
# Prerequisites:
8+
# brew install create-dmg
9+
#
10+
# Usage:
11+
# ./Scripts/build-dmg.sh # builds Release .app then packages DMG
12+
# ./Scripts/build-dmg.sh --skip-build # package DMG from existing build
13+
#───────────────────────────────────────────────────────────────────────────────
14+
15+
APP_NAME="QuotaBar"
16+
SCHEME="QuotaBar"
17+
CONFIG="Release"
18+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
20+
BUILD_DIR="${PROJECT_DIR}/build"
21+
DMG_DIR="${BUILD_DIR}/dmg"
22+
APP_PATH="${DMG_DIR}/${APP_NAME}.app"
23+
24+
# Get version from Xcode project
25+
VERSION=$(grep 'MARKETING_VERSION' "${PROJECT_DIR}/QuotaBar.xcodeproj/project.pbxproj" \
26+
| head -1 | sed 's/.*= //' | sed 's/;//' | tr -d '[:space:]')
27+
DMG_NAME="${APP_NAME}-${VERSION}.dmg"
28+
DMG_PATH="${BUILD_DIR}/${DMG_NAME}"
29+
30+
echo ""
31+
echo "╔══════════════════════════════════════════════════╗"
32+
echo "║ QuotaBar DMG Builder ║"
33+
echo "║ Version: ${VERSION}"
34+
echo "╚══════════════════════════════════════════════════╝"
35+
echo ""
36+
37+
# ── Step 0: Check dependencies ──────────────────────────────────────────────
38+
if ! command -v create-dmg &>/dev/null; then
39+
echo "❌ create-dmg not found. Install with: brew install create-dmg"
40+
exit 1
41+
fi
42+
43+
# ── Step 1: Build the app ───────────────────────────────────────────────────
44+
SKIP_BUILD=false
45+
if [[ "${1:-}" == "--skip-build" ]]; then
46+
SKIP_BUILD=true
47+
fi
48+
49+
if [[ "$SKIP_BUILD" == false ]]; then
50+
echo "🔨 Building ${APP_NAME} (${CONFIG})..."
51+
xcodebuild \
52+
-scheme "$SCHEME" \
53+
-configuration "$CONFIG" \
54+
-derivedDataPath "${BUILD_DIR}/DerivedData" \
55+
-arch arm64 -arch x86_64 \
56+
ONLY_ACTIVE_ARCH=NO \
57+
clean build 2>&1 | tail -3
58+
59+
echo "✅ Build succeeded"
60+
else
61+
echo "⏩ Skipping build (--skip-build)"
62+
fi
63+
64+
# ── Step 2: Locate and copy .app ────────────────────────────────────────────
65+
echo "📦 Preparing DMG staging area..."
66+
67+
BUILT_APP=$(find "${BUILD_DIR}/DerivedData" -name "${APP_NAME}.app" -type d \
68+
-path "*/Release/*" 2>/dev/null | head -1)
69+
70+
if [[ -z "$BUILT_APP" ]]; then
71+
echo "❌ Could not find ${APP_NAME}.app in build output"
72+
echo " Searched in: ${BUILD_DIR}/DerivedData"
73+
exit 1
74+
fi
75+
76+
rm -rf "$DMG_DIR"
77+
mkdir -p "$DMG_DIR"
78+
cp -R "$BUILT_APP" "$APP_PATH"
79+
80+
echo " App: $(du -sh "$APP_PATH" | cut -f1)${APP_PATH}"
81+
82+
# ── Step 3: Build the DMG ────────────────────────────────────────────────────
83+
echo "💿 Creating DMG..."
84+
85+
# Remove existing DMG if present
86+
rm -f "$DMG_PATH"
87+
88+
# Build .icns from app icon PNGs for the volume icon (title bar)
89+
ICON_DIR="${BUILD_DIR}/icon.iconset"
90+
ICNS_PATH="${BUILD_DIR}/VolumeIcon.icns"
91+
ICON_SRC="${PROJECT_DIR}/QuotaBar/Assets.xcassets/AppIcon.appiconset"
92+
mkdir -p "$ICON_DIR"
93+
cp "${ICON_SRC}/icon_16x16.png" "${ICON_DIR}/icon_16x16.png"
94+
cp "${ICON_SRC}/icon_32x32.png" "${ICON_DIR}/icon_16x16@2x.png"
95+
cp "${ICON_SRC}/icon_32x32.png" "${ICON_DIR}/icon_32x32.png"
96+
cp "${ICON_SRC}/icon_64x64.png" "${ICON_DIR}/icon_32x32@2x.png"
97+
cp "${ICON_SRC}/icon_128x128.png" "${ICON_DIR}/icon_128x128.png"
98+
cp "${ICON_SRC}/icon_256x256.png" "${ICON_DIR}/icon_128x128@2x.png"
99+
cp "${ICON_SRC}/icon_256x256.png" "${ICON_DIR}/icon_256x256.png"
100+
cp "${ICON_SRC}/icon_512x512.png" "${ICON_DIR}/icon_256x256@2x.png"
101+
cp "${ICON_SRC}/icon_512x512.png" "${ICON_DIR}/icon_512x512.png"
102+
cp "${ICON_SRC}/icon_1024x1024.png" "${ICON_DIR}/icon_512x512@2x.png"
103+
iconutil -c icns "$ICON_DIR" -o "$ICNS_PATH"
104+
105+
create-dmg \
106+
--volname "${APP_NAME}" \
107+
--volicon "$ICNS_PATH" \
108+
--window-pos 200 120 \
109+
--window-size 540 380 \
110+
--icon-size 128 \
111+
--icon "${APP_NAME}.app" 150 180 \
112+
--hide-extension "${APP_NAME}.app" \
113+
--app-drop-link 390 180 \
114+
--icon ".VolumeIcon.icns" 999 999 \
115+
--text-size 14 \
116+
--no-internet-enable \
117+
"$DMG_PATH" \
118+
"$DMG_DIR"
119+
120+
echo ""
121+
echo "═══════════════════════════════════════════════════"
122+
echo " ✅ DMG created successfully!"
123+
echo " 📍 ${DMG_PATH}"
124+
echo " 📏 $(du -sh "$DMG_PATH" | cut -f1)"
125+
echo "═══════════════════════════════════════════════════"
126+
echo ""

0 commit comments

Comments
 (0)