diff --git a/.changeset/rare-carrots-sleep.md b/.changeset/rare-carrots-sleep.md new file mode 100644 index 0000000000..508c7276a2 --- /dev/null +++ b/.changeset/rare-carrots-sleep.md @@ -0,0 +1,5 @@ +--- +"@pnpm/npm-resolver": patch +--- + +Avoid conflicts in metadata, when a package name has upper case letters. diff --git a/packages/npm-resolver/src/pickPackage.ts b/packages/npm-resolver/src/pickPackage.ts index 04c3e2c9ed..2954cba9af 100644 --- a/packages/npm-resolver/src/pickPackage.ts +++ b/packages/npm-resolver/src/pickPackage.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto' import { promises as fs } from 'fs' import path from 'path' import PnpmError from '@pnpm/error' @@ -76,7 +77,7 @@ export default async ( } const registryName = getRegistryName(opts.registry) - const pkgMirror = path.join(ctx.storeDir, ctx.metaDir, registryName, `${spec.name}.json`) + const pkgMirror = path.join(ctx.storeDir, ctx.metaDir, registryName, `${encodePkgName(spec.name)}.json`) const limit = metafileOperationLimits[pkgMirror] = metafileOperationLimits[pkgMirror] || pLimit(1) let metaCachedInStore: PackageMeta | null | undefined @@ -146,6 +147,13 @@ export default async ( } } +function encodePkgName (pkgName: string) { + if (pkgName !== pkgName.toLowerCase()) { + return `${pkgName}_${crypto.createHash('md5').update(pkgName).digest('hex')}` + } + return pkgName +} + async function loadMeta (pkgMirror: string): Promise { try { return await loadJsonFile(pkgMirror) diff --git a/packages/npm-resolver/test/index.ts b/packages/npm-resolver/test/index.ts index a72d8f353a..994823c171 100644 --- a/packages/npm-resolver/test/index.ts +++ b/packages/npm-resolver/test/index.ts @@ -17,6 +17,7 @@ const isPositiveMetaWithDeprecated = loadJsonFile.sync(path.join(__dirname, const isPositiveMetaFull = loadJsonFile.sync(path.join(__dirname, 'meta', 'is-positive-full.json')) const isPositiveBrokenMeta = loadJsonFile.sync(path.join(__dirname, 'meta', 'is-positive-broken.json')) const sindresorhusIsMeta = loadJsonFile.sync(path.join(__dirname, 'meta', 'sindresorhus-is.json')) +const jsonMeta = loadJsonFile.sync(path.join(__dirname, 'meta', 'JSON.json')) /* eslint-enable @typescript-eslint/no-explicit-any */ const registry = 'https://registry.npmjs.org/' @@ -73,6 +74,30 @@ test('resolveFromNpm()', async () => { expect(meta['dist-tags']).toBeTruthy() }) +test('resolveFromNpm() should save metadata to a unique file when the package name has upper case letters', async () => { + nock(registry) + .get('/JSON') + .reply(200, jsonMeta) + + const storeDir = tempy.directory() + const resolve = createResolveFromNpm({ + storeDir, + }) + const resolveResult = await resolve({ alias: 'JSON', pref: '1.0.0' }, { + registry, + }) + + expect(resolveResult!.resolvedVia).toBe('npm-registry') + expect(resolveResult!.id).toBe('registry.npmjs.org/JSON/1.0.0') + + // The resolve function does not wait for the package meta cache file to be saved + // so we must delay for a bit in order to read it + const meta = await retryLoadJsonFile(path.join(storeDir, 'metadata/registry.npmjs.org/JSON_0ecd11c1d7a287401d148a23bbd7a2f8.json')) // eslint-disable-line @typescript-eslint/no-explicit-any + expect(meta.name).toBeTruthy() + expect(meta.versions).toBeTruthy() + expect(meta['dist-tags']).toBeTruthy() +}) + test('relative workspace protocol is skipped', async () => { const storeDir = tempy.directory() const resolve = createResolveFromNpm({ diff --git a/packages/npm-resolver/test/meta/JSON.json b/packages/npm-resolver/test/meta/JSON.json new file mode 100644 index 0000000000..c8701336e4 --- /dev/null +++ b/packages/npm-resolver/test/meta/JSON.json @@ -0,0 +1,90 @@ +{ + "_id": "JSON", + "_rev": "7-cfe2e7994a4dbbf6a471517f1bc1d264", + "name": "JSON", + "description": "Douglas Crockford's json2.js", + "dist-tags": { + "latest": "1.0.0" + }, + "versions": { + "1.0.0": { + "author": { + "name": "Douglas Crockford", + "email": "douglas@crockford.com", + "url": "http://crockford.com" + }, + "contributors": [ + { + "name": "AJ ONeal", + "email": "coolaj86@gmail.com", + "url": "http://coolaj86.info" + } + ], + "name": "JSON", + "description": "Douglas Crockford's json2.js", + "keywords": [ + "ender" + ], + "version": "1.0.0", + "homepage": "http://json.org", + "repository": { + "url": "git://github.com/douglascrockford/JSON-js.git" + }, + "main": "json2.js", + "engines": { + "node": ">= 0.2.0" + }, + "dependencies": {}, + "devDependencies": {}, + "_npmJsonOpts": { + "file": "/Users/coolaj86/.npm/JSON/1.0.0/package/package.json", + "wscript": false, + "contributors": false, + "serverjs": false + }, + "_id": "JSON@1.0.0", + "_engineSupported": true, + "_npmVersion": "1.0.22", + "_nodeVersion": "v0.4.8", + "_defaultsLoaded": true, + "dist": { + "shasum": "8681531c28f8438a075589ff07248246ea960d8c", + "tarball": "https://registry.npmjs.org/JSON/-/JSON-1.0.0.tgz" + }, + "scripts": {}, + "maintainers": [ + { + "name": "coolaj86", + "email": "coolaj86@gmail.com" + } + ], + "directories": {} + } + }, + "maintainers": [ + { + "name": "coolaj86", + "email": "coolaj86@gmail.com" + } + ], + "time": { + "modified": "2018-03-06T01:41:38.380Z", + "created": "2011-09-08T00:38:16.426Z", + "1.0.0": "2011-09-08T00:38:16.795Z" + }, + "author": { + "name": "Douglas Crockford", + "email": "douglas@crockford.com", + "url": "http://crockford.com" + }, + "repository": { + "url": "git://github.com/douglascrockford/JSON-js.git" + }, + "users": { + "ukuli": true, + "zhnoah": true, + "spencermathews": true, + "d3ck": true + }, + "_attachments": {} +}