From 055b92807f711b5c8c8be6e62b8d3ce83e1ff002 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 19 Aug 2022 23:53:18 +0200 Subject: [PATCH] feat: download the latest version instead of a pinned one (#134) Fixes: https://github.com/nodejs/corepack/issues/93 --- README.md | 15 +++++++++++++-- config.json | 12 ++++++++++++ sources/Engine.ts | 23 ++++++++++++++++------- sources/corepackUtils.ts | 17 +++++++++++++++++ sources/types.ts | 5 +++++ tests/main.test.ts | 1 + 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index dee7f899..36e5d102 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,16 @@ Just use your package managers as you usually would. Run `yarn install` in Yarn ## Known Good Releases -When running Yarn or pnpm within projects that don't list a supported package manager, Corepack will default to a set of Known Good Releases. In a way, you can compare this to Node.js, where each version ships with a specific version of npm. +When running Corepack within projects that don't list a supported package +manager, it will default to a set of Known Good Releases. In a way, you can +compare this to Node.js, where each version ships with a specific version of npm. -The Known Good Releases can be updated system-wide using the `--activate` flag from the `corepack prepare` and `corepack hydrate` commands. +If there is no Known Good Release for the requested package manager, Corepack +looks up the npm registry for the latest available version and cache it for +future use. + +The Known Good Releases can be updated system-wide using the `--activate` flag +from the `corepack prepare` and `corepack hydrate` commands. ## Offline Workflow @@ -106,6 +113,10 @@ This command will retrieve the given package manager from the specified archive - `COREPACK_ENABLE_NETWORK` can be set to `0` to prevent Corepack from accessing the network (in which case you'll be responsible for hydrating the package manager versions that will be required for the projects you'll run, using `corepack hydrate`). +- `COREPACK_DEFAULT_TO_LATEST` can be set to `0` in order to instruct Corepack + not to lookup on the remote registry for the latest version of the selected + package manager. + - `COREPACK_HOME` can be set in order to define where Corepack should install the package managers. By default it is set to `%LOCALAPPDATA%\node\corepack` on Windows, and to `$HOME/.cache/node/corepack` everywhere else. diff --git a/config.json b/config.json index dba155c6..545abf1c 100644 --- a/config.json +++ b/config.json @@ -2,6 +2,10 @@ "definitions": { "npm": { "default": "8.18.0+sha1.bd6ca7f637720441f812370363e2ae67426fb42f", + "fetchLatestFrom": { + "type": "npm", + "package": "npm" + }, "transparent": { "commands": [ [ @@ -29,6 +33,10 @@ }, "pnpm": { "default": "7.9.3+sha1.843f76d13dbfa9f6dfb5135d6fbaa8b99facacc9", + "fetchLatestFrom": { + "type": "npm", + "package": "pnpm" + }, "transparent": { "commands": [ [ @@ -71,6 +79,10 @@ }, "yarn": { "default": "1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447", + "fetchLatestFrom": { + "type": "npm", + "package": "yarn" + }, "transparent": { "default": "3.2.2+sha224.634d0331703700cabfa9d9389835bd8f7426b0207ed6b74d8d34c81e", "commands": [ diff --git a/sources/Engine.ts b/sources/Engine.ts index 9525552d..93dc7faf 100644 --- a/sources/Engine.ts +++ b/sources/Engine.ts @@ -1,6 +1,7 @@ import {UsageError} from 'clipanion'; import fs from 'fs'; import path from 'path'; +import process from 'process'; import semver from 'semver'; import defaultConfig from '../config.json'; @@ -69,17 +70,25 @@ export class Engine { // Ignore errors; too bad } - if (typeof lastKnownGood !== `object` || lastKnownGood === null) - return definition.default; + if (typeof lastKnownGood === `object` && lastKnownGood !== null && + Object.prototype.hasOwnProperty.call(lastKnownGood, packageManager)) { + const override = (lastKnownGood as any)[packageManager]; + if (typeof override === `string`) { + return override; + } + } - if (!Object.prototype.hasOwnProperty.call(lastKnownGood, packageManager)) + if (process.env.COREPACK_DEFAULT_TO_LATEST === `0`) return definition.default; - const override = (lastKnownGood as any)[packageManager]; - if (typeof override !== `string`) - return definition.default; + const reference = await corepackUtils.fetchLatestStableVersion(definition.fetchLatestFrom); + + await this.activatePackageManager({ + name: packageManager, + reference, + }); - return override; + return reference; } async activatePackageManager(locator: Locator) { diff --git a/sources/corepackUtils.ts b/sources/corepackUtils.ts index 93cc7901..b4ae4e9a 100644 --- a/sources/corepackUtils.ts +++ b/sources/corepackUtils.ts @@ -12,6 +12,23 @@ import * as httpUtils from './httpUtils import * as nodeUtils from './nodeUtils'; import {RegistrySpec, Descriptor, Locator, PackageManagerSpec} from './types'; +export async function fetchLatestStableVersion(spec: RegistrySpec) { + switch (spec.type) { + case `npm`: { + const {[`dist-tags`]: {latest}, versions: {[latest]: {dist: {shasum}}}} = + await httpUtils.fetchAsJson(`https://registry.npmjs.org/${spec.package}`); + return `${latest}+sha1.${shasum}`; + } + case `url`: { + const data = await httpUtils.fetchAsJson(spec.url); + return data[spec.fields.tags].stable; + } + default: { + throw new Error(`Unsupported specification ${JSON.stringify(spec)}`); + } + } +} + export async function fetchAvailableTags(spec: RegistrySpec): Promise> { switch (spec.type) { case `npm`: { diff --git a/sources/types.ts b/sources/types.ts index 8f19f294..8ed2cfd1 100644 --- a/sources/types.ts +++ b/sources/types.ts @@ -62,6 +62,11 @@ export interface Config { */ default: string; + /** + * Defines how to fetch the latest version from a remote registry. + */ + fetchLatestFrom: RegistrySpec; + /** * Defines a set of commands that are fine to run even if the user isn't * in a project configured for the specified package manager. For instance, diff --git a/tests/main.test.ts b/tests/main.test.ts index 177dbdd6..2d93a013 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -6,6 +6,7 @@ import {runCli} from './_runCli'; beforeEach(async () => { process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise()); + process.env.COREPACK_DEFAULT_TO_LATEST = `0`; }); it(`should refuse to download a package manager if the hash doesn't match`, async () => {