Skip to content
1 change: 0 additions & 1 deletion sources/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ export class Engine {
const packageManagerInfo = await corepackUtils.installVersion(folderUtils.getInstallFolder(), locator, {
spec,
});
spec.bin ??= packageManagerInfo.bin;

return {
...packageManagerInfo,
Expand Down
103 changes: 69 additions & 34 deletions sources/corepackUtils.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import {createHash} from 'crypto';
import {once} from 'events';
import {FileHandle} from 'fs/promises';
import fs from 'fs';
import type {Dir} from 'fs';
import Module from 'module';
import path from 'path';
import semver from 'semver';

import * as engine from './Engine';
import * as debugUtils from './debugUtils';
import * as folderUtils from './folderUtils';
import * as fsUtils from './fsUtils';
import * as httpUtils from './httpUtils';
import * as nodeUtils from './nodeUtils';
import * as npmRegistryUtils from './npmRegistryUtils';
import {RegistrySpec, Descriptor, Locator, PackageManagerSpec} from './types';
import {createHash} from 'crypto';
import {once} from 'events';
import {FileHandle} from 'fs/promises';
import fs from 'fs';
import type {Dir} from 'fs';
import Module from 'module';
import path from 'path';
import semver from 'semver';

import * as engine from './Engine';
import * as debugUtils from './debugUtils';
import * as folderUtils from './folderUtils';
import * as fsUtils from './fsUtils';
import * as httpUtils from './httpUtils';
import * as nodeUtils from './nodeUtils';
import * as npmRegistryUtils from './npmRegistryUtils';
import {RegistrySpec, Descriptor, Locator, PackageManagerSpec, BinList, BinSpec, InstallSpec} from './types';
Comment thread
zhyupe marked this conversation as resolved.
Outdated

export function getRegistryFromPackageManagerSpec(spec: PackageManagerSpec) {
return process.env.COREPACK_NPM_REGISTRY
Expand Down Expand Up @@ -123,7 +123,15 @@ function parseURLReference(locator: Locator) {
return {version: encodeURIComponent(href), build: []};
}

export async function installVersion(installTarget: string, locator: Locator, {spec}: {spec: PackageManagerSpec}) {
function isValidBinList(x: unknown): x is BinList {
return Array.isArray(x) && x.length > 0;
}

function isValidBinSpec(x: unknown): x is BinSpec {
return typeof x === `object` && x !== null && !Array.isArray(x) && Object.keys(x).length > 0;
}

export async function installVersion(installTarget: string, locator: Locator, {spec}: {spec: PackageManagerSpec}): Promise<InstallSpec> {
const locatorIsASupportedPackageManager = isSupportedPackageManagerLocator(locator);
const locatorReference = locatorIsASupportedPackageManager ? semver.parse(locator.reference)! : parseURLReference(locator);
const {version, build} = locatorReference;
Expand Down Expand Up @@ -151,13 +159,18 @@ export async function installVersion(installTarget: string, locator: Locator, {s

let url: string;
if (locatorIsASupportedPackageManager) {
const defaultNpmRegistryURL = spec.url.replace(`{}`, version);
url = process.env.COREPACK_NPM_REGISTRY ?
defaultNpmRegistryURL.replace(
npmRegistryUtils.DEFAULT_NPM_REGISTRY_URL,
() => process.env.COREPACK_NPM_REGISTRY!,
) :
defaultNpmRegistryURL;
url = spec.url.replace(`{}`, version);
if (process.env.COREPACK_NPM_REGISTRY) {
const registry = getRegistryFromPackageManagerSpec(spec);
if (registry.type === `npm`) {
url = await npmRegistryUtils.fetchTarballUrl(registry.package, version);
} else {
url = url.replace(
npmRegistryUtils.DEFAULT_NPM_REGISTRY_URL,
() => process.env.COREPACK_NPM_REGISTRY!,
);
}
}
} else {
url = decodeURIComponent(version);
}
Expand Down Expand Up @@ -190,13 +203,34 @@ export async function installVersion(installTarget: string, locator: Locator, {s
const hash = stream.pipe(createHash(algo));
await once(sendTo, `finish`);

let bin;
if (!locatorIsASupportedPackageManager) {
if (ext === `.tgz`) {
bin = require(path.join(tmpFolder, `package.json`)).bin;
} else if (ext === `.js`) {
let bin: BinSpec | BinList;
const isSingleFile = outputFile !== null;

// In config, yarn berry is expected to be downloaded as a single file,
// and therefore `spec.bin` is an array. However, when dowloaded from
// custom npm registry as tarball, `bin` should be a map.
// In this case, we ignore the configured `spec.bin`.

if (isSingleFile) {
if (locatorIsASupportedPackageManager && isValidBinList(spec.bin)) {
bin = spec.bin;
} else {
bin = [locator.name];
}
} else {
if (locatorIsASupportedPackageManager && isValidBinSpec(spec.bin)) {
bin = spec.bin;
} else {
const {name: packageName, bin: packageBin} = require(path.join(tmpFolder, `package.json`));
if (typeof packageBin === `string`) {
// When `bin` is a string, the name of the executable is the name of the package.
bin = {[packageName]: packageBin};
} else if (isValidBinSpec(packageBin)) {
bin = packageBin;
} else {
throw new Error(`Unable to locate bin in package.json`);
}
}
}

const actualHash = hash.digest(`hex`);
Expand Down Expand Up @@ -263,18 +297,19 @@ export async function installVersion(installTarget: string, locator: Locator, {s
/**
* Loads the binary, taking control of the current process.
*/
export async function runVersion(locator: Locator, installSpec: { location: string, spec: PackageManagerSpec }, binName: string, args: Array<string>): Promise<void> {
export async function runVersion(locator: Locator, installSpec: InstallSpec & {spec: PackageManagerSpec}, binName: string, args: Array<string>): Promise<void> {
let binPath: string | null = null;
if (Array.isArray(installSpec.spec.bin)) {
if (installSpec.spec.bin.some(bin => bin === binName)) {
const bin = installSpec.bin ?? installSpec.spec.bin;
if (Array.isArray(bin)) {
if (bin.some(name => name === binName)) {
const parsedUrl = new URL(installSpec.spec.url);
const ext = path.posix.extname(parsedUrl.pathname);
if (ext === `.js`) {
binPath = path.join(installSpec.location, path.posix.basename(parsedUrl.pathname));
}
}
} else {
for (const [name, dest] of Object.entries(installSpec.spec.bin)) {
for (const [name, dest] of Object.entries(bin)) {
if (name === binName) {
binPath = path.join(installSpec.location, dest);
break;
Expand Down
13 changes: 13 additions & 0 deletions sources/npmRegistryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ export async function fetchAvailableVersions(packageName: string) {
const metadata = await fetchAsJson(packageName);
return Object.keys(metadata.versions);
}

export async function fetchTarballUrl(packageName: string, version: string) {
const metadata = await fetchAsJson(packageName);
const versionMetadata = metadata.versions?.[version];
if (versionMetadata === undefined)
throw new Error(`${packageName}@${version} does not exist.`);

const {tarball} = versionMetadata.dist;
if (tarball === undefined || !tarball.startsWith(`http`))
throw new Error(`${packageName}@${version} does not have a valid tarball.`);

return tarball;
}
6 changes: 6 additions & 0 deletions sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export interface PackageManagerSpec {
};
}

export interface InstallSpec {
location: string;
bin?: BinList | BinSpec;
hash: string;
}

/**
* The data structure found in config.json
*/
Expand Down