Files
Felitendo ff0d3108fe feat: merge the web dashboard into the monorepo as ftstore admin
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.
2026-08-03 10:25:03 +02:00

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
}
}