1
1
import { UsageError } from 'clipanion' ;
2
+ import type { FileHandle } from 'fs/promises' ;
2
3
import fs from 'fs' ;
3
4
import path from 'path' ;
4
5
import process from 'process' ;
@@ -7,13 +8,57 @@ import semver from 'semver';
7
8
import defaultConfig from '../config.json' ;
8
9
9
10
import * as corepackUtils from './corepackUtils' ;
11
+ import * as debugUtils from './debugUtils' ;
10
12
import * as folderUtils from './folderUtils' ;
13
+ import type { NodeError } from './nodeUtils' ;
11
14
import * as semverUtils from './semverUtils' ;
12
15
import { Config , Descriptor , Locator } from './types' ;
13
16
import { SupportedPackageManagers , SupportedPackageManagerSet } from './types' ;
14
17
15
18
export type PreparedPackageManagerInfo = Awaited < ReturnType < Engine [ `ensurePackageManager`] > > ;
16
19
20
+ export function getLastKnownGoodFile ( flag = `r` ) {
21
+ return fs . promises . open ( path . join ( folderUtils . getInstallFolder ( ) , `lastKnownGood.json` ) , flag ) ;
22
+ }
23
+
24
+ export async function getJSONFileContent ( fh : FileHandle ) {
25
+ let lastKnownGood : unknown ;
26
+ try {
27
+ lastKnownGood = JSON . parse ( await fh . readFile ( `utf8` ) ) ;
28
+ } catch {
29
+ // Ignore errors; too bad
30
+ return undefined ;
31
+ }
32
+
33
+ return lastKnownGood ;
34
+ }
35
+
36
+ async function overwriteJSONFileContent ( fh : FileHandle , content : unknown ) {
37
+ await fh . truncate ( 0 ) ;
38
+ await fh . write ( `${ JSON . stringify ( content , null , 2 ) } \n` , 0 ) ;
39
+ }
40
+
41
+ export function getLastKnownGoodFromFileContent ( lastKnownGood : unknown , packageManager : string ) {
42
+ if ( typeof lastKnownGood === `object` && lastKnownGood !== null &&
43
+ Object . hasOwn ( lastKnownGood , packageManager ) ) {
44
+ const override = ( lastKnownGood as any ) [ packageManager ] ;
45
+ if ( typeof override === `string` ) {
46
+ return override ;
47
+ }
48
+ }
49
+ return undefined ;
50
+ }
51
+
52
+ export async function activatePackageManagerFromFileHandle ( lastKnownGoodFile : FileHandle , lastKnownGood : unknown , locator : Locator ) {
53
+ if ( typeof lastKnownGood !== `object` || lastKnownGood === null )
54
+ lastKnownGood = { } ;
55
+
56
+ ( lastKnownGood as Record < string , string > ) [ locator . name ] = locator . reference ;
57
+
58
+ debugUtils . log ( `Setting ${ locator . name } @${ locator . reference } as Last Known Good version` ) ;
59
+ await overwriteJSONFileContent ( lastKnownGoodFile , lastKnownGood ) ;
60
+ }
61
+
17
62
export class Engine {
18
63
constructor ( public config : Config = defaultConfig as Config ) {
19
64
}
@@ -77,51 +122,52 @@ export class Engine {
77
122
if ( typeof definition === `undefined` )
78
123
throw new UsageError ( `This package manager (${ packageManager } ) isn't supported by this corepack build` ) ;
79
124
80
- let lastKnownGood : unknown ;
81
- try {
82
- lastKnownGood = JSON . parse ( await fs . promises . readFile ( this . getLastKnownGoodFile ( ) , `utf8` ) ) ;
83
- } catch {
84
- // Ignore errors; too bad
85
- }
86
-
87
- if ( typeof lastKnownGood === `object` && lastKnownGood !== null &&
88
- Object . hasOwn ( lastKnownGood , packageManager ) ) {
89
- const override = ( lastKnownGood as any ) [ packageManager ] ;
90
- if ( typeof override === `string` ) {
91
- return override ;
125
+ let emptyFile = false ;
126
+ const lastKnownGoodFile = await getLastKnownGoodFile ( `r+` ) . catch ( err => {
127
+ if ( ( err as NodeError ) ?. code === `ENOENT` ) {
128
+ emptyFile = true ;
129
+ return getLastKnownGoodFile ( `w` ) ;
92
130
}
93
- }
94
131
95
- if ( process . env . COREPACK_DEFAULT_TO_LATEST === `0` )
96
- return definition . default ;
132
+ throw err ;
133
+ } ) ;
134
+ try {
135
+ const lastKnownGood = emptyFile || await getJSONFileContent ( lastKnownGoodFile ) ;
136
+ const lastKnownGoodForThisPackageManager = getLastKnownGoodFromFileContent ( lastKnownGood , packageManager ) ;
137
+ if ( lastKnownGoodForThisPackageManager )
138
+ return lastKnownGoodForThisPackageManager ;
97
139
98
- const reference = await corepackUtils . fetchLatestStableVersion ( definition . fetchLatestFrom ) ;
140
+ if ( process . env . COREPACK_DEFAULT_TO_LATEST === `0` )
141
+ return definition . default ;
99
142
100
- await this . activatePackageManager ( {
101
- name : packageManager ,
102
- reference,
103
- } ) ;
143
+ const reference = await corepackUtils . fetchLatestStableVersion ( definition . fetchLatestFrom ) ;
144
+
145
+ await activatePackageManagerFromFileHandle ( lastKnownGoodFile , lastKnownGood , {
146
+ name : packageManager ,
147
+ reference,
148
+ } ) ;
104
149
105
- return reference ;
150
+ return reference ;
151
+ } finally {
152
+ await lastKnownGoodFile . close ( ) ;
153
+ }
106
154
}
107
155
108
156
async activatePackageManager ( locator : Locator ) {
109
- const lastKnownGoodFile = this . getLastKnownGoodFile ( ) ;
157
+ let emptyFile = false ;
158
+ const lastKnownGoodFile = await getLastKnownGoodFile ( `r+` ) . catch ( err => {
159
+ if ( ( err as NodeError ) ?. code === `ENOENT` ) {
160
+ emptyFile = true ;
161
+ return getLastKnownGoodFile ( `w` ) ;
162
+ }
110
163
111
- let lastKnownGood ;
164
+ throw err ;
165
+ } ) ;
112
166
try {
113
- lastKnownGood = JSON . parse ( await fs . promises . readFile ( lastKnownGoodFile , `utf8` ) ) ;
114
- } catch {
115
- // Ignore errors; too bad
167
+ await activatePackageManagerFromFileHandle ( lastKnownGoodFile , emptyFile || await getJSONFileContent ( lastKnownGoodFile ) , locator ) ;
168
+ } finally {
169
+ await lastKnownGoodFile . close ( ) ;
116
170
}
117
-
118
- if ( typeof lastKnownGood !== `object` || lastKnownGood === null )
119
- lastKnownGood = { } ;
120
-
121
- lastKnownGood [ locator . name ] = locator . reference ;
122
-
123
- await fs . promises . mkdir ( path . dirname ( lastKnownGoodFile ) , { recursive : true } ) ;
124
- await fs . promises . writeFile ( lastKnownGoodFile , `${ JSON . stringify ( lastKnownGood , null , 2 ) } \n` ) ;
125
171
}
126
172
127
173
async ensurePackageManager ( locator : Locator ) {
@@ -194,8 +240,4 @@ export class Engine {
194
240
195
241
return { name : finalDescriptor . name , reference : highestVersion [ 0 ] } ;
196
242
}
197
-
198
- private getLastKnownGoodFile ( ) {
199
- return path . join ( folderUtils . getInstallFolder ( ) , `lastKnownGood.json` ) ;
200
- }
201
243
}
0 commit comments