@@ -10,14 +10,22 @@ import defaultConfig from '../config.js
1010import * as corepackUtils from './corepackUtils' ;
1111import * as debugUtils from './debugUtils' ;
1212import * as folderUtils from './folderUtils' ;
13+ import * as miscUtils from './miscUtils' ;
1314import type { NodeError } from './nodeUtils' ;
1415import * as semverUtils from './semverUtils' ;
16+ import * as specUtils from './specUtils' ;
1517import { Config , Descriptor , Locator , PackageManagerSpec } from './types' ;
1618import { SupportedPackageManagers , SupportedPackageManagerSet } from './types' ;
1719import { isSupportedPackageManager } from './types' ;
1820
1921export type PreparedPackageManagerInfo = Awaited < ReturnType < Engine [ `ensurePackageManager`] > > ;
2022
23+ export type PackageManagerRequest = {
24+ packageManager : SupportedPackageManagers ;
25+ binaryName : string ;
26+ binaryVersion : string | null ;
27+ } ;
28+
2129export function getLastKnownGoodFile ( flag = `r` ) {
2230 return fs . promises . open ( path . join ( folderUtils . getCorepackHomeFolder ( ) , `lastKnownGood.json` ) , flag ) ;
2331}
@@ -200,15 +208,139 @@ export class Engine {
200208 spec,
201209 } ) ;
202210
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+
203219 return {
204220 ...packageManagerInfo ,
205- locator,
221+ locator : fixedHashLocator ,
206222 spec,
207223 } ;
208224 }
209225
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 ) ;
211342
343+ return await corepackUtils . runVersion ( resolved , installSpec , binaryName , args ) ;
212344 }
213345
214346 async resolveDescriptor ( descriptor : Descriptor , { allowTags = false , useCache = true } : { allowTags ?: boolean , useCache ?: boolean } = { } ) : Promise < Locator | null > {
0 commit comments