@@ -37,7 +37,7 @@ const releaseValidator: validate.Validator<IRelease> = validate.object({
37
37
38
38
const githubReleaseApiValidator : validate . Validator < IRelease [ ] > = validate . array ( releaseValidator ) ;
39
39
40
- const cachedReleaseValidator : validate . Validator < IRelease | null > = validate . optional ( releaseValidator ) ;
40
+ const cachedReleaseValidator : validate . Validator < IRelease [ ] | null > = validate . optional ( githubReleaseApiValidator ) ;
41
41
42
42
// On Windows the executable needs to be stored somewhere with an .exe extension
43
43
const exeExt = process . platform === 'win32' ? '.exe' : '' ;
@@ -85,7 +85,7 @@ class NoBinariesError extends Error {
85
85
const supportedReleasesLink =
86
86
'[See the list of supported versions here](https://github.com/haskell/vscode-haskell#supported-ghc-versions)' ;
87
87
if ( ghcVersion ) {
88
- super ( `haskell-language-server ${ hlsVersion } for GHC ${ ghcVersion } is not available on ${ os . type ( ) } .
88
+ super ( `haskell-language-server ${ hlsVersion } or earlier for GHC ${ ghcVersion } is not available on ${ os . type ( ) } .
89
89
${ supportedReleasesLink } ` ) ;
90
90
} else {
91
91
super ( `haskell-language-server ${ hlsVersion } is not available on ${ os . type ( ) } .
@@ -205,7 +205,11 @@ async function getProjectGhcVersion(
205
205
return callWrapper ( downloadedWrapper ) ;
206
206
}
207
207
208
- async function getLatestReleaseMetadata ( context : ExtensionContext , storagePath : string ) : Promise < IRelease | null > {
208
+ async function getReleaseMetadata (
209
+ context : ExtensionContext ,
210
+ storagePath : string ,
211
+ logger : Logger
212
+ ) : Promise < IRelease [ ] | null > {
209
213
const releasesUrl = workspace . getConfiguration ( 'haskell' ) . releasesURL
210
214
? url . parse ( workspace . getConfiguration ( 'haskell' ) . releasesURL )
211
215
: undefined ;
@@ -219,9 +223,28 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath:
219
223
path : '/repos/haskell/haskell-language-server/releases' ,
220
224
} ;
221
225
222
- const offlineCache = path . join ( storagePath , 'latestApprovedRelease.cache.json' ) ;
226
+ const offlineCache = path . join ( storagePath , 'approvedReleases.cache.json' ) ;
227
+ const offlineCacheOldFormat = path . join ( storagePath , 'latestApprovedRelease.cache.json' ) ;
223
228
224
- async function readCachedReleaseData ( ) : Promise < IRelease | null > {
229
+ // Migrate existing old cache file latestApprovedRelease.cache.json to the new cache file
230
+ // approvedReleases.cache.json if no such file exists yet.
231
+ if ( ! fs . existsSync ( offlineCache ) ) {
232
+ try {
233
+ const oldCachedInfo = await promisify ( fs . readFile ) ( offlineCacheOldFormat , { encoding : 'utf-8' } ) ;
234
+ const oldCachedInfoParsed = validate . parseAndValidate ( oldCachedInfo , validate . optional ( releaseValidator ) ) ;
235
+ if ( oldCachedInfoParsed !== null ) {
236
+ await promisify ( fs . writeFile ) ( offlineCache , JSON . stringify ( [ oldCachedInfoParsed ] ) , { encoding : 'utf-8' } ) ;
237
+ }
238
+ logger . info ( `Successfully migrated ${ offlineCacheOldFormat } to ${ offlineCache } ` ) ;
239
+ } catch ( err : any ) {
240
+ // Ignore if old cache file does not exist
241
+ if ( err . code !== 'ENOENT' ) {
242
+ logger . error ( `Failed to migrate ${ offlineCacheOldFormat } to ${ offlineCache } : ${ err } ` ) ;
243
+ }
244
+ }
245
+ }
246
+
247
+ async function readCachedReleaseData ( ) : Promise < IRelease [ ] | null > {
225
248
try {
226
249
const cachedInfo = await promisify ( fs . readFile ) ( offlineCache , { encoding : 'utf-8' } ) ;
227
250
return validate . parseAndValidate ( cachedInfo , cachedReleaseValidator ) ;
@@ -242,15 +265,16 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath:
242
265
243
266
try {
244
267
const releaseInfo = await httpsGetSilently ( opts ) ;
245
- const latestInfoParsed =
246
- validate . parseAndValidate ( releaseInfo , githubReleaseApiValidator ) . find ( ( x ) => ! x . prerelease ) || null ;
268
+ const releaseInfoParsed =
269
+ validate . parseAndValidate ( releaseInfo , githubReleaseApiValidator ) . filter ( ( x ) => ! x . prerelease ) || null ;
247
270
248
271
if ( updateBehaviour === 'prompt' ) {
249
272
const cachedInfoParsed = await readCachedReleaseData ( ) ;
250
273
251
274
if (
252
- latestInfoParsed !== null &&
253
- ( cachedInfoParsed === null || latestInfoParsed . tag_name !== cachedInfoParsed . tag_name )
275
+ releaseInfoParsed !== null && releaseInfoParsed . length > 0 &&
276
+ ( cachedInfoParsed === null || cachedInfoParsed . length === 0
277
+ || releaseInfoParsed [ 0 ] . tag_name !== cachedInfoParsed [ 0 ] . tag_name )
254
278
) {
255
279
const promptMessage =
256
280
cachedInfoParsed === null
@@ -266,8 +290,8 @@ async function getLatestReleaseMetadata(context: ExtensionContext, storagePath:
266
290
}
267
291
268
292
// Cache the latest successfully fetched release information
269
- await promisify ( fs . writeFile ) ( offlineCache , JSON . stringify ( latestInfoParsed ) , { encoding : 'utf-8' } ) ;
270
- return latestInfoParsed ;
293
+ await promisify ( fs . writeFile ) ( offlineCache , JSON . stringify ( releaseInfoParsed ) , { encoding : 'utf-8' } ) ;
294
+ return releaseInfoParsed ;
271
295
} catch ( githubError : any ) {
272
296
// Attempt to read from the latest cached file
273
297
try {
@@ -316,8 +340,8 @@ export async function downloadHaskellLanguageServer(
316
340
}
317
341
318
342
logger . info ( 'Fetching the latest release from GitHub or from cache' ) ;
319
- const release = await getLatestReleaseMetadata ( context , storagePath ) ;
320
- if ( ! release ) {
343
+ const releases = await getReleaseMetadata ( context , storagePath , logger ) ;
344
+ if ( ! releases ) {
321
345
let message = "Couldn't find any pre-built haskell-language-server binaries" ;
322
346
const updateBehaviour = workspace . getConfiguration ( 'haskell' ) . get ( 'updateBehavior' ) as UpdateBehaviour ;
323
347
if ( updateBehaviour === 'never-check' ) {
@@ -326,12 +350,12 @@ export async function downloadHaskellLanguageServer(
326
350
window . showErrorMessage ( message ) ;
327
351
return null ;
328
352
}
329
- logger . info ( `The latest release is ${ release . tag_name } ` ) ;
353
+ logger . info ( `The latest release is ${ releases [ 0 ] . tag_name } ` ) ;
330
354
logger . info ( 'Figure out the ghc version to use or advertise an installation link for missing components' ) ;
331
355
const dir : string = folder ?. uri ?. fsPath ?? path . dirname ( resource . fsPath ) ;
332
356
let ghcVersion : string ;
333
357
try {
334
- ghcVersion = await getProjectGhcVersion ( context , logger , dir , release , storagePath ) ;
358
+ ghcVersion = await getProjectGhcVersion ( context , logger , dir , releases [ 0 ] , storagePath ) ;
335
359
} catch ( error ) {
336
360
if ( error instanceof MissingToolError ) {
337
361
const link = error . installLink ( ) ;
@@ -354,29 +378,37 @@ export async function downloadHaskellLanguageServer(
354
378
// When searching for binaries, use startsWith because the compression may differ
355
379
// between .zip and .gz
356
380
const assetName = `haskell-language-server-${ githubOS } -${ ghcVersion } ${ exeExt } ` ;
357
- logger . info ( `Search for binary ${ assetName } in release assests` ) ;
381
+ logger . info ( `Search for binary ${ assetName } in release assets` ) ;
382
+ const release = releases ?. find ( r => r . assets . find ( ( x ) => x . name . startsWith ( assetName ) ) ) ;
358
383
const asset = release ?. assets . find ( ( x ) => x . name . startsWith ( assetName ) ) ;
359
384
if ( ! asset ) {
360
385
logger . error (
361
- `No binary ${ assetName } found in the release assets: ${ release ?. assets . map ( ( value ) => value . name ) . join ( ',' ) } `
386
+ `No binary ${ assetName } found in the release assets`
362
387
) ;
363
- window . showInformationMessage ( new NoBinariesError ( release . tag_name , ghcVersion ) . message ) ;
388
+ window . showInformationMessage ( new NoBinariesError ( releases [ 0 ] . tag_name , ghcVersion ) . message ) ;
364
389
return null ;
365
390
}
366
391
367
- const serverName = `haskell-language-server-${ release . tag_name } -${ process . platform } -${ ghcVersion } ${ exeExt } ` ;
392
+ const serverName = `haskell-language-server-${ release ? .tag_name } -${ process . platform } -${ ghcVersion } ${ exeExt } ` ;
368
393
const binaryDest = path . join ( storagePath , serverName ) ;
369
394
370
- const title = `Downloading haskell-language-server ${ release . tag_name } for GHC ${ ghcVersion } ` ;
395
+ const title = `Downloading haskell-language-server ${ release ? .tag_name } for GHC ${ ghcVersion } ` ;
371
396
logger . info ( title ) ;
372
- await downloadFile ( title , asset . browser_download_url , binaryDest ) ;
397
+ const downloaded = await downloadFile ( title , asset . browser_download_url , binaryDest ) ;
373
398
if ( ghcVersion . startsWith ( '9.' ) ) {
374
399
const warning =
375
400
'Currently, HLS supports GHC 9 only partially. ' +
376
401
'See [issue #297](https://github.com/haskell/haskell-language-server/issues/297) for more detail.' ;
377
402
logger . warn ( warning ) ;
378
403
window . showWarningMessage ( warning ) ;
379
404
}
405
+ if ( release ?. tag_name !== releases [ 0 ] . tag_name ) {
406
+ const warning = `haskell-language-server ${ releases [ 0 ] . tag_name } for GHC ${ ghcVersion } is not available on ${ os . type ( ) } . Falling back to haskell-language-server ${ release ?. tag_name } ` ;
407
+ logger . warn ( warning ) ;
408
+ if ( downloaded ) {
409
+ window . showInformationMessage ( warning ) ;
410
+ }
411
+ }
380
412
return binaryDest ;
381
413
}
382
414
0 commit comments