diff --git a/.changeset/lemon-sheep-rhyme.md b/.changeset/lemon-sheep-rhyme.md new file mode 100644 index 0000000000..38efbbe6c7 --- /dev/null +++ b/.changeset/lemon-sheep-rhyme.md @@ -0,0 +1,5 @@ +--- +"@pnpm/crypto.polyfill": major +--- + +Initial release. diff --git a/.changeset/quiet-carrots-march.md b/.changeset/quiet-carrots-march.md new file mode 100644 index 0000000000..c2747c7b85 --- /dev/null +++ b/.changeset/quiet-carrots-march.md @@ -0,0 +1,10 @@ +--- +"@pnpm/npm-resolver": patch +"@pnpm/core": patch +"@pnpm/crypto.polyfill": patch +"@pnpm/list": patch +"@pnpm/worker": patch +"pnpm": patch +--- + +Use `crypto.hash`, when available, for improved performance [#8629](https://github.com/pnpm/pnpm/pull/8629). diff --git a/crypto/polyfill/README.md b/crypto/polyfill/README.md new file mode 100644 index 0000000000..cf965a800f --- /dev/null +++ b/crypto/polyfill/README.md @@ -0,0 +1,15 @@ +# @pnpm/crypto.polyfill + +> Polyfill for functions in the crypto library + +[![npm version](https://img.shields.io/npm/v/@pnpm/crypto.polyfill.svg)](https://www.npmjs.com/package/@pnpm/crypto.polyfill) + +## Installation + +```sh +pnpm add @pnpm/crypto.polyfill +``` + +## License + +MIT diff --git a/crypto/polyfill/package.json b/crypto/polyfill/package.json new file mode 100644 index 0000000000..5d8bfa7ee2 --- /dev/null +++ b/crypto/polyfill/package.json @@ -0,0 +1,38 @@ +{ + "name": "@pnpm/crypto.polyfill", + "version": "0.0.0", + "description": "Polyfill for functions in the crypto library", + "funding": "https://opencollective.com/pnpm", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "!*.map" + ], + "exports": { + ".": "./lib/index.js" + }, + "engines": { + "node": ">=18.12" + }, + "scripts": { + "lint": "eslint \"src/**/*.ts\"", + "test": "pnpm run compile", + "prepublishOnly": "pnpm run compile", + "compile": "tsc --build && pnpm run lint --fix" + }, + "repository": "https://github.com/pnpm/pnpm/blob/main/crypto/polyfill", + "keywords": [ + "pnpm9", + "pnpm", + "crypto" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://github.com/pnpm/pnpm/blob/main/crypto/polyfill#readme", + "devDependencies": { + "@pnpm/crypto.polyfill": "workspace:*" + } +} diff --git a/crypto/polyfill/src/index.ts b/crypto/polyfill/src/index.ts new file mode 100644 index 0000000000..f30bcc51e6 --- /dev/null +++ b/crypto/polyfill/src/index.ts @@ -0,0 +1,10 @@ +import crypto from 'crypto' + +export const hash = + // @ts-expect-error -- crypto.hash is supported in Node 21.7.0+, 20.12.0+ + crypto.hash ?? + (( + algorithm: string, + data: crypto.BinaryLike, + outputEncoding: crypto.BinaryToTextEncoding + ) => crypto.createHash(algorithm).update(data).digest(outputEncoding)) diff --git a/crypto/polyfill/tsconfig.json b/crypto/polyfill/tsconfig.json new file mode 100644 index 0000000000..c6f0399f60 --- /dev/null +++ b/crypto/polyfill/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@pnpm/tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src" + }, + "include": [ + "src/**/*.ts", + "../../__typings__/**/*.d.ts" + ], + "references": [] +} diff --git a/crypto/polyfill/tsconfig.lint.json b/crypto/polyfill/tsconfig.lint.json new file mode 100644 index 0000000000..1bbe711971 --- /dev/null +++ b/crypto/polyfill/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "../../__typings__/**/*.d.ts" + ] +} diff --git a/packages/crypto.base32-hash/package.json b/packages/crypto.base32-hash/package.json index 35e532a4cc..ab3d7ce9c7 100644 --- a/packages/crypto.base32-hash/package.json +++ b/packages/crypto.base32-hash/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/pnpm/pnpm/blob/main/packages/crypto.base32-hash#readme", "dependencies": { + "@pnpm/crypto.polyfill": "workspace:*", "rfc4648": "catalog:" }, "devDependencies": { diff --git a/packages/crypto.base32-hash/src/index.ts b/packages/crypto.base32-hash/src/index.ts index c3340057a0..9329d4b90c 100644 --- a/packages/crypto.base32-hash/src/index.ts +++ b/packages/crypto.base32-hash/src/index.ts @@ -1,9 +1,9 @@ -import crypto from 'crypto' +import * as crypto from '@pnpm/crypto.polyfill' import fs from 'fs' import { base32 } from 'rfc4648' export function createBase32Hash (str: string): string { - return base32.stringify(crypto.createHash('md5').update(str).digest()).replace(/(=+)$/, '').toLowerCase() + return base32.stringify(crypto.hash('md5', str, 'buffer')).replace(/(=+)$/, '').toLowerCase() } export async function createBase32HashFromFile (file: string): Promise { diff --git a/packages/crypto.base32-hash/tsconfig.json b/packages/crypto.base32-hash/tsconfig.json index af17a88f3e..257b59d3f2 100644 --- a/packages/crypto.base32-hash/tsconfig.json +++ b/packages/crypto.base32-hash/tsconfig.json @@ -11,6 +11,9 @@ "references": [ { "path": "../../__utils__/prepare" + }, + { + "path": "../../crypto/polyfill" } ] } diff --git a/pkg-manager/core/package.json b/pkg-manager/core/package.json index c169458d6f..ba0dfd8ae1 100644 --- a/pkg-manager/core/package.json +++ b/pkg-manager/core/package.json @@ -24,6 +24,7 @@ "@pnpm/constants": "workspace:*", "@pnpm/core-loggers": "workspace:*", "@pnpm/crypto.base32-hash": "workspace:*", + "@pnpm/crypto.polyfill": "workspace:*", "@pnpm/dependency-path": "workspace:*", "@pnpm/deps.graph-sequencer": "workspace:*", "@pnpm/error": "workspace:*", diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index 5583559ff3..435c1ae5a9 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto' import path from 'path' import { buildModules, type DepsStateCache, linkBinsOfDependencies } from '@pnpm/build-modules' import { createAllowBuildFunction } from '@pnpm/builder.policy' @@ -14,6 +13,7 @@ import { stageLogger, summaryLogger, } from '@pnpm/core-loggers' +import * as crypto from '@pnpm/crypto.polyfill' import { calcPatchHashes, createOverridesMapFromParsed, @@ -746,7 +746,7 @@ Note that in CI environments, this setting is enabled by default.`, export function createObjectChecksum (obj: Record): string { const s = JSON.stringify(sortKeys(obj, { deep: true })) - return crypto.createHash('md5').update(s).digest('hex') + return crypto.hash('md5', s, 'hex') } function cacheExpired (prunedAt: string, maxAgeInMinutes: number): boolean { diff --git a/pkg-manager/core/tsconfig.json b/pkg-manager/core/tsconfig.json index bcc0a5c9ec..f42393f0f8 100644 --- a/pkg-manager/core/tsconfig.json +++ b/pkg-manager/core/tsconfig.json @@ -42,6 +42,9 @@ { "path": "../../crypto/object-hasher" }, + { + "path": "../../crypto/polyfill" + }, { "path": "../../deps/graph-sequencer" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 276dc395e8..4db92df7f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1641,6 +1641,12 @@ importers: specifier: 'catalog:' version: 3.0.6 + crypto/polyfill: + devDependencies: + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: 'link:' + dedupe/check: dependencies: '@pnpm/dedupe.types': @@ -3585,6 +3591,9 @@ importers: packages/crypto.base32-hash: dependencies: + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: link:../../crypto/polyfill rfc4648: specifier: 'catalog:' version: 1.5.3 @@ -4084,6 +4093,9 @@ importers: '@pnpm/crypto.base32-hash': specifier: workspace:* version: link:../../packages/crypto.base32-hash + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: link:../../crypto/polyfill '@pnpm/dependency-path': specifier: workspace:* version: link:../../packages/dependency-path @@ -6269,6 +6281,9 @@ importers: '@pnpm/core-loggers': specifier: workspace:* version: link:../../packages/core-loggers + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: link:../../crypto/polyfill '@pnpm/error': specifier: workspace:* version: link:../../packages/error @@ -6520,6 +6535,9 @@ importers: reviewing/list: dependencies: + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: link:../../crypto/polyfill '@pnpm/read-package-json': specifier: workspace:* version: link:../../pkg-manifest/read-package-json @@ -7521,6 +7539,9 @@ importers: '@pnpm/create-cafs-store': specifier: workspace:* version: link:../store/create-cafs-store + '@pnpm/crypto.polyfill': + specifier: workspace:* + version: link:../crypto/polyfill '@pnpm/error': specifier: workspace:* version: link:../packages/error diff --git a/resolving/npm-resolver/package.json b/resolving/npm-resolver/package.json index 41786c3c40..24e957e3e3 100644 --- a/resolving/npm-resolver/package.json +++ b/resolving/npm-resolver/package.json @@ -36,6 +36,7 @@ "dependencies": { "@pnpm/constants": "workspace:*", "@pnpm/core-loggers": "workspace:*", + "@pnpm/crypto.polyfill": "workspace:*", "@pnpm/error": "workspace:*", "@pnpm/fetching-types": "workspace:*", "@pnpm/graceful-fs": "workspace:*", diff --git a/resolving/npm-resolver/src/pickPackage.ts b/resolving/npm-resolver/src/pickPackage.ts index d96995ed61..ffa75c1f6b 100644 --- a/resolving/npm-resolver/src/pickPackage.ts +++ b/resolving/npm-resolver/src/pickPackage.ts @@ -1,6 +1,6 @@ -import crypto from 'crypto' import { promises as fs } from 'fs' import path from 'path' +import * as crypto from '@pnpm/crypto.polyfill' import { PnpmError } from '@pnpm/error' import { logger } from '@pnpm/logger' import gfs from '@pnpm/graceful-fs' @@ -270,7 +270,7 @@ function clearMeta (pkg: PackageMeta): PackageMeta { function encodePkgName (pkgName: string): string { if (pkgName !== pkgName.toLowerCase()) { - return `${pkgName}_${crypto.createHash('md5').update(pkgName).digest('hex')}` + return `${pkgName}_${crypto.hash('md5', pkgName, 'hex')}` } return pkgName } diff --git a/resolving/npm-resolver/tsconfig.json b/resolving/npm-resolver/tsconfig.json index b7c9558e9a..21665b38cd 100644 --- a/resolving/npm-resolver/tsconfig.json +++ b/resolving/npm-resolver/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../__utils__/test-fixtures" }, + { + "path": "../../crypto/polyfill" + }, { "path": "../../fs/graceful-fs" }, diff --git a/reviewing/list/package.json b/reviewing/list/package.json index 0d4cda089b..7da79135f5 100644 --- a/reviewing/list/package.json +++ b/reviewing/list/package.json @@ -34,6 +34,7 @@ }, "homepage": "https://github.com/pnpm/pnpm/blob/main/reviewing/list#readme", "dependencies": { + "@pnpm/crypto.polyfill": "workspace:*", "@pnpm/read-package-json": "workspace:*", "@pnpm/read-project-manifest": "workspace:*", "@pnpm/reviewing.dependencies-hierarchy": "workspace:*", diff --git a/reviewing/list/src/pruneTree.ts b/reviewing/list/src/pruneTree.ts index daae47809c..af07e538e3 100644 --- a/reviewing/list/src/pruneTree.ts +++ b/reviewing/list/src/pruneTree.ts @@ -1,6 +1,6 @@ +import * as crypto from '@pnpm/crypto.polyfill' import { type DependenciesHierarchy, type PackageNode } from '@pnpm/reviewing.dependencies-hierarchy' import { type PackageDependencyHierarchy } from './types' -import { createHash } from 'crypto' export function pruneDependenciesTrees (trees: PackageDependencyHierarchy[] | null, limit: number): PackageDependencyHierarchy[] { if (trees === null) { @@ -57,7 +57,7 @@ export function pruneDependenciesTrees (trees: PackageDependencyHierarchy[] | nu for (const node of path) { pathSoFar += `${node.name}@${node.version},` - const id = createHash('sha256').update(pathSoFar).digest('hex') + const id = crypto.hash('sha256', pathSoFar, 'hex') let existingNode = map.get(id) if (!existingNode) { diff --git a/reviewing/list/tsconfig.json b/reviewing/list/tsconfig.json index b6e4f7662a..afc56b3da7 100644 --- a/reviewing/list/tsconfig.json +++ b/reviewing/list/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../../__utils__/test-fixtures" }, + { + "path": "../../crypto/polyfill" + }, { "path": "../../packages/types" }, diff --git a/worker/package.json b/worker/package.json index 970c4ea1e9..5c58ce719c 100644 --- a/worker/package.json +++ b/worker/package.json @@ -34,6 +34,7 @@ "dependencies": { "@pnpm/cafs-types": "workspace:*", "@pnpm/create-cafs-store": "workspace:*", + "@pnpm/crypto.polyfill": "workspace:*", "@pnpm/error": "workspace:*", "@pnpm/exec.pkg-requires-build": "workspace:*", "@pnpm/fs.hard-link-dir": "workspace:*", diff --git a/worker/src/worker.ts b/worker/src/worker.ts index 698a5b569d..15daef9dc5 100644 --- a/worker/src/worker.ts +++ b/worker/src/worker.ts @@ -1,9 +1,9 @@ import path from 'path' import fs from 'fs' import gfs from '@pnpm/graceful-fs' -import * as crypto from 'crypto' import { type Cafs } from '@pnpm/cafs-types' import { createCafsStore } from '@pnpm/create-cafs-store' +import * as crypto from '@pnpm/crypto.polyfill' import { pkgRequiresBuild } from '@pnpm/exec.pkg-requires-build' import { hardLinkDir } from '@pnpm/fs.hard-link-dir' import { @@ -129,7 +129,7 @@ function addTarballToStore ({ buffer, cafsDir, integrity, filesIndexFile }: Tarb // Compensate for the possibility of non-uniform Base64 padding const normalizedRemoteHash: string = Buffer.from(integrityHash, 'base64').toString('hex') - const calculatedHash: string = crypto.createHash(algo).update(buffer).digest('hex') + const calculatedHash: string = crypto.hash(algo, buffer, 'hex') if (calculatedHash !== normalizedRemoteHash) { return { status: 'error', diff --git a/worker/tsconfig.json b/worker/tsconfig.json index 03da43fc34..e063e84af9 100644 --- a/worker/tsconfig.json +++ b/worker/tsconfig.json @@ -9,6 +9,9 @@ "../../__typings__/**/*.d.ts" ], "references": [ + { + "path": "../crypto/polyfill" + }, { "path": "../exec/pkg-requires-build" },