From 4edc5e0d4978f73f6b49707439d594fb7e55b1b1 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 4 Sep 2023 00:38:56 -0500 Subject: [PATCH 1/5] Don't require version range --- sources/commands/Base.ts | 2 +- sources/commands/InstallGlobal.ts | 2 +- sources/commands/deprecated/Prepare.ts | 2 +- sources/specUtils.ts | 22 ++++++++++++---------- 4 files changed, 15 insertions(+), 13 deletions(-) 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/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}; } /** From 66e371acadb75b532d118864224ed7d041c3dfcc Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 4 Sep 2023 02:36:33 -0500 Subject: [PATCH 2/5] Add a few more examples --- sources/commands/Use.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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'`, ]], }); From 8099e5acde02d2888e502d6c85352dd9ebd572f7 Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 4 Sep 2023 14:25:50 -0500 Subject: [PATCH 3/5] Update packageManager on corepack install --- sources/commands/InstallLocal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/commands/InstallLocal.ts b/sources/commands/InstallLocal.ts index ddcdb13d6..1e72a0af5 100644 --- a/sources/commands/InstallLocal.ts +++ b/sources/commands/InstallLocal.ts @@ -29,6 +29,8 @@ export class InstallLocalCommand extends BaseCommand { throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`); this.context.stdout.write(`Adding ${resolved.name}@${resolved.reference} to the cache...\n`); - await this.context.engine.ensurePackageManager(resolved); + const packageManagerInfo = await this.context.engine.ensurePackageManager(resolved); + + await this.setLocalPackageManager(packageManagerInfo); } } From 6cf4ea9bbe0cf0d2f2ed04c3b00007ba1b1fe38c Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 8 Oct 2023 20:39:14 -0500 Subject: [PATCH 4/5] update tests --- tests/main.test.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/main.test.ts b/tests/main.test.ts index 3538ff3dd..7e53cdcb6 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -29,16 +29,19 @@ 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 reify a fuzzy 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, - stderr: ``, - stdout: /expected a semver version/, + exitCode: 0, + stdout: /\d+\.\d+\.\d+/, + }); + + await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ + packageManager: `yarn`, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { @@ -46,19 +49,19 @@ it(`should require a version to be specified`, async () => { }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - exitCode: 1, - stderr: ``, - stdout: /expected a semver version/, + exitCode: 0, + stdout: /\d+\.\d+\.\d+/, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { packageManager: `yarn@^1.0.0`, }); - + await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ + packageManager: /yarn@1\.\d+\.\d+/, + }); await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ - exitCode: 1, - stderr: ``, - stdout: /expected a semver version/, + exitCode: 0, + stdout: /1\.\d+\.\d+/, }); }); }); @@ -278,6 +281,10 @@ it(`should allow to call "corepack install" without arguments within a configure stderr: ``, }); + await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ + packageManager: /yarn@1.0.0\+sha256\./, + }); + // Disable the network to make sure we don't succeed by accident process.env.COREPACK_ENABLE_NETWORK = `0`; From 8b56235fdc56602b68e7cbda41bd302926399757 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 8 Oct 2023 22:17:44 -0500 Subject: [PATCH 5/5] Allow loose specifier in packageManager. `corepack use` makes it easy to lock down a specific version. --- README.md | 7 +++++-- sources/commands/InstallLocal.ts | 4 +--- tests/main.test.ts | 23 ++++++++--------------- 3 files changed, 14 insertions(+), 20 deletions(-) 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/InstallLocal.ts b/sources/commands/InstallLocal.ts index 1e72a0af5..ddcdb13d6 100644 --- a/sources/commands/InstallLocal.ts +++ b/sources/commands/InstallLocal.ts @@ -29,8 +29,6 @@ export class InstallLocalCommand extends BaseCommand { throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`); this.context.stdout.write(`Adding ${resolved.name}@${resolved.reference} to the cache...\n`); - const packageManagerInfo = await this.context.engine.ensurePackageManager(resolved); - - await this.setLocalPackageManager(packageManagerInfo); + await this.context.engine.ensurePackageManager(resolved); } } diff --git a/tests/main.test.ts b/tests/main.test.ts index 7e53cdcb6..fa5d2a07e 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -29,7 +29,7 @@ it(`should refuse to download a package manager if the hash doesn't match`, asyn }); }); -it(`should reify a fuzzy version from package.json`, 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`, @@ -37,11 +37,8 @@ it(`should reify a fuzzy version from package.json`, async () => { await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ exitCode: 0, - stdout: /\d+\.\d+\.\d+/, - }); - - await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ - packageManager: `yarn`, + stderr: ``, + stdout: /1\./, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { @@ -50,18 +47,18 @@ it(`should reify a fuzzy version from package.json`, async () => { await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ exitCode: 0, - stdout: /\d+\.\d+\.\d+/, + stderr: ``, + stdout: /1\./, }); await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { packageManager: `yarn@^1.0.0`, }); - await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ - packageManager: /yarn@1\.\d+\.\d+/, - }); + await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({ exitCode: 0, - stdout: /1\.\d+\.\d+/, + stderr: ``, + stdout: /1\./, }); }); }); @@ -281,10 +278,6 @@ it(`should allow to call "corepack install" without arguments within a configure stderr: ``, }); - await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename))).resolves.toMatchObject({ - packageManager: /yarn@1.0.0\+sha256\./, - }); - // Disable the network to make sure we don't succeed by accident process.env.COREPACK_ENABLE_NETWORK = `0`;