@@ -6,6 +6,11 @@ import * as Which from "which";
66import { commands , ConfigurationTarget , env , MessageItem , ProgressLocation , QuickPickItem , QuickPickOptions , Uri , window , workspace } from "vscode" ;
77import { exec } from 'child_process' ;
88
9+ import { stat , writeFile , utimes } from 'fs/promises' ;
10+
11+ import fetch , { Response } from 'node-fetch' ;
12+
13+
914async function getApiKeyInput ( ) {
1015 const result = await window . showInputBox ( {
1116 placeHolder : 'Paste your API key here' ,
@@ -82,10 +87,33 @@ async function setBinaryPaths(openUri: Uri) {
8287 await config . update ( 'terminal.binary' , binaryPath , ConfigurationTarget . Global ) ;
8388}
8489
90+ async function markerFilename ( ) : Promise < string > {
91+ const config = workspace . getConfiguration ( 'imandrax' ) ;
92+ const binaryPath = await config . get ( 'lsp.binary' ) ;
93+ const binaryDir = Path . dirname ( binaryPath as string ) ;
94+ return Path . join ( binaryDir , 'imandrax-cli.installed_by_vscode' ) ;
95+ }
96+
97+ async function markInstalled ( ) : Promise < void > {
98+ const markerFile = await markerFilename ( ) ;
99+ await writeFile ( markerFile , '' ) ;
100+ await utimes ( markerFile , Date . now ( ) , Date . now ( ) ) ;
101+ }
102+
103+ export async function installedByUs ( ) : Promise < boolean > {
104+ try {
105+ await stat ( await markerFilename ( ) ) ;
106+ return true ;
107+ } catch {
108+ return false ;
109+ }
110+ }
111+
85112async function handleSuccess ( openUri : Uri ) {
86113 await setBinaryPaths ( openUri ) ;
87114 await promptForApiKey ( ) ;
88115 await promptToReloadWindow ( ) ;
116+ await markInstalled ( ) ;
89117}
90118
91119async function runInstallerForUnix ( itemT : MessageItem , title : string ) : Promise < void > {
@@ -127,12 +155,15 @@ async function runInstallerForUnix(itemT: MessageItem, title: string): Promise<v
127155 }
128156}
129157
130- export async function promptToInstall ( openUri : Uri ) {
158+ export async function promptToInstall ( openUri : Uri , update ?: boolean ) {
131159 const launchInstallerItem = { title : "Launch installer" } as const ;
132160 const items : readonly MessageItem [ ] = [ launchInstallerItem ] ;
133-
134- const itemT : MessageItem | undefined = await window . showErrorMessage ( `Could not find ImandraX. Please install it or ensure the imandrax-cli binary is in your PATH or its location is set in [Workspace Settings](${ openUri . toString ( ) } ).` , ...items ) ;
135-
161+ let itemT : MessageItem | undefined ;
162+ if ( update ) {
163+ itemT = await window . showInformationMessage ( `An updated imandrax-cli binary is available.` , ...items ) ;
164+ } else {
165+ itemT = await window . showErrorMessage ( `Could not find ImandraX. Please install it or ensure the imandrax-cli binary is in your PATH or its location is set in [Workspace Settings](${ openUri . toString ( ) } ).` , ...items ) ;
166+ }
136167 if ( itemT ) {
137168 await window . withProgress (
138169 {
@@ -141,7 +172,56 @@ export async function promptToInstall(openUri: Uri) {
141172 } ,
142173 ( ) => runInstallerForUnix ( itemT , launchInstallerItem . title ) ) . then (
143174 ( ) => handleSuccess ( openUri ) ,
144- async ( reason ) => { await window . showErrorMessage ( `ImandraX install failed\n ${ reason } ` ) ; }
175+ async ( reason ) => { await window . showErrorMessage ( `ImandraX installation failed\n ${ reason } ` ) ; }
145176 ) ;
146177 }
147178}
179+
180+ interface ReleaseObject {
181+ name : string ;
182+ generation : string ; // not guaranteed to increase
183+ meta_generation : string ; // guaranteed to increase, but not a time
184+ timeCreated : string ;
185+ timeFinalized : string ;
186+ timeStorageClassUpdated : string ;
187+ updated : string
188+ }
189+
190+ export async function updateAvailable ( ) {
191+ try {
192+ async function getFileModificationDate ( filePath : string ) : Promise < Date > {
193+ const stats = await stat ( filePath ) ;
194+ return stats . mtime ;
195+ }
196+
197+ async function getData ( pkg_name : string ) : Promise < ReleaseObject > {
198+ const url = `https://storage.googleapis.com/storage/v1/b/imandra-prod-imandrax-releases/o/${ pkg_name } ` ;
199+ const response : Response = await fetch ( url ) ;
200+ if ( ! response . ok ) {
201+ throw new Error ( `Response status: ${ response . status } ` ) ;
202+ }
203+ return await response . json ( ) as ReleaseObject ;
204+ }
205+
206+ let pkg_name = "" ;
207+ if ( process . platform == "linux" && process . arch == "x64" )
208+ pkg_name = "imandrax-linux-x86_64-latest.tar.gz" ;
209+ else if ( process . platform == "darwin" && process . arch == "arm64" )
210+ pkg_name = "imandrax-macos-aarch64-latest.pkg" ;
211+ else if ( process . platform == "darwin" && process . arch == "x64" )
212+ pkg_name = "imandrax-macos-x64-latest.pkg" ;
213+ else {
214+ throw new Error ( `Unsupported platform/architecture ${ process . platform } -${ process . arch } ` ) ;
215+ }
216+
217+ const data : ReleaseObject = await getData ( pkg_name ) ;
218+ const marker : string = await markerFilename ( ) ;
219+ const remoteTime : number = Date . parse ( data . updated )
220+ const markerTime : number | undefined = ( await getFileModificationDate ( marker ) ) ?. getTime ( ) ;
221+
222+ return markerTime && remoteTime > markerTime ;
223+ } catch ( e ) {
224+ // Don't annoy the user with a showErrorMessage about our bugs or network outages, just log them.
225+ console . log ( `Update check failed: ${ ( e as Error ) . message } .` ) ;
226+ }
227+ }
0 commit comments