mirror of https://github.com/nodejs/corepack.git
fix: hash check when downloading Yarn Berry from npm (#439)
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com> Co-authored-by: Maël Nison <nison.mael@gmail.com>
This commit is contained in:
parent
14b8a01abb
commit
467216281e
|
|
@ -149,7 +149,8 @@
|
|||
},
|
||||
"npmRegistry": {
|
||||
"type": "npm",
|
||||
"package": "@yarnpkg/cli-dist"
|
||||
"package": "@yarnpkg/cli-dist",
|
||||
"bin": "bin/yarn.js"
|
||||
},
|
||||
"commands": {
|
||||
"use": [
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import * as httpUtils from './httpUtils
|
|||
import * as nodeUtils from './nodeUtils';
|
||||
import * as npmRegistryUtils from './npmRegistryUtils';
|
||||
import {RegistrySpec, Descriptor, Locator, PackageManagerSpec} from './types';
|
||||
import {BinList, BinSpec, InstallSpec} from './types';
|
||||
import {BinList, BinSpec, InstallSpec, DownloadSpec} from './types';
|
||||
|
||||
export function getRegistryFromPackageManagerSpec(spec: PackageManagerSpec) {
|
||||
return process.env.COREPACK_NPM_REGISTRY
|
||||
|
|
@ -132,6 +132,66 @@ function isValidBinSpec(x: unknown): x is BinSpec {
|
|||
return typeof x === `object` && x !== null && !Array.isArray(x) && Object.keys(x).length > 0;
|
||||
}
|
||||
|
||||
async function download(installTarget: string, url: string, algo: string, binPath: string | null = null): Promise<DownloadSpec> {
|
||||
// Creating a temporary folder inside the install folder means that we
|
||||
// are sure it'll be in the same drive as the destination, so we can
|
||||
// just move it there atomically once we are done
|
||||
|
||||
const tmpFolder = folderUtils.getTemporaryFolder(installTarget);
|
||||
debugUtils.log(`Downloading to ${tmpFolder}`);
|
||||
|
||||
const stream = await httpUtils.fetchUrlStream(url);
|
||||
|
||||
const parsedUrl = new URL(url);
|
||||
const ext = path.posix.extname(parsedUrl.pathname);
|
||||
|
||||
let outputFile: string | null = null;
|
||||
let sendTo: any;
|
||||
|
||||
if (ext === `.tgz`) {
|
||||
const {default: tar} = await import(`tar`);
|
||||
sendTo = tar.x({
|
||||
strip: 1,
|
||||
cwd: tmpFolder,
|
||||
filter: binPath ? path => {
|
||||
const pos = path.indexOf(`/`);
|
||||
return pos !== -1 && path.slice(pos + 1) === binPath;
|
||||
} : undefined,
|
||||
});
|
||||
} else if (ext === `.js`) {
|
||||
outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname));
|
||||
sendTo = fs.createWriteStream(outputFile);
|
||||
}
|
||||
stream.pipe(sendTo);
|
||||
|
||||
let hash = !binPath ? stream.pipe(createHash(algo)) : null;
|
||||
await once(sendTo, `finish`);
|
||||
|
||||
if (binPath) {
|
||||
const downloadedBin = path.join(tmpFolder, binPath);
|
||||
outputFile = path.join(tmpFolder, path.basename(downloadedBin));
|
||||
try {
|
||||
await renameSafe(downloadedBin, outputFile);
|
||||
} catch (err) {
|
||||
if ((err as nodeUtils.NodeError)?.code === `ENOENT`)
|
||||
throw new Error(`Cannot locate '${binPath}' in downloaded tarball`, {cause: err});
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Calculate the hash of the bin file
|
||||
const fileStream = fs.createReadStream(outputFile);
|
||||
hash = fileStream.pipe(createHash(algo));
|
||||
await once(fileStream, `close`);
|
||||
}
|
||||
|
||||
return {
|
||||
tmpFolder,
|
||||
outputFile,
|
||||
hash: hash!.digest(`hex`),
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -159,12 +219,16 @@ export async function installVersion(installTarget: string, locator: Locator, {s
|
|||
}
|
||||
|
||||
let url: string;
|
||||
let binPath: string | null = null;
|
||||
if (locatorIsASupportedPackageManager) {
|
||||
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);
|
||||
if (registry.bin) {
|
||||
binPath = registry.bin;
|
||||
}
|
||||
} else {
|
||||
url = url.replace(
|
||||
npmRegistryUtils.DEFAULT_NPM_REGISTRY_URL,
|
||||
|
|
@ -182,33 +246,9 @@ export async function installVersion(installTarget: string, locator: Locator, {s
|
|||
}
|
||||
}
|
||||
|
||||
// Creating a temporary folder inside the install folder means that we
|
||||
// are sure it'll be in the same drive as the destination, so we can
|
||||
// just move it there atomically once we are done
|
||||
|
||||
const tmpFolder = folderUtils.getTemporaryFolder(installTarget);
|
||||
debugUtils.log(`Installing ${locator.name}@${version} from ${url} to ${tmpFolder}`);
|
||||
const stream = await httpUtils.fetchUrlStream(url);
|
||||
|
||||
const parsedUrl = new URL(url);
|
||||
const ext = path.posix.extname(parsedUrl.pathname);
|
||||
|
||||
let outputFile: string | null = null;
|
||||
|
||||
let sendTo: any;
|
||||
if (ext === `.tgz`) {
|
||||
const {default: tar} = await import(`tar`);
|
||||
sendTo = tar.x({strip: 1, cwd: tmpFolder});
|
||||
} else if (ext === `.js`) {
|
||||
outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname));
|
||||
sendTo = fs.createWriteStream(outputFile);
|
||||
}
|
||||
|
||||
stream.pipe(sendTo);
|
||||
|
||||
debugUtils.log(`Installing ${locator.name}@${version} from ${url}`);
|
||||
const algo = build[0] ?? `sha256`;
|
||||
const hash = stream.pipe(createHash(algo));
|
||||
await once(sendTo, `finish`);
|
||||
const {tmpFolder, outputFile, hash: actualHash} = await download(installTarget, url, algo, binPath);
|
||||
|
||||
let bin: BinSpec | BinList;
|
||||
const isSingleFile = outputFile !== null;
|
||||
|
|
@ -240,7 +280,6 @@ export async function installVersion(installTarget: string, locator: Locator, {s
|
|||
}
|
||||
}
|
||||
|
||||
const actualHash = hash.digest(`hex`);
|
||||
if (build[1] && actualHash !== build[1])
|
||||
throw new Error(`Mismatch hashes. Expected ${build[1]}, got ${actualHash}`);
|
||||
|
||||
|
|
@ -305,6 +344,14 @@ export async function installVersion(installTarget: string, locator: Locator, {s
|
|||
};
|
||||
}
|
||||
|
||||
async function renameSafe(oldPath: fs.PathLike, newPath: fs.PathLike) {
|
||||
if (process.platform === `win32`) {
|
||||
await renameUnderWindows(oldPath, newPath);
|
||||
} else {
|
||||
await fs.promises.rename(oldPath, newPath);
|
||||
}
|
||||
}
|
||||
|
||||
async function renameUnderWindows(oldPath: fs.PathLike, newPath: fs.PathLike) {
|
||||
// Windows malicious file analysis blocks files currently under analysis, so we need to wait for file release
|
||||
const retries = 5;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export function isSupportedPackageManager(value: string): value is SupportedPack
|
|||
export interface NpmRegistrySpec {
|
||||
type: `npm`;
|
||||
package: string;
|
||||
bin?: string;
|
||||
}
|
||||
|
||||
export interface UrlRegistrySpec {
|
||||
|
|
@ -59,6 +60,12 @@ export interface InstallSpec {
|
|||
hash: string;
|
||||
}
|
||||
|
||||
export interface DownloadSpec {
|
||||
tmpFolder: string;
|
||||
outputFile: string | null;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The data structure found in config.json
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -787,19 +787,19 @@ it(`should download yarn berry from custom registry`, async () => {
|
|||
process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT = `1`;
|
||||
|
||||
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
|
||||
packageManager: `yarn@3.0.0`,
|
||||
packageManager: `yarn@3.0.0-rc.2+sha224.f83f6d1cbfac10ba6b516a62ccd2a72ccd857aa6c514d1cd7185ec60`,
|
||||
});
|
||||
|
||||
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
|
||||
exitCode: 0,
|
||||
stdout: `3.0.0\n`,
|
||||
stderr: `! Corepack is about to download https://registry.npmmirror.com/@yarnpkg/cli-dist/-/cli-dist-3.0.0.tgz\n`,
|
||||
stdout: `3.0.0-rc.2\n`,
|
||||
stderr: `! Corepack is about to download https://registry.npmmirror.com/@yarnpkg/cli-dist/-/cli-dist-3.0.0-rc.2.tgz\n`,
|
||||
});
|
||||
|
||||
// Should keep working with cache
|
||||
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
|
||||
exitCode: 0,
|
||||
stdout: `3.0.0\n`,
|
||||
stdout: `3.0.0-rc.2\n`,
|
||||
stderr: ``,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
BIN
tests/nocks.db
BIN
tests/nocks.db
Binary file not shown.
Loading…
Reference in New Issue