Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
43 changes: 37 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,44 @@ jobs:
- name: Checkout
uses: actions/checkout@v5

- name: Setup Swift 6.3.1
uses: swift-actions/setup-swift@v3
with:
swift-version: "6.3.1"
- name: Test shared data contracts
run: swift test --package-path Packages/PlexData

- name: Build
run: swift build
run: |
xcodebuild \
-project PlexBar.xcodeproj \
-scheme PlexBar \
-configuration Debug \
-destination 'platform=macOS' \
-derivedDataPath build/DerivedData \
-clonedSourcePackagesDirPath .build/SourcePackages \
-onlyUsePackageVersionsFromResolvedFile \
CODE_SIGNING_ALLOWED=NO \
build

- name: Test
run: swift test
run: |
xcodebuild \
-project PlexBar.xcodeproj \
-scheme PlexBar \
-configuration Debug \
-destination 'platform=macOS' \
-derivedDataPath build/DerivedData \
-clonedSourcePackagesDirPath .build/SourcePackages \
-onlyUsePackageVersionsFromResolvedFile \
CODE_SIGNING_ALLOWED=NO \
test

- name: Build and test Studio
run: |
xcodebuild \
-project PlexBar.xcodeproj \
-scheme PlexBarStudio \
-configuration Debug \
-destination 'platform=macOS' \
-derivedDataPath build/StudioDerivedData \
-clonedSourcePackagesDirPath .build/SourcePackages \
-onlyUsePackageVersionsFromResolvedFile \
CODE_SIGNING_ALLOWED=NO \
test
97 changes: 26 additions & 71 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
echo "RELEASE_VERSION=$VERSION" >> "$GITHUB_ENV"
echo "BUILD_NUMBER=$GITHUB_RUN_NUMBER" >> "$GITHUB_ENV"
echo "IS_PRERELEASE=$IS_PRERELEASE" >> "$GITHUB_ENV"
echo "DMG_PATH=$RUNNER_TEMP/PlexBar-${TAG}-arm64.dmg" >> "$GITHUB_ENV"
echo "DMG_PATH=$RUNNER_TEMP/PlexBar-${TAG}-universal.dmg" >> "$GITHUB_ENV"
echo "release_tag=$TAG" >> "$GITHUB_OUTPUT"
echo "release_version=$VERSION" >> "$GITHUB_OUTPUT"
echo "build_number=$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -90,11 +90,6 @@ jobs:

echo "RELEASE_NOTES_PATH=$RELEASE_NOTES_PATH" >> "$GITHUB_ENV"

- name: Setup Swift 6.3.1
uses: swift-actions/setup-swift@v3
with:
swift-version: "6.3.1"

- name: Install create-dmg
env:
HOMEBREW_NO_AUTO_UPDATE: 1
Expand Down Expand Up @@ -131,11 +126,32 @@ jobs:
"$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db

- name: Build release app bundle
SIGNING_IDENTITY="$(
security find-identity -v -p codesigning "$KEYCHAIN_PATH" |
sed -nE 's/^[[:space:]]*[0-9]+\) [0-9A-F]+ "(Developer ID Application:.*)"$/\1/p' |
head -n 1
)"

if [[ -z "$SIGNING_IDENTITY" ]]; then
echo "No Developer ID Application identity was imported"
exit 1
fi

if [[ ! "$SIGNING_IDENTITY" =~ \(([A-Z0-9]{10})\)$ ]]; then
echo "Could not derive the Apple team identifier from the signing identity"
exit 1
fi

echo "CODE_SIGN_IDENTITY=$SIGNING_IDENTITY" >> "$GITHUB_ENV"
echo "CODE_SIGN_KEYCHAIN=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
echo "APPLE_TEAM_ID=${BASH_REMATCH[1]}" >> "$GITHUB_ENV"

- name: Build and verify release app bundle
env:
BUILD_CONFIGURATION: release
APP_MARKETING_VERSION: ${{ env.RELEASE_VERSION }}
APP_BUILD_VERSION: ${{ env.BUILD_NUMBER }}
run: script/build_and_run.sh build
run: script/build_and_run.sh verify

