feat: setup sparkle framework

This commit is contained in:
wibus-wee
2024-08-06 13:48:58 +08:00
parent 2f8cc3952f
commit 855a54ab94
5 changed files with 130 additions and 7 deletions

View File

@@ -34,13 +34,18 @@ jobs:
# brew install graphicsmagick imagemagick
- name: Set up env.APP_CONF to Debug
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
if: github.event_name != 'release'
run: |
echo "APP_CONF=Debug" >> $GITHUB_ENV
- name: Set up env.APP_CONF to Release
if: github.event_name == 'release'
run: |
echo "APP_CONF=Release" >> $GITHUB_ENV
APP_VERSION=$(xcrun agvtool mvers -terse1)
APP_BUILD=$(xcrun agvtool vers -terse)
echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
echo "APP_BUILD=$APP_BUILD" >> $GITHUB_ENV
- name: Build for macOS (Universal)
shell: bash
run: |
@@ -50,18 +55,52 @@ jobs:
run: |
hdiutil create -volname "InjectGUI" -srcfolder "build/Build/Products/${{ env.APP_CONF }}/InjectGUI.app" -ov -format UDZO "InjectGUI.dmg"
# - name: Create DMG
# run: |
# npm install --global create-dmg
# create-dmg 'build/Build/Products/Release/InjectGUI.app' --overwrite --dmg-title='InjectGUI'
- name: Upload DMG Artifact
uses: actions/upload-artifact@v4
with:
name: InjectGUI
path: InjectGUI.dmg
- name: Upload DMG to GitHub Release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: InjectGUI.dmg
############################
# Sparkle Appcast
############################
- name: Update Sparkle appcast
if: github.event_name == 'release'
env:
# SPARKLE_KEY: Sparkle EdDSA key exported from `generate_keys -x` as plain text
# SPARKLE_CHANNEL: Seperate dev builds from default channel, to be specified in [SPUUpdaterDelegate allowedChannelsForUpdater:]
# SPARKLE_DL_PREFIX: Prefix for the URL from where updates will be downloaded
# SPARKLE_LINK: CodeEdit Website
# https://github.com/wibus-wee/InjectGUI/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg
# RELEASE_NOTES_PREFIX: The URL to prefix before an update link:
# https://codeedit.app/whats-new/raw/{v0.1.0} -- data in {} is inserted by sparkle
# RELEASE_NOTES_URL: The URL of the entire release notes page: https://codeedit.app/whats-new
SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }}
# SPARKLE_CHANNEL: dev
SPARKLE_DL_PREFIX: "https://github.com/wibus-wee/InjectGUI/releases/download"
SPARKLE_LINK: "https://github.com/wibus-wee/InjectGUI"
APP_VERSION: ${{ env.APP_VERSION }}
APP_BUILD: ${{ env.APP_BUILD }}
# RELEASE_NOTES_URL: "https://codeedit.app/whats-new/"
# RELEASE_NOTES_PREFIX: "https://codeedit.app/sparkle/"
run: |
SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin"
SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive"
echo -n "$SPARKLE_KEY" | tee "$RUNNER_TEMP/sparkle_key"
mkdir "$SPARKLE_ARCHIVE"
cp "$RUNNER_TEMP/InjectGUI.dmg" "$SPARKLE_ARCHIVE"
SPARKLE_SIG=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$SPARKLE_ARCHIVE/InjectGUI.dmg" | cut -d\" -f2)
echo "<!DOCTYPE>" > "$SPARKLE_ARCHIVE/InjectGUI.html" # Need a blank html doc with the DOCTYPE tag to trick sparkle into loading our remote release notes.
"$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --maximum-deltas 0 "$SPARKLE_ARCHIVE"
- name: Upload Sparkle Appcast
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: "${{ RUNNER.TEMP }}/Sparkle_Archive/appcast.xml"

View File

