Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
`[email protected]` 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
Expand Down
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
14 changes: 7 additions & 7 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,36 @@ 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), {
packageManager: `yarn@stable`,
});

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), {
packageManager: `yarn@^1.0.0`,
});

await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
exitCode: 0,
stderr: ``,
stdout: /expected a semver version/,
stdout: /1\./,
});
});
});
Expand Down