- name: Validate Sparkle release configuration
run: |
Expand Down Expand Up @@ -173,67 +189,6 @@ jobs:
/usr/libexec/PlistBuddy -c "Print :SUVerifyUpdateBeforeExtraction" dist/PlexBar.app/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Print :SUEnableAutomaticChecks" dist/PlexBar.app/Contents/Info.plist

- name: Sign app bundle
run: |
APP_PATH="dist/PlexBar.app"
SPARKLE_FRAMEWORK="$APP_PATH/Contents/Frameworks/Sparkle.framework"
CODE_SIGN_IDENTITY="Developer ID Application"

if [[ ! -d "$APP_PATH" ]]; then
echo "App bundle not found at $APP_PATH"
exit 1
fi

if [[ ! -d "$SPARKLE_FRAMEWORK" ]]; then
echo "Sparkle framework not found at $SPARKLE_FRAMEWORK"
exit 1
fi

codesign --force \
--options runtime \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$SPARKLE_FRAMEWORK/Versions/B/XPCServices/Installer.xpc"

if [[ -d "$SPARKLE_FRAMEWORK/Versions/B/XPCServices/Downloader.xpc" ]]; then
codesign --force \
--options runtime \
--preserve-metadata=entitlements \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$SPARKLE_FRAMEWORK/Versions/B/XPCServices/Downloader.xpc"
fi

codesign --force \
--options runtime \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$SPARKLE_FRAMEWORK/Versions/B/Autoupdate"

codesign --force \
--options runtime \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$SPARKLE_FRAMEWORK/Versions/B/Updater.app"

codesign --force \
--options runtime \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$SPARKLE_FRAMEWORK"

codesign --force \
--options runtime \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$APP_PATH"

- name: Create signed DMG
run: |
DMG_BACKGROUND_PATH=".github/release-assets/background.png"
Expand Down Expand Up @@ -276,7 +231,7 @@ jobs:
- name: Sign DMG
run: |
codesign --force \
--sign "Developer ID Application" \
--sign "$CODE_SIGN_IDENTITY" \
--keychain "$KEYCHAIN_PATH" \
--timestamp \
"$DMG_PATH"
Expand Down Expand Up @@ -368,7 +323,7 @@ jobs:
env:
SPARKLE_PRIVATE_KEY_BASE64: ${{ secrets.SPARKLE_PRIVATE_KEY_BASE64 }}
run: |
SPARKLE_BIN=".build/artifacts/sparkle/Sparkle/bin/generate_appcast"
SPARKLE_BIN=".build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_appcast"
APPCAST_WORK_DIR="appcast-work"
PAGES_DIR="pages"
SPARKLE_KEY_PATH="$RUNNER_TEMP/sparkle-ed25519-private-key"
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
/.build
/build
/dist
/Packages
/Packages/**/.build/
/Packages/**/.swiftpm/
xcuserdata/
DerivedData/
*.xcresult
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Expand All @@ -17,3 +19,6 @@ Icon[^a-zA-Z]

# docs
/docs/plans

# Studio keeps unaccepted generations and local history out of version control.
/PlexBar/Resources/MockServer/.studio/
21 changes: 13 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@ This file defines project constraints for coding agents working in this reposito

## Platform + App Contract

- PlexBar is a macOS-only app built with SwiftPM.
- PlexBar is a macOS-only app built by the `PlexBar` target in `PlexBar.xcodeproj`.
- Minimum supported platform is macOS 26+.
- The app is menu-bar-first and should remain an accessory app without a Dock icon unless otherwise explicitly requested.
- PlexBar is one regular macOS app with a primary window and a persistent menu-bar extra. The window and menu-bar UI must share the same stores and services.
- UI work should stay SwiftUI-first.
- Do not introduce AppKit UI implementations unless a maintainer explicitly asks.
- Use native Apple media frameworks. `AVPlayerView` may be bridged into SwiftUI where SwiftUI has no equivalent macOS player view; do not replace native controls with a custom imitation.
- Do not introduce web views, cross-platform UI layers, or third-party playback engines.

