Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion sources/commands/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export abstract class BaseCommand extends Command<Context> {

const resolvedSpecs = all
? await this.context.engine.getDefaultDescriptors()
: patterns.map(pattern => specUtils.parseSpec(pattern, `CLI arguments`, {enforceExactVersion: false}));
: patterns.map(pattern => specUtils.parseSpec(pattern, `CLI arguments`));

if (resolvedSpecs.length === 0) {
const lookup = await specUtils.loadSpec(this.context.cwd);
Expand Down
2 changes: 1 addition & 1 deletion sources/commands/InstallGlobal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class InstallGlobalCommand extends BaseCommand {
if (arg.endsWith(`.tgz`)) {
await this.installFromTarball(path.resolve(this.context.cwd, arg));
} else {
await this.installFromDescriptor(specUtils.parseSpec(arg, `CLI arguments`, {enforceExactVersion: false}));
await this.installFromDescriptor(specUtils.parseSpec(arg, `CLI arguments`));
}
}
} else {
Expand Down
10 changes: 8 additions & 2 deletions sources/commands/Use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ export class UseCommand extends BaseCommand {
automatically perform an install.
`,
examples: [[
`Configure the project to use the latest Yarn release`,
`corepack use 'yarn@*'`,
`Configure the project to use the latest pnpm release`,
`corepack use pnpm`,
], [
`Use a tagged version`,
`corepack use yarn@stable`,
], [
`Use a partial version number`,
`corepack use 'yarn@3'`,
]],
});

Expand Down
2 changes: 1 addition & 1 deletion sources/commands/deprecated/Prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class PrepareCommand extends Command<Context> {

for (const request of specs) {
const spec = typeof request === `string`
? specUtils.parseSpec(request, `CLI arguments`, {enforceExactVersion: false})
? specUtils.parseSpec(request, `CLI arguments`)
: request;

const resolved = await this.context.engine.resolveDescriptor(spec, {allowTags: true});
Expand Down
22 changes: 12 additions & 10 deletions sources/specUtils.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import semver from 'semver';

import {Descriptor, Locator, isSupportedPackageManager} from './types';

const nodeModulesRegExp = /[\\/]node_modules[\\/](@[^\\/]*[\\/])?([^@\\/][^\\/]*)$/;

export function parseSpec(raw: unknown, source: string, {enforceExactVersion = true} = {}): Descriptor {
export function parseSpec(raw: unknown, source: string): Descriptor {
if (typeof raw !== `string`)
throw new UsageError(`Invalid package manager specification in ${source}; expected a string`);

const match = raw.match(/^(?!_)(.+)@(.+)$/);
if (match === null || (enforceExactVersion && !semver.valid(match[2])))
throw new UsageError(`Invalid package manager specification in ${source}; expected a semver version${enforceExactVersion ? `` : `, range, or tag`}`);
const match = /^(@?[^@]+)(?:@(.+))?$/.exec(raw);
const name = match?.[1];
const range = match?.[2] ?? `*`;

if (!isSupportedPackageManager(match[1]))
if (!name) {
throw new UsageError(
`Invalid package manager specification in ${source}. Could not determine package manager name`,
);
}

if (!isSupportedPackageManager(name))
throw new UsageError(`Unsupported package manager specification (${match})`);

return {
name: match[1],
range: match[2],
};
return {name, range};
}

/**
Expand Down