diff --git a/README.md b/README.md index a83114e48..877b98568 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,14 @@ Set your package's manager with the `packageManager` field in `package.json`: ``` Here, `yarn` is the name of the package manager, specified at version `3.2.3`, -along with the SHA-224 hash of this version for validation. -`packageManager@x.y.z` is required. The hash is optional but strongly +along with the SHA-224 hash of this version for validation. The hash is optional but strongly recommended as a security practice. Permitted values for the package manager are `yarn`, `npm`, and `pnpm`. +To set this string, it is recommended to use the +[`corepack use` command](#corepack-use-nameversion), which will resolve the +full version number and set the hash appropriately. + ## Known Good Releases When running Corepack within projects that don't list a supported package diff --git a/sources/commands/Base.ts b/sources/commands/Base.ts index 01139766b..01dedf947 100644 --- a/sources/commands/Base.ts +++ b/sources/commands/Base.ts @@ -14,7 +14,7 @@ export abstract class BaseCommand extends Command { 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); diff --git a/sources/commands/InstallGlobal.ts b/sources/commands/InstallGlobal.ts index 788fb1f1b..14d146ff1 100644 --- a/sources/commands/InstallGlobal.ts +++ b/sources/commands/InstallGlobal.ts @@ -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 { diff --git a/sources/commands/Use.ts b/sources/commands/Use.ts index aa75db9ae..e0d7df7ee 100644 --- a/sources/commands/Use.ts +++ b/sources/commands/Use.ts @@ -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'`, ]], }); diff --git a/sources/commands/deprecated/Prepare.ts b/sources/commands/deprecated/Prepare.ts index c9acc5107..19aad0937 100644 --- a/sources/commands/deprecated/Prepare.ts +++ b/sources/commands/deprecated/Prepare.ts @@ -58,7 +58,7 @@ export class PrepareCommand extends Command { 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}); diff --git a/sources/specUtils.ts b/sources/specUtils.ts index f1bc1f434..1f3da7a0e 100644 --- a/sources/specUtils.ts +++ b/sources/specUtils.ts @@ -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}; } /** diff --git a/tests/main.test.ts b/tests/main.test.ts index 3538ff3dd..fa5d2a07e 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -29,16 +29,16 @@ it(`should refuse to download a package manager if the hash doesn't match`, asyn }); }); -it(`should require a version to be specified`, async () => { +it(`should resolve version from package.json`, async () => { await xfs.mktempPromise(async cwd => { await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { packageManager: `yarn`, }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - exitCode: 1, + exitCode: 0, stderr: ``, - stdout: /expected a semver version/, + stdout: /1\./, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { @@ -46,9 +46,9 @@ it(`should require a version to be specified`, async () => { }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - exitCode: 1, + exitCode: 0, stderr: ``, - stdout: /expected a semver version/, + stdout: /1\./, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { @@ -56,9 +56,9 @@ it(`should require a version to be specified`, async () => { }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - exitCode: 1, + exitCode: 0, stderr: ``, - stdout: /expected a semver version/, + stdout: /1\./, }); }); });