Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 54 additions & 47 deletions sources/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,20 @@ export class Engine {
}

/**
* Locates the active project's package manager specification.
* Locates the package manager specification from nearest package.json having one.
*
* If the specification exists but doesn't match the active package manager,
* an error is thrown to prevent users from using the wrong package manager,
* which would lead to inconsistent project layouts.
*
* If the project doesn't include a specification file, we just assume that
* whatever the user uses is exactly what they want to use. Since the version
* isn't specified, we fallback on known good versions.
* If none of the package.json files have specification,
* or a package.json had specification which was invalid but allowed failure,
* we just assume that whatever the user uses is exactly what they want to use.
* Since the version isn't specified, we fallback on known good versions.
*
* Finally, if the project doesn't exist at all, we ask the user whether they
* want to create one in the current project. If they do, we initialize a new
* project using the default package managers, and configure it so that we
* don't need to ask again in the future.
* Finally, if the package.json doesn't exist at all,
* we just assume that whatever the user uses is exactly what they want to use.
* Since the version isn't specified, we fallback on known good versions.
*/
async findProjectSpec(initialCwd: string, locator: Locator | LazyLocator, {transparent = false, binaryVersion}: {transparent?: boolean, binaryVersion?: string | null} = {}): Promise<Descriptor> {
// A locator is a valid descriptor (but not the other way around)
Expand All @@ -258,59 +258,66 @@ export class Engine {
if (process.env.COREPACK_ENABLE_STRICT === `0`)
transparent = true;

while (true) {
const result = await specUtils.loadSpec(initialCwd);
const result = await specUtils.loadSpec(initialCwd);

switch (result.type) {
case `NoProject`: {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
switch (result.type) {
case `NoProject`: {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();

debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} as no project manifest were found`);
return fallbackDescriptor;
}
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} as no project manifest were found`);
return fallbackDescriptor;
}

case `NoSpec`: {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
case `NoSpec`: {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();

if (process.env.COREPACK_ENABLE_AUTO_PIN === `1`) {
const resolved = await this.resolveDescriptor(fallbackDescriptor, {allowTags: true});
if (resolved === null)
throw new UsageError(`Failed to successfully resolve '${fallbackDescriptor.range}' to a valid ${fallbackDescriptor.name} release`);
if (process.env.COREPACK_ENABLE_AUTO_PIN === `1`) {
const resolved = await this.resolveDescriptor(fallbackDescriptor, {allowTags: true});
if (resolved === null)
throw new UsageError(`Failed to successfully resolve '${fallbackDescriptor.range}' to a valid ${fallbackDescriptor.name} release`);

const installSpec = await this.ensurePackageManager(resolved);
const installSpec = await this.ensurePackageManager(resolved);

console.error(`! The local project doesn't define a 'packageManager' field. Corepack will now add one referencing ${installSpec.locator.name}@${installSpec.locator.reference}.`);
console.error(`! For more details about this field, consult the documentation at https://nodejs.org/api/packages.html#packagemanager`);
console.error();
console.error(`! The local project doesn't define a 'packageManager' field. Corepack will now add one referencing ${installSpec.locator.name}@${installSpec.locator.reference}.`);
console.error(`! For more details about this field, consult the documentation at https://nodejs.org/api/packages.html#packagemanager`);
console.error();

await specUtils.setLocalPackageManager(path.dirname(result.target), installSpec);
}

debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in the absence of "packageManager" field in ${result.target}`);
return fallbackDescriptor;
await specUtils.setLocalPackageManager(path.dirname(result.target), installSpec);
}

case `Found`: {
const spec = result.getSpec({enforceExactVersion: !binaryVersion});
if (spec.name !== locator.name) {
if (transparent) {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();

debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in a ${spec.name}@${spec.range} project`);
return fallbackDescriptor;
} else {
throw new UsageError(`This project is configured to use ${spec.name} because ${result.target} has a "packageManager" field`);
}
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in the absence of "packageManager" field in ${result.target}`);
return fallbackDescriptor;
}

case `Found`: {
const spec = result.getSpec({enforceExactVersion: !binaryVersion});
if (spec.name !== locator.name) {
const devEnginesSayMismatchIsNotError = result.sourceField === `devEngines.packageManager`
&& result.devEnginesRange !== undefined
&& result.devEnginesRange.onFail !== `error`;

if (transparent || devEnginesSayMismatchIsNotError) {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();

if (devEnginesSayMismatchIsNotError && result.devEnginesRange!.onFail === `warn`)
console.warn(`! Corepack validation warning: Using ${fallbackDescriptor.name} as requested (@${fallbackDescriptor.range}) because ${result.target} defines "devEngines.packageManager" with mismatched ${spec.name}@${spec.range} and onFail: warn.`);

debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in a ${spec.name}@${spec.range} project`);
return fallbackDescriptor;
} else {
debugUtils.log(`Using ${spec.name}@${spec.range} as defined in project manifest ${result.target}`);
return spec;
throw new UsageError(`This project is configured to use ${spec.name} because ${result.target} has a "${result.sourceField}" field`);
}
} else {
debugUtils.log(`Using ${spec.name}@${spec.range} as defined in project manifest ${result.target}`);
return spec;
}
}
}

throw new Error(`Assertion failed: Unsupported loadSpec result type`);
}

async executePackageManagerRequest({packageManager, binaryName, binaryVersion}: PackageManagerRequest, {cwd, args}: {cwd: string, args: Array<string>}): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion sources/commands/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export abstract class BaseCommand extends Command<Context> {
throw new UsageError(`The local project doesn't feature a 'packageManager' field nor a 'devEngines.packageManager' field - please specify the package manager to pack, or update the manifest to reference it`);

default: {
return [lookup.range ?? lookup.getSpec()];
return [lookup.devEnginesRange ?? lookup.getSpec()];
}
}
}
Expand Down
Loading