Add macOS app installer workflow

This commit is contained in:
diyaa 2026-07-26 16:24:24 +02:00
parent 1f7e696f1c
commit 53fa92d222
7 changed files with 272 additions and 0 deletions

View File

@ -0,0 +1,12 @@
<?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.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

26
FchatiApp/Info.plist Normal file
View File

@ -0,0 +1,26 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>FchatiApp</string>
<key>CFBundleIdentifier</key>
<string>de.diyaa.fchati</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Fchati</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>14.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

18
FchatiApp/Package.swift Normal file
View File

@ -0,0 +1,18 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "FchatiApp",
platforms: [.macOS(.v14)],
targets: [
.executableTarget(
name: "FchatiApp",
path: "Sources/FchatiApp"
),
.testTarget(
name: "FchatiAppTests",
dependencies: ["FchatiApp"],
path: "Tests/FchatiAppTests"
)
]
)

View File

@ -0,0 +1,95 @@
import SwiftUI
@main
struct FchatiApp: App {
var body: some Scene {
MenuBarExtra("Fchati", systemImage: "message") {
RootView()
}
.menuBarExtraStyle(.window)
}
}
enum AppTab: String, CaseIterable, Identifiable {
case chat = "Chat"
case pairing = "Pairing"
case settings = "Settings"
var id: Self { self }
var title: String {
rawValue
}
var icon: String {
switch self {
case .chat:
"message"
case .pairing:
"link"
case .settings:
"gearshape"
}
}
}
private struct RootView: View {
@State private var selectedTab: AppTab = .chat
var body: some View {
VStack(spacing: 0) {
Picker("Section", selection: $selectedTab) {
ForEach(AppTab.allCases) { tab in
Label(tab.title, systemImage: tab.icon)
.tag(tab)
}
}
.pickerStyle(.segmented)
.labelsHidden()
.padding()
Divider()
Group {
switch selectedTab {
case .chat:
PlaceholderView(
icon: "message",
title: "No messages yet",
description: "Pair with someone to start chatting."
)
case .pairing:
PlaceholderView(
icon: "link",
title: "Pair a device",
description: "Create or join a pairing session."
)
case .settings:
PlaceholderView(
icon: "gearshape",
title: "Settings",
description: "Your preferences will appear here."
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(width: 360)
.frame(minHeight: 480)
}
}
private struct PlaceholderView: View {
let icon: String
let title: String
let description: String
var body: some View {
ContentUnavailableView(
title,
systemImage: icon,
description: Text(description)
)
.padding()
}
}

View File

@ -0,0 +1,11 @@
import XCTest
@testable import FchatiApp
final class AppTabTests: XCTestCase {
func testTabsMatchTheAppShell() {
XCTAssertEqual(AppTab.allCases, [.chat, .pairing, .settings])
XCTAssertEqual(AppTab.chat.icon, "message")
XCTAssertEqual(AppTab.pairing.icon, "link")
XCTAssertEqual(AppTab.settings.icon, "gearshape")
}
}

50
IMPLEMENTATION_STATUS.md Normal file
View File

@ -0,0 +1,50 @@
# حالة التنفيذ
## آخر تحديث
تم إنشاء الهيكل الأولي لتطبيق ماك، مع سكربت محلي لاختباره وبنائه وتثبيته وفتحه.
## ما تم إنجازه
- إنشاء حزمة سويفت لتطبيق ماك يدعم نظام ماك 14 فما فوق.
- إنشاء تطبيق شريط القوائم مع واجهة بعرض 360 نقطة وارتفاع أدنى 480 نقطة.
- إضافة تبويبات للدردشة والاقتران والإعدادات، مع حالات بداية مؤقتة لكل تبويب.
- إضافة صلاحيات الشبكة وصندوق الحماية للتطبيق.
- إضافة اختبار يتحقق من وجود التبويبات الثلاثة وأيقوناتها.
- إضافة ملف معلومات لتجميع الملف التنفيذي داخل حزمة تطبيق قابلة للتشغيل.
- إضافة سكربت للتنظيف والاختبار والبناء والتثبيت والفتح.
## السكربت المحلي
المسار:
`scripts/run-macos-app.sh`
ينفذ السكربت الخطوات التالية بالترتيب:
1. يحذف نواتج البناء الحالية الخاصة بتطبيق ماك فقط.
2. يشغّل الاختبارات.
3. يبني نسخة إصدار من التطبيق.
4. ينشئ حزمة تطبيق ماك مؤقتة ويوقعها محليًا.
5. يحذف النسخة الموجودة من التطبيق في مجلد التطبيقات، إن وجدت.
6. ينقل النسخة الجديدة إلى مجلد التطبيقات.
7. يفتح التطبيق الجديد.
مسار التثبيت الناتج:
`/Applications/Fchati.app`
## التحقق المنفذ
- نجح اختبار الحزمة، مع اختبار واحد ناجح ودون إخفاقات.
- تم تنفيذ سكربت التثبيت كاملًا بنجاح، بما في ذلك البناء والتثبيت والفتح.
- تمت إضافة اختبار للحفاظ على عقد واجهة البداية.
- لم يتم تعديل ملف المهام الموجود مسبقًا.
## الحالة الحالية
واجهة البداية وحزمة التطبيق جاهزتان للتشغيل المحلي.
لا تزال ميزات التخزين الآمن والاقتران والاتصال بالخادم والدردشة الفعلية غير منفذة.
المهمة التالية المقترحة هي إضافة التخزين الآمن للهوية والرموز، ثم ربط شاشة الاقتران بالخادم.

60
scripts/run-macos-app.sh Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail
REPOSITORY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROJECT_DIR="$REPOSITORY_DIR/FchatiApp"
BUILD_DIR="$PROJECT_DIR/.build"
PRODUCT_NAME="FchatiApp"
INSTALL_PATH="/Applications/Fchati.app"
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/fchati-install.XXXXXX")"
STAGING_APP="$STAGING_DIR/Fchati.app"
cleanup() {
rm -rf "$STAGING_DIR"
}
trap cleanup EXIT
if [[ ! -f "$PROJECT_DIR/Package.swift" ]]; then
echo "FchatiApp package not found: $PROJECT_DIR" >&2
exit 1
fi
if [[ -e "$INSTALL_PATH" && ! -d "$INSTALL_PATH" ]]; then
echo "Expected an application bundle at $INSTALL_PATH, but found a non-directory." >&2
exit 1
fi
echo "Removing the current Swift build artifacts..."
rm -rf "$BUILD_DIR"
echo "Running tests..."
swift test --package-path "$PROJECT_DIR"
echo "Building the release executable..."
swift build --configuration release --package-path "$PROJECT_DIR"
EXECUTABLE_PATH="$PROJECT_DIR/.build/release/$PRODUCT_NAME"
if [[ ! -x "$EXECUTABLE_PATH" ]]; then
echo "Release executable not found: $EXECUTABLE_PATH" >&2
exit 1
fi
echo "Creating the application bundle..."
mkdir -p "$STAGING_APP/Contents/MacOS"
cp "$PROJECT_DIR/Info.plist" "$STAGING_APP/Contents/Info.plist"
cp "$EXECUTABLE_PATH" "$STAGING_APP/Contents/MacOS/$PRODUCT_NAME"
codesign --force --sign - --timestamp=none "$STAGING_APP"
if [[ -d "$INSTALL_PATH" ]]; then
echo "Removing the installed application..."
rm -rf "$INSTALL_PATH"
fi
echo "Installing the application..."
mv "$STAGING_APP" "$INSTALL_PATH"
echo "Opening Fchati..."
open "$INSTALL_PATH"
echo "Fchati was installed and opened successfully."