feat(macos): add proper macOS wrapper app (#1290)

Still missing build process + assembly of bundle
This commit is contained in:
Christian Benincasa
2025-07-29 22:01:25 -04:00
committed by GitHub
parent b8e5fb25db
commit e293f4cf57
46 changed files with 1204 additions and 27 deletions

View File

@@ -43,10 +43,6 @@ jobs:
target: linux-x64
- name: ubuntu-22.04-arm
target: linux-arm64
- name: macos-13
target: macos-x64
- name: macos-14
target: macos-arm64
- name: windows-latest
target: win-x64
runs-on: ubuntu-latest
@@ -71,40 +67,21 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: 'Create env file (release)'
run: |
touch .env
echo TUNARR_EDGE_BUILD=false >> .env
echo NODE_ENV=production >> .env
echo TUNARR_BIND_ADDR=0.0.0.0 >> .env
cat .env
cp .env server/.env
cp .env web/.env
if: startsWith(github.ref, 'refs/tags/')
- name: 'Create env file (edge)'
- name: 'Create env file'
run: |
touch .env
echo TUNARR_BUILD="$(git rev-parse --short ${{ github.sha }})" >> .env
echo TUNARR_EDGE_BUILD=true >> .env
echo TUNARR_EDGE_BUILD=${{github.ref == format('refs/heads/{0}', 'dev') && (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')}} >> .env
echo NODE_ENV=production >> .env
echo TUNARR_BIND_ADDR=0.0.0.0 >> .env
grep -v '^#.*' .env >> $GITHUB_ENV
cat .env
cp .env server/.env
cp .env web/.env
if: github.ref == format('refs/heads/{0}', 'dev') && (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
- name: Bundle web
run: pnpm turbo bundle --filter=@tunarr/web
# Install ldid for adhoc signing of macos ARM binaries. PKG should take
# care of this.
- name: Install ldid
uses: MOZGIII/install-ldid-action@v1
with:
tag: v2.1.5-procursus2
if: matrix.os.target == 'macos-arm64'
- name: Build Executable
run: pnpm turbo make-bin -- --target ${{matrix.os.target}}
@@ -124,3 +101,135 @@ jobs:
files: |
./server/bin/tunarr-*
if: startsWith(github.ref, 'refs/tags/')
build-mac:
needs: typecheck
strategy:
matrix:
os:
- name: macos-13
target: macos-x64
runner: macos-13
- name: macos-14
target: macos-arm64
runner: macos-latest
runs-on: ${{matrix.os.runner}}
steps:
- name: Checkout
uses: actions/checkout@v4 # Checkout code from repository
- uses: ilammy/setup-nasm@v1
- name: Use Latest Corepack
run: |
npm install -g corepack@latest
corepack enable && corepack enable pnpm
pnpm --version
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22.13.0
cache: 'pnpm'
- name: Install dependencies
shell: bash
run: |
pnpm install
brew install jq
- name: Import Code-Signing Certificates
uses: Apple-Actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
p12-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
- name: 'Create env file'
run: |
touch .env
echo TUNARR_BUILD="$(git rev-parse --short ${{ github.sha }})" >> .env
echo TUNARR_EDGE_BUILD=${{github.ref == format('refs/heads/{0}', 'dev') && (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')}} >> .env
echo NODE_ENV=production >> .env
echo TUNARR_BIND_ADDR=0.0.0.0 >> .env
tunarr_version="$(jq -r .version package.json)"
if [[ "$TUNARR_EDGE_BUILD" == "true" || "$TUNARR_EDGE_BUILD" == "1" ]]; then
is_edge_build=true
else
is_edge_build=false
fi
if [[ -n "$TUNARR_BUILD" && "$TUNARR_EDGE_BUILD" == true ]]; then
tunarr_version+="-${build}"
fi
echo "TUNARR_VERSION=${tunarr_version}" >> $GITHUB_ENV
release_name="Tunarr-${tunarr_version}-${{ matrix.os.target }}"
echo "RELEASE_NAME=${release_name}" >> $GITHUB_ENV
grep -v '^#.*' .env >> $GITHUB_ENV
cat .env
cp .env server/.env
cp .env web/.env
- name: Bundle web
run: pnpm turbo bundle --filter=@tunarr/web
- name: Build Executable
run: pnpm turbo make-bin -- --target ${{matrix.os.target}}
- name: Bundle
shell: bash
run: |
brew install coreutils
plutil -replace CFBundleShortVersionString -string "$TUNARR_VERSION" macos/Tunarr/Tunarr/Info.plist
plutil -replace CFBundleVersion -string "$TUNARR_VERSION" macos/Tunarr/Tunarr/Info.plist
scripts/bundle-macos.sh
- name: Sign
shell: bash
run: scripts/sign-macos.sh
- name: Create DMG
shell: bash
run: |
brew install create-dmg
create-dmg \
--volname "Tunarr" \
--volicon "design/Tunarr.icns" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "Tunarr.app" 200 190 \
--hide-extension "Tunarr.app" \
--app-drop-link 600 185 \
--skip-jenkins \
--no-internet-enable \
"Tunarr.dmg" \
"Tunarr.app/"
- name: Notarize
shell: bash
run: |
xcrun notarytool submit Tunarr.dmg --apple-id "${{ secrets.AC_USERNAME }}" --password "${{ secrets.AC_PASSWORD }}" --team-id XX35C4MZN8 --wait
xcrun stapler staple Tunarr.dmg
- name: Finalize
shell: bash
run: |
mv Tunarr.dmg "${{env.RELEASE_NAME}}.dmg"
- name: Upload Edge Binary
uses: softprops/action-gh-release@v2
with:
name: Tunarr Edge
tag_name: edge
prerelease: true
files: |
${{ env.RELEASE_NAME }}.dmg
if: github.ref == format('refs/heads/{0}', 'dev') && (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule')
- name: Upload Binaries
uses: softprops/action-gh-release@v2
with:
files: |
${{ env.RELEASE_NAME }}.dmg
if: startsWith(github.ref, 'refs/tags/')

2
.gitignore vendored
View File

@@ -20,4 +20,4 @@ log.txt
coverage/
*.dmg
*.app
*.app

BIN
design/Tunarr.icns Normal file

Binary file not shown.

View File

@@ -0,0 +1,556 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXContainerItemProxy section */
14E9EAAB2E35689400486B60 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 14E9EA942E35689100486B60 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 14E9EA9B2E35689100486B60;
remoteInfo = Tunarr;
};
14E9EAB52E35689400486B60 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 14E9EA942E35689100486B60 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 14E9EA9B2E35689100486B60;
remoteInfo = Tunarr;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
14E9EA9C2E35689100486B60 /* Tunarr.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tunarr.app; sourceTree = BUILT_PRODUCTS_DIR; };
14E9EAAA2E35689400486B60 /* TunarrTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TunarrTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
14E9EAB42E35689400486B60 /* TunarrUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TunarrUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
14E9EA9E2E35689100486B60 /* Tunarr */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Tunarr;
sourceTree = "<group>";
};
14E9EAAD2E35689400486B60 /* TunarrTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = TunarrTests;
sourceTree = "<group>";
};
14E9EAB72E35689400486B60 /* TunarrUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = TunarrUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
14E9EA992E35689100486B60 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAA72E35689400486B60 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAB12E35689400486B60 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
14E9EA932E35689100486B60 = {
isa = PBXGroup;
children = (
14E9EA9E2E35689100486B60 /* Tunarr */,
14E9EAAD2E35689400486B60 /* TunarrTests */,
14E9EAB72E35689400486B60 /* TunarrUITests */,
14E9EA9D2E35689100486B60 /* Products */,
);
sourceTree = "<group>";
};
14E9EA9D2E35689100486B60 /* Products */ = {
isa = PBXGroup;
children = (
14E9EA9C2E35689100486B60 /* Tunarr.app */,
14E9EAAA2E35689400486B60 /* TunarrTests.xctest */,
14E9EAB42E35689400486B60 /* TunarrUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
14E9EA9B2E35689100486B60 /* Tunarr */ = {
isa = PBXNativeTarget;
buildConfigurationList = 14E9EABE2E35689400486B60 /* Build configuration list for PBXNativeTarget "Tunarr" */;
buildPhases = (
14E9EA982E35689100486B60 /* Sources */,
14E9EA992E35689100486B60 /* Frameworks */,
14E9EA9A2E35689100486B60 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
14E9EA9E2E35689100486B60 /* Tunarr */,
);
name = Tunarr;
packageProductDependencies = (
);
productName = Tunarr;
productReference = 14E9EA9C2E35689100486B60 /* Tunarr.app */;
productType = "com.apple.product-type.application";
};
14E9EAA92E35689400486B60 /* TunarrTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 14E9EAC12E35689400486B60 /* Build configuration list for PBXNativeTarget "TunarrTests" */;
buildPhases = (
14E9EAA62E35689400486B60 /* Sources */,
14E9EAA72E35689400486B60 /* Frameworks */,
14E9EAA82E35689400486B60 /* Resources */,
);
buildRules = (
);
dependencies = (
14E9EAAC2E35689400486B60 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
14E9EAAD2E35689400486B60 /* TunarrTests */,
);
name = TunarrTests;
packageProductDependencies = (
);
productName = TunarrTests;
productReference = 14E9EAAA2E35689400486B60 /* TunarrTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
14E9EAB32E35689400486B60 /* TunarrUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 14E9EAC42E35689400486B60 /* Build configuration list for PBXNativeTarget "TunarrUITests" */;
buildPhases = (
14E9EAB02E35689400486B60 /* Sources */,
14E9EAB12E35689400486B60 /* Frameworks */,
14E9EAB22E35689400486B60 /* Resources */,
);
buildRules = (
);
dependencies = (
14E9EAB62E35689400486B60 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
14E9EAB72E35689400486B60 /* TunarrUITests */,
);
name = TunarrUITests;
packageProductDependencies = (
);
productName = TunarrUITests;
productReference = 14E9EAB42E35689400486B60 /* TunarrUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
14E9EA942E35689100486B60 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
TargetAttributes = {
14E9EA9B2E35689100486B60 = {
CreatedOnToolsVersion = 16.4;
};
14E9EAA92E35689400486B60 = {
CreatedOnToolsVersion = 16.4;
TestTargetID = 14E9EA9B2E35689100486B60;
};
14E9EAB32E35689400486B60 = {
CreatedOnToolsVersion = 16.4;
TestTargetID = 14E9EA9B2E35689100486B60;
};
};
};
buildConfigurationList = 14E9EA972E35689100486B60 /* Build configuration list for PBXProject "Tunarr" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 14E9EA932E35689100486B60;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 14E9EA9D2E35689100486B60 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
14E9EA9B2E35689100486B60 /* Tunarr */,
14E9EAA92E35689400486B60 /* TunarrTests */,
14E9EAB32E35689400486B60 /* TunarrUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
14E9EA9A2E35689100486B60 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAA82E35689400486B60 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAB22E35689400486B60 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
14E9EA982E35689100486B60 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAA62E35689400486B60 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
14E9EAB02E35689400486B60 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
14E9EAAC2E35689400486B60 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 14E9EA9B2E35689100486B60 /* Tunarr */;
targetProxy = 14E9EAAB2E35689400486B60 /* PBXContainerItemProxy */;
};
14E9EAB62E35689400486B60 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 14E9EA9B2E35689100486B60 /* Tunarr */;
targetProxy = 14E9EAB52E35689400486B60 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
14E9EABC2E35689400486B60 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = XX35C4MZN8;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
14E9EABD2E35689400486B60 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = XX35C4MZN8;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
};
name = Release;
};
14E9EABF2E35689400486B60 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Tunarr/Tunarr.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.Tunarr;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
14E9EAC02E35689400486B60 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Tunarr/Tunarr.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.Tunarr;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
14E9EAC22E35689400486B60 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.TunarrTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tunarr.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Tunarr";
};
name = Debug;
};
14E9EAC32E35689400486B60 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.TunarrTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tunarr.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Tunarr";
};
name = Release;
};
14E9EAC52E35689400486B60 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.TunarrUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Tunarr;
};
name = Debug;
};
14E9EAC62E35689400486B60 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = XX35C4MZN8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cbenincasa.TunarrUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Tunarr;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
14E9EA972E35689100486B60 /* Build configuration list for PBXProject "Tunarr" */ = {
isa = XCConfigurationList;
buildConfigurations = (
14E9EABC2E35689400486B60 /* Debug */,
14E9EABD2E35689400486B60 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
14E9EABE2E35689400486B60 /* Build configuration list for PBXNativeTarget "Tunarr" */ = {
isa = XCConfigurationList;
buildConfigurations = (
14E9EABF2E35689400486B60 /* Debug */,
14E9EAC02E35689400486B60 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
14E9EAC12E35689400486B60 /* Build configuration list for PBXNativeTarget "TunarrTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
14E9EAC22E35689400486B60 /* Debug */,
14E9EAC32E35689400486B60 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
14E9EAC42E35689400486B60 /* Build configuration list for PBXNativeTarget "TunarrUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
14E9EAC52E35689400486B60 /* Debug */,
14E9EAC62E35689400486B60 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 14E9EA942E35689100486B60 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -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>SchemeUserState</key>
<dict>
<key>Tunarr.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,37 @@
//
// AppDelegate.swift
// Tunarr
//
// Created by Christian Benincasa on 7/26/25.
//
import AppKit
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
let bundle = Bundle.main
var task = Process()
func applicationDidFinishLaunching(_ notification: Notification) {
guard let executablePath = Bundle.main.url(forAuxiliaryExecutable: "tunarr-macos")
else {
/// TODO: Do a popup here.
NSLog("Error: Bundled executable 'tunarr-macos' not found.")
return
}
task.executableURL = executablePath
do {
try task.run()
NSLog("Successfully started Tunarr subprocess.")
} catch {
NSLog("Could not launch Tunarr. Reason: \(error)")
}
}
func applicationWillTerminate(_ aNotification: Notification) {
NSLog("Shutting down Tunarr.")
task.terminate()
task.waitUntilExit()
}
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,68 @@
{
"images" : [
{
"filename" : "tunarr_16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "tunarr_16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "tunarr_32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "tunarr_32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "tunarr_128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "tunarr_128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "tunarr_256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "tunarr_256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "tunarr_512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "tunarr_512_512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,58 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "7x7"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "7x7"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "11x11"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "11x11"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "24x24"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "24x24"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "50x50"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "50x50"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "100x100"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "100x100"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "favicon.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "favicon 1.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "favicon 2.svg",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,25 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
line { stroke: #231A12; }
path.darkMode, circle { fill: #231A12; }
circle.button {fill: white}
@media (prefers-color-scheme: dark) {
line { stroke: #474143 }
path.darkMode, circle { fill: #474143 }
circle.button {fill: white}
}
</style>
<line class="darkMode" x1="13.0531" y1="3.85848" x2="15.1559" y2="7.61348" stroke="#231A12"/>
<line x1="18.3012" y1="3.78277" x2="15.8843" y2="7.34375" stroke="#231A12"/>
<path class="darkMode" d="M0 17C0 11.4772 4.47715 7 10 7H22C27.5228 7 32 11.4772 32 17V18C32 23.5228 27.5228 28 22 28H10C4.47715 28 0 23.5228 0 18V17Z" fill="#231A12"/>
<path class="darkMode" d="M0 8.48936C0 7.66681 0.69548 7 1.5534 7H30.4466C31.3045 7 32 7.66681 32 8.48936V26.5106C32 27.3332 31.3045 28 30.4466 28H1.5534C0.69548 28 0 27.3332 0 26.5106V8.48936Z" fill="#231A12"/>
<circle cx="13" cy="4" r="1" fill="#231A12"/>
<circle cx="18" cy="4" r="1" fill="#231A12"/>
<path d="M3.00397 11.5614C3.00397 11.0091 3.45168 10.5614 4.00397 10.5614H8.33894V24.53H4.00397C3.45168 24.53 3.00397 24.0823 3.00397 23.53V11.5614Z" fill="#DD3C2C"/>
<path d="M18.8472 10.5614H23.1822C23.7345 10.5614 24.1822 11.0091 24.1822 11.5614V23.53C24.1822 24.0823 23.7345 24.53 23.1822 24.53H18.8472V10.5614Z" fill="#E98E1D"/>
<rect x="13.6739" y="10.5614" width="5.33497" height="13.9686" fill="#68AAE5"/>
<rect x="8.33899" y="10.5614" width="5.33497" height="13.9686" fill="#8DCA23"/>
<circle class="button" cx="28" cy="14" r="1" fill="white"/>
<circle class="button" cx="28" cy="22" r="1" fill="white"/>
<circle class="button" cx="28" cy="18" r="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,25 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
line { stroke: #231A12; }
path.darkMode, circle { fill: #231A12; }
circle.button {fill: white}
@media (prefers-color-scheme: dark) {
line { stroke: #474143 }
path.darkMode, circle { fill: #474143 }
circle.button {fill: white}
}
</style>
<line class="darkMode" x1="13.0531" y1="3.85848" x2="15.1559" y2="7.61348" stroke="#231A12"/>
<line x1="18.3012" y1="3.78277" x2="15.8843" y2="7.34375" stroke="#231A12"/>
<path class="darkMode" d="M0 17C0 11.4772 4.47715 7 10 7H22C27.5228 7 32 11.4772 32 17V18C32 23.5228 27.5228 28 22 28H10C4.47715 28 0 23.5228 0 18V17Z" fill="#231A12"/>
<path class="darkMode" d="M0 8.48936C0 7.66681 0.69548 7 1.5534 7H30.4466C31.3045 7 32 7.66681 32 8.48936V26.5106C32 27.3332 31.3045 28 30.4466 28H1.5534C0.69548 28 0 27.3332 0 26.5106V8.48936Z" fill="#231A12"/>
<circle cx="13" cy="4" r="1" fill="#231A12"/>
<circle cx="18" cy="4" r="1" fill="#231A12"/>
<path d="M3.00397 11.5614C3.00397 11.0091 3.45168 10.5614 4.00397 10.5614H8.33894V24.53H4.00397C3.45168 24.53 3.00397 24.0823 3.00397 23.53V11.5614Z" fill="#DD3C2C"/>
<path d="M18.8472 10.5614H23.1822C23.7345 10.5614 24.1822 11.0091 24.1822 11.5614V23.53C24.1822 24.0823 23.7345 24.53 23.1822 24.53H18.8472V10.5614Z" fill="#E98E1D"/>
<rect x="13.6739" y="10.5614" width="5.33497" height="13.9686" fill="#68AAE5"/>
<rect x="8.33899" y="10.5614" width="5.33497" height="13.9686" fill="#8DCA23"/>
<circle class="button" cx="28" cy="14" r="1" fill="white"/>
<circle class="button" cx="28" cy="22" r="1" fill="white"/>
<circle class="button" cx="28" cy="18" r="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,25 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
line { stroke: #231A12; }
path.darkMode, circle { fill: #231A12; }
circle.button {fill: white}
@media (prefers-color-scheme: dark) {
line { stroke: #474143 }
path.darkMode, circle { fill: #474143 }
circle.button {fill: white}
}
</style>
<line class="darkMode" x1="13.0531" y1="3.85848" x2="15.1559" y2="7.61348" stroke="#231A12"/>
<line x1="18.3012" y1="3.78277" x2="15.8843" y2="7.34375" stroke="#231A12"/>
<path class="darkMode" d="M0 17C0 11.4772 4.47715 7 10 7H22C27.5228 7 32 11.4772 32 17V18C32 23.5228 27.5228 28 22 28H10C4.47715 28 0 23.5228 0 18V17Z" fill="#231A12"/>
<path class="darkMode" d="M0 8.48936C0 7.66681 0.69548 7 1.5534 7H30.4466C31.3045 7 32 7.66681 32 8.48936V26.5106C32 27.3332 31.3045 28 30.4466 28H1.5534C0.69548 28 0 27.3332 0 26.5106V8.48936Z" fill="#231A12"/>
<circle cx="13" cy="4" r="1" fill="#231A12"/>
<circle cx="18" cy="4" r="1" fill="#231A12"/>
<path d="M3.00397 11.5614C3.00397 11.0091 3.45168 10.5614 4.00397 10.5614H8.33894V24.53H4.00397C3.45168 24.53 3.00397 24.0823 3.00397 23.53V11.5614Z" fill="#DD3C2C"/>
<path d="M18.8472 10.5614H23.1822C23.7345 10.5614 24.1822 11.0091 24.1822 11.5614V23.53C24.1822 24.0823 23.7345 24.53 23.1822 24.53H18.8472V10.5614Z" fill="#E98E1D"/>
<rect x="13.6739" y="10.5614" width="5.33497" height="13.9686" fill="#68AAE5"/>
<rect x="8.33899" y="10.5614" width="5.33497" height="13.9686" fill="#8DCA23"/>
<circle class="button" cx="28" cy="14" r="1" fill="white"/>
<circle class="button" cx="28" cy="22" r="1" fill="white"/>
<circle class="button" cx="28" cy="18" r="1" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -0,0 +1,8 @@
<?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>LSUIElement</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,22 @@
<?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.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,47 @@
//
// TunarrApp.swift
// Tunarr
//
// Created by Christian Benincasa on 7/26/25.
//
import SwiftUI
@main
struct TunarrApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
MenuBarExtra("Tunarr", image: "StatusBarButtonImage") {
Button("Launch Web UI") {
let portString = ProcessInfo.processInfo.environment[
"TUNARR_SERVER_PORT"
]
NSLog("Port string is \(portString ?? "null")")
let port = Int(portString ?? "") ?? 8000
let url = "http://localhost:\(port)"
NSWorkspace.shared.open(NSURL(string: url)! as URL)
}
Button("Show Logs") {
NSWorkspace.shared.activateFileViewerSelecting(
[
URL(
fileURLWithPath:
FileManager.default
.homeDirectoryForCurrentUser
.appendingPathComponent(
"Library"
).appendingPathComponent("Preferences")
.appendingPathComponent("tunarr")
.appendingPathComponent("logs").path
)
]
)
}
Divider()
Button("Quit Tunarr") {
NSApplication.shared.terminate(nil)
}
}
}
}

View File

@@ -0,0 +1,17 @@
//
// TunarrTests.swift
// TunarrTests
//
// Created by Christian Benincasa on 7/26/25.
//
import Testing
@testable import Tunarr
struct TunarrTests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}

View File

@@ -0,0 +1,41 @@
//
// TunarrUITests.swift
// TunarrUITests
//
// Created by Christian Benincasa on 7/26/25.
//
import XCTest
final class TunarrUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}

View File

@@ -0,0 +1,33 @@
//
// TunarrUITestsLaunchTests.swift
// TunarrUITests
//
// Created by Christian Benincasa on 7/26/25.
//
import XCTest
final class TunarrUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}

22
scripts/bundle-macos.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
BINARY_NAME="$1"
SCRIPT_FOLDER=$(dirname "${BASH_SOURCE[0]}")
REPO_ROOT=$(realpath "$SCRIPT_FOLDER/..")
APP_NAME="$REPO_ROOT/Tunarr.app"
if [ -d "$APP_NAME" ]
then
rm -rf "$APP_NAME"
fi
pushd "$REPO_ROOT/macos/Tunarr" || exit
xcodebuild build
cp -R "$REPO_ROOT/macos/Tunarr/build/Release/Tunarr.app" "$APP_NAME"
popd || exit
cp -a "$REPO_ROOT/server/bin/$BINARY_NAME" "$APP_NAME/Contents/MacOS/tunarr-macos"
chmod +x "$APP_NAME/Contents/MacOS/tunarr-macos"

3
scripts/gen-icns.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
iconutil -c icns -o ./design/Tunarr.icns macos/Tunarr/Tunarr/Assets.xcassets/TunarrIcon.iconset

11
scripts/sign-macos-dmg.sh Executable file
View File

@@ -0,0 +1,11 @@
#! /bin/bash
#!/usr/bin/env bash
SCRIPT_FOLDER=$(dirname "${BASH_SOURCE[0]}")
REPO_ROOT=$(realpath "$SCRIPT_FOLDER/..")
DMG_NAME="$REPO_ROOT/Tunarr.dmg"
ENTITLEMENTS="$REPO_ROOT/macos/Tunarr/Tunarr/Tunarr.entitlements"
SIGNING_IDENTITY="040A9C3F4C8D3D983822CCB65959A6FDB9DACDCD"
codesign --force --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" "$DMG_NAME"

9
scripts/sign-macos.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
SCRIPT_FOLDER=$(dirname "${BASH_SOURCE[0]}")
REPO_ROOT=$(realpath "$SCRIPT_FOLDER/..")
APP_NAME="$REPO_ROOT/Tunarr.app"
ENTITLEMENTS="$REPO_ROOT/macos/Tunarr/Tunarr/Tunarr.entitlements"
SIGNING_IDENTITY="040A9C3F4C8D3D983822CCB65959A6FDB9DACDCD"
codesign --force --verbose --timestamp --options=runtime --entitlements "$ENTITLEMENTS" --sign "$SIGNING_IDENTITY" --deep "$APP_NAME"