Skip to content

Commit 886e769

Browse files
committed
Add check for update alert
1 parent d40d4f0 commit 886e769

File tree

9 files changed

+144
-5
lines changed

9 files changed

+144
-5
lines changed

Config.debug.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Version.xcconfig"
2+
#include "Secrets.xcconfig"
23

34
BUNDLE_IDENTIFIER_BASE = dev.com.intii.CopilotForXcode
45
EXTENSION_BUNDLE_NAME = Copilot Dev

Config.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Version.xcconfig"
2+
#include "Secrets.xcconfig"
23

34
BUNDLE_IDENTIFIER_BASE = com.intii.CopilotForXcode
45
EXTENSION_BUNDLE_NAME = Copilot

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
C81458982939EFDC00135263 /* EditorExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = EditorExtension.entitlements; sourceTree = "<group>"; };
155155
C81458AD293A009600135263 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
156156
C81458AE293A009800135263 /* Config.debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.debug.xcconfig; sourceTree = "<group>"; };
157+
C815034B29B3A7CC00751CAC /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = "<group>"; };
157158
C8189B162938972F00C9DCDA /* Copilot for Xcode Dev.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Copilot for Xcode Dev.app"; sourceTree = BUILT_PRODUCTS_DIR; };
158159
C8189B192938972F00C9DCDA /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
159160
C8189B1B2938972F00C9DCDA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -262,6 +263,7 @@
262263
C8520308293D805800460097 /* README.md */,
263264
C887BC832965D96000931567 /* DEVELOPMENT.md */,
264265
C81E867D296FE4420026E908 /* Version.xcconfig */,
266+
C815034B29B3A7CC00751CAC /* Secrets.xcconfig */,
265267
C81458AD293A009600135263 /* Config.xcconfig */,
266268
C81458AE293A009800135263 /* Config.debug.xcconfig */,
267269
C8189B282938979000C9DCDA /* Core */,

Core/Package.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ let package = Package(
99
products: [
1010
.library(
1111
name: "Service",
12-
targets: ["Service", "SuggestionInjector", "FileChangeChecker", "LaunchAgentManager"]
12+
targets: [
13+
"Service",
14+
"SuggestionInjector",
15+
"FileChangeChecker",
16+
"LaunchAgentManager",
17+
"UpdateChecker",
18+
]
1319
),
1420
.library(
1521
name: "Client",
@@ -97,5 +103,6 @@ let package = Package(
97103
"Environment",
98104
]
99105
),
106+
.target(name: "UpdateChecker"),
100107
]
101108
)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import AppKit
2+
import Foundation
3+
import os.log
4+
import SwiftUI
5+
6+
struct Release: Codable {
7+
let tag_name: String?
8+
let html_url: String?
9+
let body: String?
10+
let published_at: String?
11+
}
12+
13+
let skippedUpdateVersionKey = "skippedUpdateVersion"
14+
15+
public struct UpdateChecker {
16+
var skippedUpdateVersion: String? {
17+
UserDefaults.standard.string(forKey: skippedUpdateVersionKey)
18+
}
19+
20+
public init() {}
21+
22+
public func checkForUpdate() async {
23+
let url =
24+
URL(string: "https://api.github.com/repos/intitni/CopilotForXcode/releases/latest")!
25+
do {
26+
var request = URLRequest(url: url)
27+
let token = (Bundle.main.infoDictionary?["GITHUB_TOKEN"] as? String) ?? ""
28+
if !token.isEmpty {
29+
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
30+
}
31+
request.setValue("application/vnd.github+json", forHTTPHeaderField: "Accept")
32+
request.setValue("X-GitHub-Api-Version", forHTTPHeaderField: "2022-11-28")
33+
34+
let (data, _) = try await URLSession.shared.data(for: request)
35+
let decoder = JSONDecoder()
36+
let release = try decoder.decode(Release.self, from: data)
37+
guard let version = release.tag_name,
38+
version != skippedUpdateVersion,
39+
version != Bundle.main
40+
.infoDictionary?["CFBundleShortVersionString"] as? String
41+
else { return }
42+
43+
Task { @MainActor in
44+
let alert = NSAlert()
45+
alert.messageText = "Copilot for Xcode \(version) is available!"
46+
alert.informativeText = "Would you like to visit the release page?"
47+
let view = NSHostingView(
48+
rootView:
49+
AccessoryView(releaseNote: release.body)
50+
)
51+
view.frame = .init(origin: .zero, size: .init(width: 400, height: 200))
52+
alert.accessoryView = view
53+
54+
alert.addButton(withTitle: "Visit Release Page")
55+
alert.addButton(withTitle: "Skip This Version")
56+
alert.addButton(withTitle: "Cancel")
57+
alert.alertStyle = .informational
58+
let screenFrame = NSScreen.main?.frame ?? .zero
59+
let window = NSWindow(
60+
contentRect: .init(
61+
x: screenFrame.midX,
62+
y: screenFrame.midY,
63+
width: 1,
64+
height: 1
65+
),
66+
styleMask: .borderless,
67+
backing: .buffered,
68+
defer: true
69+
)
70+
window.level = .statusBar
71+
window.isReleasedWhenClosed = false
72+
alert.beginSheetModal(for: window) { [window] response in
73+
switch response {
74+
case .alertFirstButtonReturn:
75+
if let url = URL(string: release.html_url ?? "") {
76+
NSWorkspace.shared.open(url)
77+
}
78+
case .alertSecondButtonReturn:
79+
UserDefaults.standard.set(version, forKey: skippedUpdateVersionKey)
80+
default:
81+
break
82+
}
83+
window.close()
84+
}
85+
}
86+
} catch {
87+
os_log(.error, "%@", error.localizedDescription)
88+
}
89+
}
90+
}
91+
92+
struct AccessoryView: View {
93+
let releaseNote: String?
94+
95+
var body: some View {
96+
if let releaseNote {
97+
ScrollView {
98+
Text(releaseNote)
99+
.padding()
100+
101+
Spacer()
102+
}
103+
.background(Color(nsColor: .textBackgroundColor))
104+
.overlay {
105+
Rectangle()
106+
.stroke(Color(nsColor: .separatorColor), style: .init(lineWidth: 2))
107+
}
108+
} else {
109+
EmptyView()
110+
}
111+
}
112+
}

