Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow packageManager to be specified by name alone #300

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
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.
`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
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