mirror of https://github.com/nodejs/corepack.git
Adds support for executing arbitrary versions of packages managers (#39)
This commit is contained in:
parent
b77ed59321
commit
a11e796f7f
15
config.json
15
config.json
|
|
@ -15,7 +15,7 @@
|
||||||
"npm": "./bin/npm-cli.js",
|
"npm": "./bin/npm-cli.js",
|
||||||
"npx": "./bin/npx-cli.js"
|
"npx": "./bin/npx-cli.js"
|
||||||
},
|
},
|
||||||
"tags": {
|
"registry": {
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"package": "npm"
|
"package": "npm"
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
"pnpm": "./bin/pnpm.js",
|
"pnpm": "./bin/pnpm.js",
|
||||||
"pnpx": "./bin/pnpx.js"
|
"pnpx": "./bin/pnpx.js"
|
||||||
},
|
},
|
||||||
"tags": {
|
"registry": {
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"package": "pnpm"
|
"package": "pnpm"
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
"pnpm": "./bin/pnpm.cjs",
|
"pnpm": "./bin/pnpm.cjs",
|
||||||
"pnpx": "./bin/pnpx.cjs"
|
"pnpx": "./bin/pnpx.cjs"
|
||||||
},
|
},
|
||||||
"tags": {
|
"registry": {
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"package": "pnpm"
|
"package": "pnpm"
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
"yarn": "./bin/yarn.js",
|
"yarn": "./bin/yarn.js",
|
||||||
"yarnpkg": "./bin/yarn.js"
|
"yarnpkg": "./bin/yarn.js"
|
||||||
},
|
},
|
||||||
"tags": {
|
"registry": {
|
||||||
"type": "npm",
|
"type": "npm",
|
||||||
"package": "yarn"
|
"package": "yarn"
|
||||||
}
|
}
|
||||||
|
|
@ -79,10 +79,13 @@
|
||||||
"name": "yarn",
|
"name": "yarn",
|
||||||
"url": "https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",
|
"url": "https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",
|
||||||
"bin": ["yarn", "yarnpkg"],
|
"bin": ["yarn", "yarnpkg"],
|
||||||
"tags": {
|
"registry": {
|
||||||
"type": "url",
|
"type": "url",
|
||||||
"url": "https://repo.yarnpkg.com/tags",
|
"url": "https://repo.yarnpkg.com/tags",
|
||||||
"field": "tags"
|
"fields": {
|
||||||
|
"tags": "latest",
|
||||||
|
"versions": "tags"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "corepack",
|
"name": "corepack",
|
||||||
"version": "0.6.0",
|
"version": "0.8.0",
|
||||||
"homepage": "https://github.com/nodejs/corepack#readme",
|
"homepage": "https://github.com/nodejs/corepack#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/nodejs/corepack/issues"
|
"url": "https://github.com/nodejs/corepack/issues"
|
||||||
|
|
|
||||||
|
|
@ -105,23 +105,42 @@ export class Engine {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveDescriptor(descriptor: Descriptor, {useCache = true}: {useCache?: boolean} = {}) {
|
async resolveDescriptor(descriptor: Descriptor, {allowTags = false, useCache = true}: {allowTags?: boolean, useCache?: boolean} = {}) {
|
||||||
const definition = this.config.definitions[descriptor.name];
|
const definition = this.config.definitions[descriptor.name];
|
||||||
if (typeof definition === `undefined`)
|
if (typeof definition === `undefined`)
|
||||||
throw new UsageError(`This package manager (${descriptor.name}) isn't supported by this corepack build`);
|
throw new UsageError(`This package manager (${descriptor.name}) isn't supported by this corepack build`);
|
||||||
|
|
||||||
|
let finalDescriptor = descriptor;
|
||||||
|
if (descriptor.range.match(/^[a-z-]+$/)) {
|
||||||
|
if (!allowTags)
|
||||||
|
throw new UsageError(`Packages managers can't be referended via tags in this context`);
|
||||||
|
|
||||||
|
// We only resolve tags from the latest registry entry
|
||||||
|
const ranges = Object.keys(definition.ranges);
|
||||||
|
const tagRange = ranges[ranges.length - 1];
|
||||||
|
|
||||||
|
const tags = await pmmUtils.fetchAvailableTags(definition.ranges[tagRange].registry);
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(tags, descriptor.range))
|
||||||
|
throw new UsageError(`Tag not found (${descriptor.range})`);
|
||||||
|
|
||||||
|
finalDescriptor = {
|
||||||
|
name: descriptor.name,
|
||||||
|
range: tags[descriptor.range],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// If a compatible version is already installed, no need to query one
|
// If a compatible version is already installed, no need to query one
|
||||||
// from the remote listings
|
// from the remote listings
|
||||||
const cachedVersion = await pmmUtils.findInstalledVersion(folderUtils.getInstallFolder(), descriptor);
|
const cachedVersion = await pmmUtils.findInstalledVersion(folderUtils.getInstallFolder(), finalDescriptor);
|
||||||
if (cachedVersion !== null && useCache)
|
if (cachedVersion !== null && useCache)
|
||||||
return {name: descriptor.name, reference: cachedVersion};
|
return {name: finalDescriptor.name, reference: cachedVersion};
|
||||||
|
|
||||||
const candidateRangeDefinitions = Object.keys(definition.ranges).filter(range => {
|
const candidateRangeDefinitions = Object.keys(definition.ranges).filter(range => {
|
||||||
return semverUtils.satisfiesWithPrereleases(descriptor.range, range);
|
return semverUtils.satisfiesWithPrereleases(finalDescriptor.range, range);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagResolutions = await Promise.all(candidateRangeDefinitions.map(async range => {
|
const tagResolutions = await Promise.all(candidateRangeDefinitions.map(async range => {
|
||||||
return [range, await pmmUtils.fetchAvailableVersions(definition.ranges[range].tags)] as const;
|
return [range, await pmmUtils.fetchAvailableVersions(definition.ranges[range].registry)] as const;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// If a version is available under multiple strategies (for example if
|
// If a version is available under multiple strategies (for example if
|
||||||
|
|
@ -133,11 +152,11 @@ export class Engine {
|
||||||
resolutionMap.set(entry, range);
|
resolutionMap.set(entry, range);
|
||||||
|
|
||||||
const candidates = [...resolutionMap.keys()];
|
const candidates = [...resolutionMap.keys()];
|
||||||
const maxSatisfying = semver.maxSatisfying(candidates, descriptor.range);
|
const maxSatisfying = semver.maxSatisfying(candidates, finalDescriptor.range);
|
||||||
if (maxSatisfying === null)
|
if (maxSatisfying === null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return {name: descriptor.name, reference: maxSatisfying};
|
return {name: finalDescriptor.name, reference: maxSatisfying};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLastKnownGoodFile() {
|
private getLastKnownGoodFile() {
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,17 @@ export type Context = BaseContext & CustomContext;
|
||||||
|
|
||||||
export async function main(argv: Array<string>, context: CustomContext & Partial<Context>) {
|
export async function main(argv: Array<string>, context: CustomContext & Partial<Context>) {
|
||||||
const firstArg = argv[0];
|
const firstArg = argv[0];
|
||||||
|
const [, candidatePackageManager, requestedVersion] = firstArg.match(/^([^@]*)(?:@(.*))?$/)!;
|
||||||
|
|
||||||
if (isSupportedPackageManager(firstArg)) {
|
if (isSupportedPackageManager(candidatePackageManager)) {
|
||||||
const packageManager = firstArg;
|
const packageManager = candidatePackageManager;
|
||||||
const binaryName = argv[1];
|
const binaryName = argv[1];
|
||||||
|
|
||||||
// Note: we're playing a bit with Clipanion here, since instead of letting it
|
// Note: we're playing a bit with Clipanion here, since instead of letting it
|
||||||
// decide how to route the commands, we'll instead tweak the init settings
|
// decide how to route the commands, we'll instead tweak the init settings
|
||||||
// based on the arguments.
|
// based on the arguments.
|
||||||
const cli = new Cli<Context>({binaryName});
|
const cli = new Cli<Context>({binaryName});
|
||||||
const defaultVersion = await context.engine.getDefaultVersion(firstArg);
|
const defaultVersion = await context.engine.getDefaultVersion(packageManager);
|
||||||
|
|
||||||
class BinaryCommand extends Command<Context> {
|
class BinaryCommand extends Command<Context> {
|
||||||
proxy = Option.Proxy();
|
proxy = Option.Proxy();
|
||||||
|
|
@ -63,7 +64,10 @@ export async function main(argv: Array<string>, context: CustomContext & Partial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = await context.engine.resolveDescriptor(descriptor);
|
if (requestedVersion)
|
||||||
|
descriptor.range = requestedVersion;
|
||||||
|
|
||||||
|
const resolved = await context.engine.resolveDescriptor(descriptor, {allowTags: true});
|
||||||
if (resolved === null)
|
if (resolved === null)
|
||||||
throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`);
|
throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
import {StdioOptions, spawn} from 'child_process';
|
import {StdioOptions, spawn} from 'child_process';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
|
||||||
import * as debugUtils from './debugUtils';
|
import * as debugUtils from './debugUtils';
|
||||||
import * as folderUtils from './folderUtils';
|
import * as folderUtils from './folderUtils';
|
||||||
import * as fsUtils from './fsUtils';
|
import * as fsUtils from './fsUtils';
|
||||||
import * as httpUtils from './httpUtils';
|
import * as httpUtils from './httpUtils';
|
||||||
import {Context} from './main';
|
import {Context} from './main';
|
||||||
import {TagSpec, Descriptor, Locator, PackageManagerSpec} from './types';
|
import {RegistrySpec, Descriptor, Locator, PackageManagerSpec} from './types';
|
||||||
|
|
||||||
declare const __non_webpack_require__: unknown;
|
declare const __non_webpack_require__: unknown;
|
||||||
|
|
||||||
export async function fetchAvailableVersions(spec: TagSpec) {
|
export async function fetchAvailableTags(spec: RegistrySpec): Promise<Record<string, string>> {
|
||||||
|
switch (spec.type) {
|
||||||
|
case `npm`: {
|
||||||
|
const data = await httpUtils.fetchAsJson(`https://registry.npmjs.org/${spec.package}`, {headers: {[`Accept`]: `application/vnd.npm.install-v1+json`}});
|
||||||
|
return data[`dist-tags`];
|
||||||
|
}
|
||||||
|
case `url`: {
|
||||||
|
const data = await httpUtils.fetchAsJson(spec.url);
|
||||||
|
return data[spec.fields.tags];
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unsupported specification ${JSON.stringify(spec)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchAvailableVersions(spec: RegistrySpec): Promise<Array<string>> {
|
||||||
switch (spec.type) {
|
switch (spec.type) {
|
||||||
case `npm`: {
|
case `npm`: {
|
||||||
const data = await httpUtils.fetchAsJson(`https://registry.npmjs.org/${spec.package}`, {headers: {[`Accept`]: `application/vnd.npm.install-v1+json`}});
|
const data = await httpUtils.fetchAsJson(`https://registry.npmjs.org/${spec.package}`, {headers: {[`Accept`]: `application/vnd.npm.install-v1+json`}});
|
||||||
|
|
@ -20,7 +36,7 @@ export async function fetchAvailableVersions(spec: TagSpec) {
|
||||||
}
|
}
|
||||||
case `url`: {
|
case `url`: {
|
||||||
const data = await httpUtils.fetchAsJson(spec.url);
|
const data = await httpUtils.fetchAsJson(spec.url);
|
||||||
const field = data[spec.field];
|
const field = data[spec.fields.versions];
|
||||||
return Array.isArray(field) ? field : Object.keys(field);
|
return Array.isArray(field) ? field : Object.keys(field);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,23 @@ export function isSupportedPackageManager(value: string): value is SupportedPack
|
||||||
return SupportedPackageManagerSet.has(value as SupportedPackageManagers);
|
return SupportedPackageManagerSet.has(value as SupportedPackageManagers);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NpmTagSpec {
|
export interface NpmRegistrySpec {
|
||||||
type: `npm`;
|
type: `npm`;
|
||||||
package: string;
|
package: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UrlTagSpec {
|
export interface UrlRegistrySpec {
|
||||||
type: `url`;
|
type: `url`;
|
||||||
url: string;
|
url: string;
|
||||||
field: string;
|
fields: {
|
||||||
|
tags: string;
|
||||||
|
versions: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TagSpec =
|
export type RegistrySpec =
|
||||||
| NpmTagSpec
|
| NpmRegistrySpec
|
||||||
| UrlTagSpec;
|
| UrlRegistrySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how the package manager is meant to be downloaded and accessed.
|
* Defines how the package manager is meant to be downloaded and accessed.
|
||||||
|
|
@ -43,7 +46,7 @@ export type TagSpec =
|
||||||
export interface PackageManagerSpec {
|
export interface PackageManagerSpec {
|
||||||
url: string;
|
url: string;
|
||||||
bin: BinSpec | BinList;
|
bin: BinSpec | BinList;
|
||||||
tags: TagSpec;
|
registry: RegistrySpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue