Skip to content

Commit de8a7da

Browse files
Fix version injection and implement in-place updates
- Fix GitHub Actions workflow to inject version info before building - Update Info.plist with proper version, build number, and git hash - Implement in-place update mechanism instead of redirecting to GitHub - Download DMG directly from GitHub releases and install automatically - Mount DMG, copy app to Applications, unmount, and launch updated app - Replace 'Download Update' with 'Download & Install' for better UX - Fix version display in About dialog to show correct values
1 parent 85efbfd commit de8a7da

2 files changed

Lines changed: 123 additions & 49 deletions

File tree

.github/workflows/build-and-release.yml

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -61,52 +61,15 @@ jobs:
6161
6262
- name: Update Info.plist with version and hash
6363
run: |
64-
# Find the Info.plist file
65-
INFO_PLIST=$(find . -name "Info.plist" -path "*/A6Cutter.app/Contents/Info.plist" | head -1)
66-
67-
if [ -z "$INFO_PLIST" ]; then
68-
# If not found, create a temporary one for build
69-
INFO_PLIST="A6Cutter/Info.plist"
70-
mkdir -p A6Cutter
71-
cat > "$INFO_PLIST" << EOF
72-
<?xml version="1.0" encoding="UTF-8"?>
73-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PList 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
74-
<plist version="1.0">
75-
<dict>
76-
<key>CFBundleShortVersionString</key>
77-
<string>${{ steps.version_info.outputs.VERSION }}</string>
78-
<key>CFBundleVersion</key>
79-
<string>${{ github.run_number }}</string>
80-
<key>GitHash</key>
81-
<string>${{ steps.version_info.outputs.GIT_HASH }}</string>
82-
</dict>
83-
</plist>
84-
EOF
85-
fi
64+
# Update the source Info.plist before building
65+
INFO_PLIST="A6Cutter/Info.plist"
8666
87-
# Update existing Info.plist or create new one
88-
if [ -f "$INFO_PLIST" ]; then
89-
# Use plutil to update the plist
90-
plutil -replace CFBundleShortVersionString -string "${{ steps.version_info.outputs.VERSION }}" "$INFO_PLIST"
91-
plutil -replace CFBundleVersion -string "${{ github.run_number }}" "$INFO_PLIST"
92-
plutil -replace GitHash -string "${{ steps.version_info.outputs.GIT_HASH }}" "$INFO_PLIST"
93-
else
94-
# Create new Info.plist
95-
cat > "$INFO_PLIST" << EOF
96-
<?xml version="1.0" encoding="UTF-8"?>
97-
<!DOCTYPE plist PUBLIC "-//Apple//DTD Plist 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
98-
<plist version="1.0">
99-
<dict>
100-
<key>CFBundleShortVersionString</key>
101-
<string>${{ steps.version_info.outputs.VERSION }}</string>
102-
<key>CFBundleVersion</key>
103-
<string>${{ github.run_number }}</string>
104-
<key>GitHash</key>
105-
<string>${{ steps.version_info.outputs.GIT_HASH }}</string>
106-
</dict>
107-
</plist>
108-
EOF
109-
fi
67+
# Update version and build number
68+
plutil -replace CFBundleShortVersionString -string "${{ steps.version_info.outputs.VERSION }}" "$INFO_PLIST"
69+
plutil -replace CFBundleVersion -string "${{ github.run_number }}" "$INFO_PLIST"
70+
71+
# Add git hash to Info.plist
72+
plutil -replace GitHash -string "${{ steps.version_info.outputs.GIT_HASH }}" "$INFO_PLIST"
11073
11174
echo "Updated Info.plist with version ${{ steps.version_info.outputs.VERSION }} and hash ${{ steps.version_info.outputs.GIT_HASH_SHORT }}"
11275

A6Cutter/A6CutterApp.swift

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,127 @@ struct A6CutterApp: App {
128128
alert.messageText = "Update Available"
129129
alert.informativeText = "A6Cutter \(latestVersion) is available. You currently have version \(currentVersion).\n\n\(releaseNotes)"
130130
alert.alertStyle = .informational
131-
alert.addButton(withTitle: "Download Update")
131+
alert.addButton(withTitle: "Download & Install")
132132
alert.addButton(withTitle: "Later")
133133

134134
let response = alert.runModal()
135135
if response == .alertFirstButtonReturn {
136-
// Open GitHub releases page
137-
if let url = URL(string: "https://github.com/devopsmariocom/A6Cutter/releases/latest") {
138-
NSWorkspace.shared.open(url)
136+
// Download and install the update directly
137+
downloadAndInstallUpdate(latestVersion: latestVersion)
138+
}
139+
}
140+
141+
private func downloadAndInstallUpdate(latestVersion: String) {
142+
// Show progress dialog
143+
let progressAlert = NSAlert()
144+
progressAlert.messageText = "Downloading Update"
145+
progressAlert.informativeText = "Please wait while the update is downloaded and installed..."
146+
progressAlert.alertStyle = .informational
147+
progressAlert.addButton(withTitle: "Cancel")
148+
progressAlert.runModal()
149+
150+
// Download the DMG from GitHub releases
151+
let dmgUrl = "https://github.com/devopsmariocom/A6Cutter/releases/download/\(latestVersion)/A6Cutter-\(latestVersion).dmg"
152+
153+
guard let url = URL(string: dmgUrl) else {
154+
showDownloadError("Invalid download URL")
155+
return
156+
}
157+
158+
let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
159+
DispatchQueue.main.async {
160+
if let error = error {
161+
self.showDownloadError("Download failed: \(error.localizedDescription)")
162+
return
163+
}
164+
165+
guard let localURL = localURL else {
166+
self.showDownloadError("No local file received")
167+
return
168+
}
169+
170+
// Install the update
171+
self.installUpdate(from: localURL)
139172
}
140173
}
174+
175+
task.resume()
176+
}
177+
178+
private func installUpdate(from dmgURL: URL) {
179+
// Mount the DMG
180+
let mountTask = Process()
181+
mountTask.launchPath = "/usr/bin/hdiutil"
182+
mountTask.arguments = ["attach", dmgURL.path, "-nobrowse", "-noverify", "-noautoopen"]
183+
184+
let pipe = Pipe()
185+
mountTask.standardOutput = pipe
186+
mountTask.standardError = pipe
187+
188+
mountTask.launch()
189+
mountTask.waitUntilExit()
190+
191+
if mountTask.terminationStatus != 0 {
192+
showDownloadError("Failed to mount DMG")
193+
return
194+
}
195+
196+
// Get the mount point
197+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
198+
let output = String(data: data, encoding: .utf8) ?? ""
199+
let lines = output.components(separatedBy: .newlines)
200+
let mountPoint = lines.first { $0.contains("/Volumes/") }?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
201+
202+
if mountPoint.isEmpty {
203+
showDownloadError("Could not find mount point")
204+
return
205+
}
206+
207+
// Copy the app to Applications
208+
let sourceApp = "\(mountPoint)/A6Cutter.app"
209+
let destinationApp = "/Applications/A6Cutter.app"
210+
211+
let copyTask = Process()
212+
copyTask.launchPath = "/bin/rm"
213+
copyTask.arguments = ["-rf", destinationApp]
214+
copyTask.launch()
215+
copyTask.waitUntilExit()
216+
217+
let moveTask = Process()
218+
moveTask.launchPath = "/bin/cp"
219+
moveTask.arguments = ["-R", sourceApp, "/Applications/"]
220+
moveTask.launch()
221+
moveTask.waitUntilExit()
222+
223+
if moveTask.terminationStatus != 0 {
224+
showDownloadError("Failed to install update")
225+
return
226+
}
227+
228+
// Unmount the DMG
229+
let unmountTask = Process()
230+
unmountTask.launchPath = "/usr/bin/hdiutil"
231+
unmountTask.arguments = ["detach", mountPoint]
232+
unmountTask.launch()
233+
unmountTask.waitUntilExit()
234+
235+
// Launch the updated app
236+
let launchTask = Process()
237+
launchTask.launchPath = "/usr/bin/open"
238+
launchTask.arguments = [destinationApp]
239+
launchTask.launch()
240+
241+
// Exit current app
242+
NSApplication.shared.terminate(nil)
243+
}
244+
245+
private func showDownloadError(_ message: String) {
246+
let alert = NSAlert()
247+
alert.messageText = "Update Failed"
248+
alert.informativeText = message
249+
alert.alertStyle = .warning
250+
alert.addButton(withTitle: "OK")
251+
alert.runModal()
141252
}
142253

143254
private func showNoUpdatesDialog() {

0 commit comments

Comments
 (0)