diff --git a/.changeset/beige-ravens-speak.md b/.changeset/beige-ravens-speak.md new file mode 100644 index 0000000000..8f72285206 --- /dev/null +++ b/.changeset/beige-ravens-speak.md @@ -0,0 +1,5 @@ +--- +"dependency-path": minor +--- + +Export new function: createPeersFolderSuffix(). diff --git a/.changeset/large-items-work.md b/.changeset/large-items-work.md new file mode 100644 index 0000000000..efa7ca24b9 --- /dev/null +++ b/.changeset/large-items-work.md @@ -0,0 +1,7 @@ +--- +"@pnpm/core": major +"@pnpm/resolve-dependencies": major +"pnpm": major +--- + +Use a base32 hash instead of a hex to encode too long dependency paths. diff --git a/packages/core/test/install/multipleImporters.ts b/packages/core/test/install/multipleImporters.ts index 0b1b7bce93..cb39c16ff3 100644 --- a/packages/core/test/install/multipleImporters.ts +++ b/packages/core/test/install/multipleImporters.ts @@ -1164,7 +1164,7 @@ test('resolve a subdependency from the workspace and use it as a peer', async () '/abc-grand-parent-with-c/1.0.0', '/abc-parent-with-ab/1.0.0', '/abc-parent-with-ab/1.0.0_peer-c@1.0.1', - '/abc/1.0.0_2ff3f699e79762943311edf90e6e1302', + '/abc/1.0.0_f7z7ngphs5rjimyr5x4q43qtai', '/abc/1.0.0_peer-a@peer-a+peer-b@1.0.0', '/dep-of-pkg-with-1-dep/100.0.0', '/is-positive/1.0.0', diff --git a/packages/core/test/install/peerDependencies.ts b/packages/core/test/install/peerDependencies.ts index 8a83296894..89bc44fa3e 100644 --- a/packages/core/test/install/peerDependencies.ts +++ b/packages/core/test/install/peerDependencies.ts @@ -438,14 +438,14 @@ test('peer dependencies are linked when running one named installation', async ( const pkgVariationsDir = path.resolve('node_modules/.pnpm/abc@1.0.0') - const pkgVariation1 = path.join(pkgVariationsDir + '_165e1e08a3f7e7f77ddb572ad0e55660/node_modules') + const pkgVariation1 = path.join(pkgVariationsDir + '_6ea473aweg4rki46lsbci3nehq/node_modules') await okFile(path.join(pkgVariation1, 'abc')) await okFile(path.join(pkgVariation1, 'peer-a')) await okFile(path.join(pkgVariation1, 'peer-b')) await okFile(path.join(pkgVariation1, 'peer-c')) await okFile(path.join(pkgVariation1, 'dep-of-pkg-with-1-dep')) - const pkgVariation2 = path.join(pkgVariationsDir + '_f101cfec1621b915239e5c82246da43c/node_modules') + const pkgVariation2 = path.join(pkgVariationsDir + '_czpb4cfd67t7o7o3k4vnbzkwma/node_modules') await okFile(path.join(pkgVariation2, 'abc')) await okFile(path.join(pkgVariation2, 'peer-a')) await okFile(path.join(pkgVariation2, 'peer-b')) @@ -461,6 +461,7 @@ test('peer dependencies are linked when running one named installation', async ( }) test('peer dependencies are linked when running two separate named installations', async () => { + await addDistTag({ package: 'abc-parent-with-ab', version: '1.0.0', distTag: 'latest' }) await addDistTag({ package: 'peer-a', version: '1.0.0', distTag: 'latest' }) await addDistTag({ package: 'peer-c', version: '1.0.0', distTag: 'latest' }) prepareEmpty() @@ -470,14 +471,14 @@ test('peer dependencies are linked when running two separate named installations const pkgVariationsDir = path.resolve('node_modules/.pnpm/abc@1.0.0') - const pkgVariation1 = path.join(pkgVariationsDir + '_165e1e08a3f7e7f77ddb572ad0e55660/node_modules') + const pkgVariation1 = path.join(pkgVariationsDir + '_6ea473aweg4rki46lsbci3nehq/node_modules') await okFile(path.join(pkgVariation1, 'abc')) await okFile(path.join(pkgVariation1, 'peer-a')) await okFile(path.join(pkgVariation1, 'peer-b')) await okFile(path.join(pkgVariation1, 'peer-c')) await okFile(path.join(pkgVariation1, 'dep-of-pkg-with-1-dep')) - const pkgVariation2 = path.join(pkgVariationsDir + '_165e1e08a3f7e7f77ddb572ad0e55660/node_modules') + const pkgVariation2 = path.join(pkgVariationsDir + '_6ea473aweg4rki46lsbci3nehq/node_modules') await okFile(path.join(pkgVariation2, 'abc')) await okFile(path.join(pkgVariation2, 'peer-a')) await okFile(path.join(pkgVariation2, 'peer-b')) diff --git a/packages/dependency-path/package.json b/packages/dependency-path/package.json index 566488e803..0912945f04 100644 --- a/packages/dependency-path/package.json +++ b/packages/dependency-path/package.json @@ -33,6 +33,7 @@ "dependencies": { "@pnpm/types": "workspace:8.0.0", "encode-registry": "^3.0.0", + "rfc4648": "^1.5.1", "semver": "^7.3.4" }, "devDependencies": { diff --git a/packages/dependency-path/src/index.ts b/packages/dependency-path/src/index.ts index 45569e455c..4221455f67 100644 --- a/packages/dependency-path/src/index.ts +++ b/packages/dependency-path/src/index.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { Registries } from '@pnpm/types' import encodeRegistry from 'encode-registry' +import { base32 } from 'rfc4648' import semver from 'semver' export function isAbsolute (dependencyPath: string) { @@ -132,7 +133,7 @@ export function parse (dependencyPath: string) { export function depPathToFilename (depPath: string) { const filename = depPathToFilenameUnescaped(depPath).replace(/\//g, '+') if (filename.length > 120 || filename !== filename.toLowerCase() && !filename.startsWith('file+')) { - return `${filename.substring(0, 50)}_${crypto.createHash('md5').update(filename).digest('hex')}` + return `${filename.substring(0, 50)}_${createBase32Hash(filename)}` } return filename } @@ -147,3 +148,22 @@ function depPathToFilenameUnescaped (depPath: string) { } return depPath.replace(':', '+') } + +export function createPeersFolderSuffix (peers: Array<{name: string, version: string}>): string { + const folderName = peers.map(({ name, version }) => `${name.replace('/', '+')}@${version}`).sort().join('+') + + // We don't want the folder name to get too long. + // Otherwise, an ENAMETOOLONG error might happen. + // see: https://github.com/pnpm/pnpm/issues/977 + // + // A bigger limit might be fine but the base32 encoded md5 hash will be 26 symbols, + // so for consistency's sake, we go with 26. + if (folderName.length > 26) { + return `_${createBase32Hash(folderName)}` + } + return `_${folderName}` +} + +function createBase32Hash (str: string): string { + return base32.stringify(crypto.createHash('md5').update(str).digest()).replace(/(=+)$/, '').toLowerCase() +} diff --git a/packages/dependency-path/test/index.ts b/packages/dependency-path/test/index.ts index 0e5d4bfac1..17ea365022 100644 --- a/packages/dependency-path/test/index.ts +++ b/packages/dependency-path/test/index.ts @@ -136,8 +136,8 @@ test('depPathToFilename()', () => { expect(filename).toBe('file+test+foo-1.0.0.tgz_foo@2.0.0') expect(filename).not.toContain(':') - expect(depPathToFilename('abcd/'.repeat(200))).toBe('abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+_27524303f1ddd808db67f175ff83606e') - expect(depPathToFilename('/JSONSteam/1.0.0')).toBe('JSONSteam@1.0.0_4b2567ab922fbdf01171f59fab8f6fef') + expect(depPathToFilename('abcd/'.repeat(200))).toBe('abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+abcd+_e5jega7r3xmarw3h6f277a3any') + expect(depPathToFilename('/JSONSteam/1.0.0')).toBe('JSONSteam@1.0.0_jmswpk4sf667aelr6wp2xd3p54') }) test('tryGetPackageId', () => { diff --git a/packages/pnpm/test/install/hooks.ts b/packages/pnpm/test/install/hooks.ts index 98bbc9385c..9da33b170f 100644 --- a/packages/pnpm/test/install/hooks.ts +++ b/packages/pnpm/test/install/hooks.ts @@ -612,5 +612,5 @@ test('readPackage hook is used during removal inside a workspace', async () => { process.chdir('..') const lockfile = await readYamlFile('pnpm-lock.yaml') - expect(lockfile.packages!['/abc/1.0.0_is-negative@1.0.0+peer-a@1.0.0'].peerDependencies!['is-negative']).toBe('1.0.0') + expect(lockfile.packages!['/abc/1.0.0_vt2fli7reel7pfbmpdhs3d7fya'].peerDependencies!['is-negative']).toBe('1.0.0') }) diff --git a/packages/resolve-dependencies/src/resolvePeers.ts b/packages/resolve-dependencies/src/resolvePeers.ts index eca8099ecf..6642130fdc 100644 --- a/packages/resolve-dependencies/src/resolvePeers.ts +++ b/packages/resolve-dependencies/src/resolvePeers.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto' import filenamify from 'filenamify' import path from 'path' import { satisfiesWithPrereleases } from '@yarnpkg/core/lib/semverUtils' @@ -7,7 +6,7 @@ import { PeerDependencyIssues, PeerDependencyIssuesByProjects, } from '@pnpm/types' -import { depPathToFilename } from 'dependency-path' +import { depPathToFilename, createPeersFolderSuffix } from 'dependency-path' import { KeyValuePair } from 'ramda' import fromPairs from 'ramda/src/fromPairs' import isEmpty from 'ramda/src/isEmpty' @@ -511,18 +510,3 @@ function toPkgByName (nodes: Array<{alias: str } return pkgsByName } - -function createPeersFolderSuffix (peers: Array<{name: string, version: string}>) { - const folderName = peers.map(({ name, version }) => `${name.replace('/', '+')}@${version}`).sort().join('+') - - // We don't want the folder name to get too long. - // Otherwise, an ENAMETOOLONG error might happen. - // see: https://github.com/pnpm/pnpm/issues/977 - // - // A bigger limit might be fine but the md5 hash will be 32 symbols, - // so for consistency's sake, we go with 32. - if (folderName.length > 32) { - return `_${crypto.createHash('md5').update(folderName).digest('hex')}` - } - return `_${folderName}` -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3265c28e4c..e0a800334c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -684,10 +684,12 @@ importers: '@types/semver': ^7.3.4 dependency-path: workspace:9.0.0 encode-registry: ^3.0.0 + rfc4648: ^1.5.1 semver: ^7.3.4 dependencies: '@pnpm/types': link:../types encode-registry: 3.0.0 + rfc4648: 1.5.1 semver: 7.3.6 devDependencies: '@types/semver': 7.3.9 @@ -13203,6 +13205,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + /rfc4648/1.5.1: + resolution: {integrity: sha512-60e/YWs2/D3MV1ErdjhJHcmlgnyLUiG4X/14dgsfm9/zmCWLN16xI6YqJYSCd/OANM7bUNzJqPY5B8/02S9Ibw==} + dev: false + /right-pad/1.0.1: resolution: {integrity: sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=} engines: {node: '>= 0.10'}