@@ -4,24 +4,49 @@ import Terminal
44public struct GitHubCopilotInstallationManager {
55 private static var isInstalling = false
66
7+ static var downloadURL : URL {
8+ let commitHash = " 1358e8e45ecedc53daf971924a0541ddf6224faf "
9+ let link = " https://github.com/github/copilot.vim/archive/ \( commitHash) .zip "
10+ return URL ( string: link) !
11+ }
12+
13+ static let latestSupportedVersion = " 1.8.4 "
14+
715 public init ( ) { }
816
917 public enum InstallationStatus {
1018 case notInstalled
11- case installed
19+ case installed( String )
20+ case outdated( current: String , latest: String )
21+ case unsupported( current: String , latest: String )
1222 }
1323
1424 public func checkInstallation( ) -> InstallationStatus {
1525 guard let urls = try ? GitHubCopilotBaseService . createFoldersIfNeeded ( )
1626 else { return . notInstalled }
1727 let executableFolderURL = urls. executableURL
1828 let binaryURL = executableFolderURL. appendingPathComponent ( " copilot " )
29+ let versionFileURL = executableFolderURL. appendingPathComponent ( " version " )
1930
2031 if !FileManager. default. fileExists ( atPath: binaryURL. path) {
2132 return . notInstalled
2233 }
2334
24- return . installed
35+ if FileManager . default. fileExists ( atPath: versionFileURL. path) ,
36+ let versionData = try ? Data ( contentsOf: versionFileURL) ,
37+ let version = String ( data: versionData, encoding: . utf8)
38+ {
39+ switch version. compare ( Self . latestSupportedVersion) {
40+ case . orderedAscending:
41+ return . outdated( current: version, latest: Self . latestSupportedVersion)
42+ case . orderedSame:
43+ return . installed( version)
44+ case . orderedDescending:
45+ return . unsupported( current: version, latest: Self . latestSupportedVersion)
46+ }
47+ }
48+
49+ return . outdated( current: " Unknown " , latest: Self . latestSupportedVersion)
2550 }
2651
2752 public enum InstallationStep {
@@ -34,13 +59,16 @@ public struct GitHubCopilotInstallationManager {
3459 public enum Error : Swift . Error , LocalizedError {
3560 case isInstalling
3661 case failedToFindLanguageServer
62+ case failedToInstallLanguageServer
3763
3864 public var errorDescription : String ? {
3965 switch self {
4066 case . isInstalling:
4167 return " Language server is installing. "
4268 case . failedToFindLanguageServer:
4369 return " Failed to find language server. Please open an issue on GitHub. "
70+ case . failedToInstallLanguageServer:
71+ return " Failed to install language server. Please open an issue on GitHub. "
4472 }
4573 }
4674 }
@@ -57,22 +85,63 @@ public struct GitHubCopilotInstallationManager {
5785 do {
5886 continuation. yield ( . downloading)
5987 let urls = try GitHubCopilotBaseService . createFoldersIfNeeded ( )
60- let executable = Bundle . main. bundleURL. appendingPathComponent ( " Contents/Applications/CopilotForXcodeExtensionService.app/Contents/Resources/copilot " )
61- guard FileManager . default. fileExists ( atPath: executable. path) else {
62- throw Error . failedToFindLanguageServer
63- }
64-
65- let targetURL = urls. executableURL. appendingPathComponent ( " copilot " )
6688
67- try FileManager . default. copyItem (
68- at: executable,
69- to: targetURL
89+ // download
90+ let ( fileURL, _) = try await URLSession . shared. download ( from: Self . downloadURL)
91+ let targetURL = urls. executableURL. appendingPathComponent ( " archive " )
92+ . appendingPathExtension ( " zip " )
93+ try FileManager . default. copyItem ( at: fileURL, to: targetURL)
94+ defer { try ? FileManager . default. removeItem ( at: targetURL) }
95+
96+ // uninstall
97+ continuation. yield ( . uninstalling)
98+ try await uninstall ( )
99+
100+ // decompress
101+ continuation. yield ( . decompressing)
102+ let terminal = Terminal ( )
103+
104+ _ = try await terminal. runCommand (
105+ " /usr/bin/unzip " ,
106+ arguments: [ targetURL. path] ,
107+ currentDirectoryPath: urls. executableURL. path,
108+ environment: [ : ]
70109 )
71110
111+ let contentURLs = try FileManager . default. contentsOfDirectory (
112+ at: urls. executableURL,
113+ includingPropertiesForKeys: nil ,
114+ options: [ ]
115+ )
116+
117+ defer {
118+ for url in contentURLs {
119+ try ? FileManager . default. removeItem ( at: url)
120+ }
121+ }
122+
123+ guard let gitFolderURL = contentURLs
124+ . first ( where: { $0. lastPathComponent. hasPrefix ( " copilot.vim " ) } )
125+ else {
126+ continuation. finish ( throwing: Error . failedToInstallLanguageServer)
127+ return
128+ }
129+
130+ let lspURL = gitFolderURL. appendingPathComponent ( " copilot " )
131+ let installationURL = urls. executableURL. appendingPathComponent ( " copilot " )
132+ try FileManager . default. copyItem ( at: lspURL, to: installationURL)
133+
72134 // update permission 755
73135 try FileManager . default. setAttributes (
74136 [ . posixPermissions: 0o755 ] ,
75- ofItemAtPath: targetURL. path
137+ ofItemAtPath: installationURL. path
138+ )
139+
140+ // create version file
141+ let data = Self . latestSupportedVersion. data ( using: . utf8)
142+ FileManager . default. createFile (
143+ atPath: urls. executableURL. appendingPathComponent ( " version " ) . path,
144+ contents: data
76145 )
77146
78147 continuation. yield ( . done)
@@ -89,8 +158,13 @@ public struct GitHubCopilotInstallationManager {
89158 else { return }
90159 let executableFolderURL = urls. executableURL
91160 let binaryURL = executableFolderURL. appendingPathComponent ( " copilot " )
161+ let versionFileURL = executableFolderURL. appendingPathComponent ( " version " )
92162 if FileManager . default. fileExists ( atPath: binaryURL. path) {
93163 try FileManager . default. removeItem ( at: binaryURL)
94164 }
165+ if FileManager . default. fileExists ( atPath: versionFileURL. path) {
166+ try FileManager . default. removeItem ( at: versionFileURL)
167+ }
95168 }
96169}
170+
0 commit comments