FTStore now holds both halves of the product:
ios/ the swift app — the fork of claration/Feather, unchanged apart
from its new home
web/ FTStore Admin, formerly its own repository. serves the
dashboard at /admin, the ftstore.json feed and IPA downloads
app-repo.json and Images/ stay at the repo root on purpose: the former is
a published AltStore source whose raw.githubusercontent URL is what
installed copies resolve updates through, so moving it would break every
subscriber.
The admin server is renamed throughout: package ftstore-admin, images
ghcr.io/<owner>/ftstore-admin-{app,worker}, postgres role/db ftstore,
session cookie ftstore_session, temp dir /tmp/ftstore, backup type
ftstore-settings, outbound user-agent FTStoreAdmin. backups written under the old
name still import, and compose is pinned to name: ftstore so the
volume prefix does not follow the directory name.
apply-rebrand.sh gains a relocation step: upstream's tree is rooted at
the repo root while ours lives in ios/, so a merge drops Feather's files
next to ios/ and the script folds them in before renaming. it never
touches web/. still idempotent.
release tags are namespaced — v* for the app, web-v* for the server —
so a server release no longer triggers a macOS build or rewrites
app-repo.json. workflows are prefixed ios-/web- and path-filtered.
90 lines
2.1 KiB
Swift
90 lines
2.1 KiB
Swift
//
|
|
// Bundle+versions.swift
|
|
// Loader
|
|
//
|
|
// Created by samara on 18.03.2025.
|
|
//
|
|
|
|
import Foundation.NSBundle
|
|
|
|
extension Bundle {
|
|
/// Get the name of the app
|
|
public var name: String {
|
|
if
|
|
let displayName = object(forInfoDictionaryKey: "CFBundleDisplayName") as? String,
|
|
!displayName.isEmpty
|
|
{
|
|
return displayName
|
|
}
|
|
if
|
|
let name = object(forInfoDictionaryKey: "CFBundleName") as? String,
|
|
!name.isEmpty
|
|
{
|
|
return name
|
|
}
|
|
return object(forInfoDictionaryKey: "CFBundleExecutable") as? String ?? ""
|
|
}
|
|
|
|
/// Get the executable name of the app
|
|
public var exec: String {
|
|
return object(forInfoDictionaryKey: "CFBundleExecutable") as? String ?? ""
|
|
}
|
|
|
|
/// Get the "short" version of the app
|
|
public var version: String {
|
|
if let version = object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
|
|
return version
|
|
}
|
|
|
|
return object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
|
|
}
|
|
|
|
/// Get the icon of the app
|
|
public var iconFileName: String? {
|
|
if
|
|
let icons = object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
|
|
let primary = icons["CFBundlePrimaryIcon"] as? [String: Any],
|
|
let files = primary["CFBundleIconFiles"] as? [String],
|
|
let name = files.last
|
|
{
|
|
return name
|
|
}
|
|
|
|
if
|
|
let iPadIcons = object(forInfoDictionaryKey: "CFBundleIcons~ipad") as? [String: Any],
|
|
let primary = iPadIcons["CFBundlePrimaryIcon"] as? [String: Any],
|
|
let files = primary["CFBundleIconFiles"] as? [String],
|
|
let name = files.last
|
|
{
|
|
return name
|
|
}
|
|
|
|
if
|
|
let iconFiles = object(forInfoDictionaryKey: "CFBundleIconFiles") as? [String],
|
|
let name = iconFiles.last ?? iconFiles.first
|
|
{
|
|
return name
|
|
}
|
|
|
|
if
|
|
let iPhoneIconFiles = object(forInfoDictionaryKey: "CFBundleIconFiles~iphone") as? [String],
|
|
let name = iPhoneIconFiles.last ?? iPhoneIconFiles.first
|
|
{
|
|
return name
|
|
}
|
|
|
|
if
|
|
let iPadIconFiles = object(forInfoDictionaryKey: "CFBundleIconFiles~ipad") as? [String],
|
|
let name = iPadIconFiles.last ?? iPadIconFiles.first
|
|
{
|
|
return name
|
|
}
|
|
|
|
if let iconFile = object(forInfoDictionaryKey: "CFBundleIconFile") as? String {
|
|
return iconFile
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|