diff --git a/src/dev/yarn/yarn_lock_v1.test.ts b/src/dev/yarn/yarn_lock_v1.test.ts index 837b6c7b8b13d..03fe0b3424f8a 100644 --- a/src/dev/yarn/yarn_lock_v1.test.ts +++ b/src/dev/yarn/yarn_lock_v1.test.ts @@ -72,4 +72,25 @@ shared@~1.2.0: }, }); }); + + it('indexes each package alias in a compound yarn.lock header', () => { + const lockfile = ` +"@scope/alias@npm:@scope/alias@2.0.1", "plain-name@1 - 2", "plain-name@npm:@scope/alias@2.0.1": + version "2.0.1" + resolved "https://example.invalid/pkg.tgz" +`; + + const parsed = parseYarnLock(lockfile); + + expect(parsed['plain-name@2.0.1']).toMatchObject({ + name: 'plain-name', + requestedVersions: ['1 - 2', 'npm:@scope/alias@2.0.1'], + resolvedVersion: '2.0.1', + }); + expect(parsed['@scope/alias@2.0.1']).toMatchObject({ + name: '@scope/alias', + requestedVersions: ['npm:@scope/alias@2.0.1'], + resolvedVersion: '2.0.1', + }); + }); }); diff --git a/src/dev/yarn/yarn_lock_v1.ts b/src/dev/yarn/yarn_lock_v1.ts index bbb2d0d52bdcd..0ae6dd43be5d1 100644 --- a/src/dev/yarn/yarn_lock_v1.ts +++ b/src/dev/yarn/yarn_lock_v1.ts @@ -22,6 +22,23 @@ export interface PackageInfo { const makeKey = (name: string, version: string) => `${name}@${version}`; const trimQuotes = (str: string) => str.replace(/(^"|"$)/g, ''); + +/** Splits a yarn.lock header descriptor into [packageName, requestedRangeOrDescriptor]. */ +const splitYarnLockDescriptor = (entry: string): [string, string] => { + if (entry.startsWith('@')) { + const versionSeparator = entry.indexOf('@', 1); + if (versionSeparator === -1) { + throw new Error(`Invalid yarn.lock descriptor: ${entry}`); + } + return [entry.slice(0, versionSeparator), entry.slice(versionSeparator + 1)]; + } + const at = entry.indexOf('@'); + if (at === -1) { + throw new Error(`Invalid yarn.lock descriptor: ${entry}`); + } + return [entry.slice(0, at), entry.slice(at + 1)]; +}; + const splitDependencyLine = (line: string) => { const match = line.trim().match(/^(\S+)\s+(.+)$/); @@ -44,37 +61,40 @@ export const parseYarnLock = (content: string, focus?: string[]): Record { + const [pkgName] = splitYarnLockDescriptor(entry); + return focusSet.has(pkgName); + }); + if (!anyFocused) { + continue; + } } - const requestedVersions = headerEntries.map((entry) => entry.substring(name.length + 1)); - - const packageInfo: PackageInfo = { - name, - requestedVersions, - }; + let resolvedVersion: string | undefined; + let resolvedUrl: string | undefined; + let integrity: string | undefined; + let blockDependencies: { [key: string]: string } | undefined; for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); const [key, value] = line.split(/\s+/, 2).map(trimQuotes); if (key === 'version') { - packageInfo.resolvedVersion = value; + resolvedVersion = value; } else if (key === 'resolved') { - packageInfo.resolvedUrl = value; + resolvedUrl = value; } else if (key === 'integrity') { - packageInfo.integrity = value; + integrity = value; } else if (key === 'dependencies:' || key === 'optionalDependencies:') { let depCount = 0; if (focusSet === null) { - packageInfo.dependencies = packageInfo.dependencies || {}; + blockDependencies = blockDependencies || {}; for (let j = i + 1; j < lines.length; j++) { const depLine = lines[j]; if (!/^\s{4,}\S/.test(depLine)) break; const [depKey, depVersion] = splitDependencyLine(depLine); - packageInfo.dependencies![depKey] = depVersion; + blockDependencies[depKey] = depVersion; depCount++; } } else { @@ -87,18 +107,35 @@ export const parseYarnLock = (content: string, focus?: string[]): Record