@@ -10,14 +10,22 @@ import defaultConfig from '../config.js
10
10
import * as corepackUtils from './corepackUtils' ;
11
11
import * as debugUtils from './debugUtils' ;
12
12
import * as folderUtils from './folderUtils' ;
13
+ import * as miscUtils from './miscUtils' ;
13
14
import type { NodeError } from './nodeUtils' ;
14
15
import * as semverUtils from './semverUtils' ;
16
+ import * as specUtils from './specUtils' ;
15
17
import { Config , Descriptor , Locator , PackageManagerSpec } from './types' ;
16
18
import { SupportedPackageManagers , SupportedPackageManagerSet } from './types' ;
17
19
import { isSupportedPackageManager } from './types' ;
18
20
19
21
export type PreparedPackageManagerInfo = Awaited < ReturnType < Engine [ `ensurePackageManager`] > > ;
20
22
23
+ export type PackageManagerRequest = {
24
+ packageManager : SupportedPackageManagers ;
25
+ binaryName : string ;
26
+ binaryVersion : string | null ;
27
+ } ;
28
+
21
29
export function getLastKnownGoodFile ( flag = `r` ) {
22
30
return fs . promises . open ( path . join ( folderUtils . getCorepackHomeFolder ( ) , `lastKnownGood.json` ) , flag ) ;
23
31
}
@@ -200,15 +208,139 @@ export class Engine {
200
208
spec,
201
209
} ) ;
202
210
211
+ const noHashReference = locator . reference . replace ( / \+ .* / , `` ) ;
212
+ const fixedHashReference = `${ noHashReference } +${ packageManagerInfo . hash } ` ;
213
+
214
+ const fixedHashLocator = {
215
+ name : locator . name ,
216
+ reference : fixedHashReference ,
217
+ } ;
218
+
203
219
return {
204
220
...packageManagerInfo ,
205
- locator,
221
+ locator : fixedHashLocator ,
206
222
spec,
207
223
} ;
208
224
}
209
225
210
- async fetchAvailableVersions ( ) {
226
+ /**
227
+ * Locates the active project's package manager specification.
228
+ *
229
+ * If the specification exists but doesn't match the active package manager,
230
+ * an error is thrown to prevent users from using the wrong package manager,
231
+ * which would lead to inconsistent project layouts.
232
+ *
233
+ * If the project doesn't include a specification file, we just assume that
234
+ * whatever the user uses is exactly what they want to use. Since the version
235
+ * isn't explicited, we fallback on known good versions.
236
+ *
237
+ * Finally, if the project doesn't exist at all, we ask the user whether they
238
+ * want to create one in the current project. If they do, we initialize a new
239
+ * project using the default package managers, and configure it so that we
240
+ * don't need to ask again in the future.
241
+ */
242
+ async findProjectSpec ( initialCwd : string , locator : Locator , { transparent = false } : { transparent ?: boolean } = { } ) : Promise < Descriptor > {
243
+ // A locator is a valid descriptor (but not the other way around)
244
+ const fallbackDescriptor = { name : locator . name , range : `${ locator . reference } ` } ;
245
+
246
+ if ( process . env . COREPACK_ENABLE_PROJECT_SPEC === `0` )
247
+ return fallbackDescriptor ;
248
+
249
+ if ( process . env . COREPACK_ENABLE_STRICT === `0` )
250
+ transparent = true ;
251
+
252
+ while ( true ) {
253
+ const result = await specUtils . loadSpec ( initialCwd ) ;
254
+
255
+ switch ( result . type ) {
256
+ case `NoProject` :
257
+ return fallbackDescriptor ;
258
+
259
+ case `NoSpec` : {
260
+ if ( process . env . COREPACK_ENABLE_AUTO_PIN !== `0` ) {
261
+ const resolved = await this . resolveDescriptor ( fallbackDescriptor , { allowTags : true } ) ;
262
+ if ( resolved === null )
263
+ throw new UsageError ( `Failed to successfully resolve '${ fallbackDescriptor . range } ' to a valid ${ fallbackDescriptor . name } release` ) ;
264
+
265
+ const installSpec = await this . ensurePackageManager ( resolved ) ;
266
+
267
+ console . error ( `! The local project doesn't define a 'packageManager' field. Corepack will now add one referencing ${ installSpec . locator . name } @${ installSpec . locator . reference } .` ) ;
268
+ console . error ( `! For more details about this field, consult the documentation at https://nodejs.org/api/packages.html#packagemanager` ) ;
269
+ console . error ( ) ;
270
+
271
+ await specUtils . setLocalPackageManager ( path . dirname ( result . target ) , installSpec ) ;
272
+ }
273
+
274
+ return fallbackDescriptor ;
275
+ }
276
+
277
+ case `Found` : {
278
+ if ( result . spec . name !== locator . name ) {
279
+ if ( transparent ) {
280
+ return fallbackDescriptor ;
281
+ } else {
282
+ throw new UsageError ( `This project is configured to use ${ result . spec . name } ` ) ;
283
+ }
284
+ } else {
285
+ return result . spec ;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ async executePackageManagerRequest ( { packageManager, binaryName, binaryVersion} : PackageManagerRequest , { cwd, args} : { cwd : string , args : Array < string > } ) : Promise < number | void > {
293
+ let fallbackLocator : Locator = {
294
+ name : binaryName as SupportedPackageManagers ,
295
+ reference : undefined as any ,
296
+ } ;
297
+
298
+ let isTransparentCommand = false ;
299
+ if ( packageManager != null ) {
300
+ const defaultVersion = await this . getDefaultVersion ( packageManager ) ;
301
+ const definition = this . config . definitions [ packageManager ] ! ;
302
+
303
+ // If all leading segments match one of the patterns defined in the `transparent`
304
+ // key, we tolerate calling this binary even if the local project isn't explicitly
305
+ // configured for it, and we use the special default version if requested.
306
+ for ( const transparentPath of definition . transparent . commands ) {
307
+ if ( transparentPath [ 0 ] === binaryName && transparentPath . slice ( 1 ) . every ( ( segment , index ) => segment === args [ index ] ) ) {
308
+ isTransparentCommand = true ;
309
+ break ;
310
+ }
311
+ }
312
+
313
+ const fallbackReference = isTransparentCommand
314
+ ? definition . transparent . default ?? defaultVersion
315
+ : defaultVersion ;
316
+
317
+ fallbackLocator = {
318
+ name : packageManager ,
319
+ reference : fallbackReference ,
320
+ } ;
321
+ }
322
+
323
+ let descriptor : Descriptor ;
324
+ try {
325
+ descriptor = await this . findProjectSpec ( cwd , fallbackLocator , { transparent : isTransparentCommand } ) ;
326
+ } catch ( err ) {
327
+ if ( err instanceof miscUtils . Cancellation ) {
328
+ return 1 ;
329
+ } else {
330
+ throw err ;
331
+ }
332
+ }
333
+
334
+ if ( binaryVersion )
335
+ descriptor . range = binaryVersion ;
336
+
337
+ const resolved = await this . resolveDescriptor ( descriptor , { allowTags : true } ) ;
338
+ if ( resolved === null )
339
+ throw new UsageError ( `Failed to successfully resolve '${ descriptor . range } ' to a valid ${ descriptor . name } release` ) ;
340
+
341
+ const installSpec = await this . ensurePackageManager ( resolved ) ;
211
342
343
+ return await corepackUtils . runVersion ( resolved , installSpec , binaryName , args ) ;
212
344
}
213
345
214
346
async resolveDescriptor ( descriptor : Descriptor , { allowTags = false , useCache = true } : { allowTags ?: boolean , useCache ?: boolean } = { } ) : Promise < Locator | null > {
0 commit comments