@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
380D777B2C61EA56005F3150 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 380D777A2C61EA56005F3150 /* Sparkle */; };
380D777D2C61EAB9005F3150 /* CheckForUpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D777C2C61EAB9005F3150 /* CheckForUpdatesView.swift */; };
381027E02C5F784F00348460 /* Extension+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027DF2C5F784F00348460 /* Extension+Scene.swift */; };
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027E12C5F7C4800348460 /* Extension+Font.swift */; };
381027E42C5F7E1100348460 /* Extension+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027E32C5F7E1100348460 /* Extension+URL.swift */; };
@@ -36,6 +38,8 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
380D777C2C61EAB9005F3150 /* CheckForUpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckForUpdatesView.swift; sourceTree = "<group>"; };
380D777E2C61ED32005F3150 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
381027DF2C5F784F00348460 /* Extension+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Scene.swift"; sourceTree = "<group>"; };
381027E12C5F7C4800348460 /* Extension+Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Font.swift"; sourceTree = "<group>"; };
381027E32C5F7E1100348460 /* Extension+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+URL.swift"; sourceTree = "<group>"; };
@@ -72,6 +76,7 @@
buildActionMask = 2147483647;
files = (
38E944F52C5A7F6A00B252A3 /* Cache in Frameworks */,
380D777B2C61EA56005F3150 /* Sparkle in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -98,6 +103,7 @@
38877A1B2C4A6F83009F5910 /* InjectGUI */ = {
isa = PBXGroup;
children = (
380D777E2C61ED32005F3150 /* Info.plist */,
38877A3B2C4A9EAB009F5910 /* Util */,
38877A382C4A7306009F5910 /* App */,
38877A312C4A7217009F5910 /* Extension */,
@@ -141,6 +147,7 @@
38AD95ED2C58F59C0032E79F /* StatusView.swift */,
38BC1F5B2C4BB02200C3B60E /* SettingsView.swift */,
387CEA782C5BD9C400E3A5AC /* AdminPrivilegeView.swift */,
380D777C2C61EAB9005F3150 /* CheckForUpdatesView.swift */,
);
path = View;
sourceTree = "<group>";
@@ -193,6 +200,7 @@
name = InjectGUI;
packageProductDependencies = (
38E944F42C5A7F6A00B252A3 /* Cache */,
380D777A2C61EA56005F3150 /* Sparkle */,
);
productName = InjectGUI;
productReference = 38877A192C4A6F83009F5910 /* InjectGUI.app */;
@@ -225,6 +233,7 @@
mainGroup = 38877A102C4A6F83009F5910;
packageReferences = (
38E944F32C5A7F6A00B252A3 /* XCRemoteSwiftPackageReference "Cache" */,
380D77792C61EA56005F3150 /* XCRemoteSwiftPackageReference "Sparkle" */,
);
productRefGroup = 38877A1A2C4A6F83009F5910 /* Products */;
projectDirPath = "";
@@ -275,6 +284,7 @@
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */,
387CEA792C5BD9C400E3A5AC /* AdminPrivilegeView.swift in Sources */,
38AD95EE2C58F59C0032E79F /* StatusView.swift in Sources */,
380D777D2C61EAB9005F3150 /* CheckForUpdatesView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -416,6 +426,7 @@
DEVELOPMENT_ASSET_PATHS = "\"InjectGUI/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = InjectGUI/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@@ -454,6 +465,7 @@
DEVELOPMENT_ASSET_PATHS = "\"InjectGUI/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = InjectGUI/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@@ -505,6 +517,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
380D77792C61EA56005F3150 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
38E944F32C5A7F6A00B252A3 /* XCRemoteSwiftPackageReference "Cache" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/hyperoslo/Cache";
@@ -516,6 +536,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
380D777A2C61EA56005F3150 /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = 380D77792C61EA56005F3150 /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
38E944F42C5A7F6A00B252A3 /* Cache */ = {
isa = XCSwiftPackageProductDependency;
package = 38E944F32C5A7F6A00B252A3 /* XCRemoteSwiftPackageReference "Cache" */;

View File

@@ -6,6 +6,7 @@
//
import SwiftUI
import Sparkle
let configuration = Configuration.shared
let injectConfiguration = InjectConfiguration.shared
@@ -16,8 +17,12 @@ let executor = Executor.shared
@main
struct InjectGUIApp: App {
@AppStorage("showAdminPrivilegeView") private var showAdminPrivilegeView: Bool = true
private let updaterController: SPUStandardUpdaterController
init() {
// If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later
// This is where you can also pass an updater delegate if you need one
updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
setupApplicationSupportDirectory()
checkPassword()
}
@@ -29,7 +34,12 @@ struct InjectGUIApp: App {
AdminPrivilegeView()
}
}
.commands { SidebarCommands() }
.commands {
SidebarCommands()
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updater: updaterController.updater)
}
}
.contentSizedWindowResizability()
Settings {

10
InjectGUI/Info.plist Normal file
View File

@@ -0,0 +1,10 @@
<?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>SUFeedURL</key>
<string>https://github.com/wibus-wee/InjectGUI/releases/download/latest/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>uj8dRzY60RpQhFt88Lm4vwy7a5TEceTqRWmBeZpb4e0=</string>
</dict>
</plist>

View File

@@ -0,0 +1,39 @@
//
// CheckForUpdatesView.swift
// InjectGUI
//
// Created by wibus on 2024/8/6.
//
import SwiftUI
import Sparkle
// This view model class publishes when new updates can be checked by the user
final class CheckForUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false
init(updater: SPUUpdater) {
updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
}
}
// This is the view for the Check for Updates menu item
// Note this intermediate view is necessary for the disabled state on the menu item to work properly before Monterey.
// See https://stackoverflow.com/questions/68553092/menu-not-updating-swiftui-bug for more info
struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
private let updater: SPUUpdater
init(updater: SPUUpdater) {
self.updater = updater
// Create our view model for our CheckForUpdatesView
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
}
var body: some View {
Button("Check for Updates…", action: updater.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}