Core/Sources/XPCShared/UserDefaults.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public extension UserDefaults {
1313
setDefaultValue(true, forKey: SettingsKey.quitXPCServiceOnXcodeAndAppQuit)
1414
setDefaultValue(false, forKey: SettingsKey.realtimeSuggestionToggle)
1515
setDefaultValue(1 as Double, forKey: SettingsKey.realtimeSuggestionDebounce)
16+
setDefaultValue(false, forKey: SettingsKey.automaticallyCheckForUpdate)
1617
}
1718
}
1819

@@ -22,4 +23,5 @@ public enum SettingsKey {
2223
public static let realtimeSuggestionDebounce = "RealtimeSuggestionDebounce"
2324
public static let quitXPCServiceOnXcodeAndAppQuit = "QuitXPCServiceOnXcodeAndAppQuit"
2425
public static let suggestionPresentationMode = "SuggestionPresentationMode"
26+
public static let automaticallyCheckForUpdate = "AutomaticallyCheckForUpdate"
2527
}

ExtensionService/AppDelegate.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import os.log
55
import Service
66
import ServiceManagement
77
import SwiftUI
8+
import UpdateChecker
89
import UserNotifications
910
import XPCShared
1011

@@ -20,6 +21,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
2021
private var xpcListener: (NSXPCListener, ServiceDelegate)?
2122

2223
func applicationDidFinishLaunching(_: Notification) {
24+
#warning("TODO: disable in tests")
2325
_ = GraphicalUserInterfaceController.shared
2426
// setup real-time suggestion controller
2527
_ = RealtimeSuggestionController.shared
@@ -30,10 +32,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
3032
os_log(.info, "XPC Service started.")
3133
NSApp.setActivationPolicy(.prohibited)
3234
buildStatusBarMenu()
33-
#warning("TODO: disable in tests")
34-
AXIsProcessTrustedWithOptions([
35-
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true,
36-
] as NSDictionary)
35+
checkForUpdate()
3736
}
3837

3938
@objc private func buildStatusBarMenu() {
@@ -179,6 +178,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
179178
listener.resume()
180179
return (listener, delegate)
181180
}
181+
182+
func requestAccessoryAPIPermission() {
183+
AXIsProcessTrustedWithOptions([
184+
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true,
185+
] as NSDictionary)
186+
}
187+
188+
func checkForUpdate() {
189+
Task {
190+
await UpdateChecker().checkForUpdate()
191+
}
192+
}
182193
}
183194

184195
private class UserDefaultsObserver: NSObject {

ExtensionService/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<string>$(BUNDLE_IDENTIFIER_BASE)</string>
77
<key>EXTENSION_BUNDLE_NAME</key>
88
<string>$(EXTENSION_BUNDLE_NAME)</string>
9+
<key>GITHUB_TOKEN</key>
10+
<string>$(GITHUB_TOKEN)</string>
911
<key>XPCService</key>
1012
<dict>
1113
<key>ServiceType</key>

Secrets.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GITHUB_TOKEN =

0 commit comments

Comments
 (0)