## Project Boundaries

- App sources live in `Sources/PlexBar/`.
- Tests live in `Tests/PlexBarTests/`.
- PlexBar app sources live in `PlexBar/`; its integration tests live in `PlexBarTests/`.
- Studio is a separate macOS app target, `PlexBarStudio`, with sources in `Studio/`, tests in `StudioTests/`, and its own README in `Studio/README.md`.
- Shared Plex models and mock-data contracts live in the local `Packages/PlexData` package. Apps import its products; do not add cross-app source-file membership or duplicate shared types.
- Package tests live in `Packages/PlexData/Tests/` and run with `swift test --package-path Packages/PlexData`.
- Keep package code independent of app UI, stores, authentication, playback orchestration, and resource locations. Each app owns its resource loading.
- Keep view code in `Views/`, stateful app logic in `Stores/`, API/auth code in `Services/`, and shared helpers in `Support/`.
- Keep playback orchestration in `Playback/`, app-specific models in `Models/`, and shared decoded Plex contracts in the package's `Sources/PlexModels/`.

## Code Expectations

Expand Down Expand Up @@ -60,7 +65,7 @@ This file defines project constraints for coding agents working in this reposito
From repo root (`/Users/austinsmith/Developer/Repos/PlexBar`), build with:

```bash
swift build
xcodebuild -project PlexBar.xcodeproj -scheme PlexBar -configuration Debug -destination 'platform=macOS' build
```

To build and launch the app bundle:
Expand All @@ -73,15 +78,15 @@ script/build_and_run.sh

- PlexBar uses Sparkle for auto-updates of Developer ID releases.
- Sparkle appcast/release workflow is documented in `docs/sparkle-updates.md`.
- Sparkle update metadata is injected by `script/build_and_run.sh` at bundle generation time; keep it out of checked-in source plist files.
- Sparkle update metadata is injected by Xcode from build settings supplied by `script/build_and_run.sh`.
- `script/build_and_run.sh` loads `.env.local` when present for local Sparkle build metadata.

## Testing

From repo root (`/Users/austinsmith/Developer/Repos/PlexBar`), run:

```bash
swift test
xcodebuild -project PlexBar.xcodeproj -scheme PlexBar -configuration Debug -destination 'platform=macOS' test
```

Add tests when they protect meaningful behavior, parsing logic, or regressions. Avoid low-value tests for simple refactors or trivial helpers.
4 changes: 4 additions & 0 deletions Config/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "Shared.xcconfig"

SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG
ONLY_ACTIVE_ARCH = YES
38 changes: 38 additions & 0 deletions Config/PlexBar-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key>
<string>$(ASSETCATALOG_COMPILER_APPICON_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.entertainment</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUEnableAutomaticChecks</key>
<true/>
<key>SUFeedURL</key>
<string>$(SPARKLE_APPCAST_URL)</string>
<key>SUPublicEDKey</key>
<string>$(SPARKLE_PUBLIC_KEY)</string>
<key>SUVerifyUpdateBeforeExtraction</key>
<true/>
</dict>
</plist>
58 changes: 58 additions & 0 deletions Config/PlexBarTV-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.crapshack.PlexBar.tv.topshelf</string>
<key>CFBundleURLSchemes</key>
<array><string>plexbar-tv</string></array>
</dict>
</array>
<key>CFBundleDisplayName</key>
<string>PlexBar</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.entertainment</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocalNetworkUsageDescription</key>
<string>PlexBar connects directly to your Plex Media Server to browse and play your library.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchScreen</key>
<dict>
<key>UIColorName</key>
<string></string>
</dict>
<key>UIUserInterfaceStyle</key>
<string>Dark</string>
</dict>
</plist>
14 changes: 14 additions & 0 deletions Config/PlexBarTV.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.crapshack.PlexBar.tv</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</plist>
Loading
Loading