Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/dev/yarn/yarn_lock_v1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
});
});
85 changes: 61 additions & 24 deletions src/dev/yarn/yarn_lock_v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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+(.+)$/);

Expand All @@ -44,37 +61,40 @@ export const parseYarnLock = (content: string, focus?: string[]): Record<string,

const header = lines[0].replace(/:$/, '').trim();
const headerEntries = header.split(', ').map(trimQuotes);
const name = headerEntries[0].split(/(?!^)@/)[0];

if (focusSet !== null && !focusSet.has(name)) {
continue;
if (focusSet !== null) {
const anyFocused = headerEntries.some((entry) => {
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 {
Expand All @@ -87,18 +107,35 @@ export const parseYarnLock = (content: string, focus?: string[]): Record<string,
}
}

if (!packageInfo.resolvedVersion) {
console.warn(`No resolved version found for package ${name}. Skipping.`);
if (!resolvedVersion) {
console.warn(
`No resolved version found for package block starting with ${headerEntries[0]}. Skipping.`
);
continue;
}
const entryKey = makeKey(name, packageInfo.resolvedVersion!);
if (!packages[entryKey]) {
packages[entryKey] = packageInfo;
} else {
const existing = packages[entryKey];
existing.requestedVersions = Array.from(
new Set([...existing.requestedVersions, ...packageInfo.requestedVersions])
).sort();

for (const headerEntry of headerEntries) {
const [pkgName, requestedVersion] = splitYarnLockDescriptor(headerEntry);
if (focusSet !== null && !focusSet.has(pkgName)) {
continue;
}

const entryKey = makeKey(pkgName, resolvedVersion);
if (!packages[entryKey]) {
packages[entryKey] = {
name: pkgName,
requestedVersions: [requestedVersion],
resolvedVersion,
resolvedUrl,
integrity,
dependencies: blockDependencies ? { ...blockDependencies } : undefined,
};
} else {
const existing = packages[entryKey];
existing.requestedVersions = Array.from(
new Set([...existing.requestedVersions, requestedVersion])
).sort();
}
}
}
return packages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,20 @@
@elastic/eui@113.3.0
@elastic/numeral@2.5.1
@elastic/prismjs-esql@1.1.2
@emotion/babel-plugin@11.11.0
@emotion/cache@11.11.0
@emotion/hash@0.9.1
@emotion/babel-plugin@11.13.5
@emotion/cache@11.14.0
@emotion/hash@0.9.2
@emotion/is-prop-valid@1.4.0
@emotion/memoize@0.8.1
@emotion/memoize@0.9.0
@emotion/react@11.11.1
@emotion/serialize@1.1.2
@emotion/react@11.14.0
@emotion/serialize@1.3.3
@emotion/sheet@1.4.1
@emotion/stylis@0.8.5
@emotion/unitless@0.10.0
@emotion/unitless@0.7.5
@emotion/unitless@0.8.1
@emotion/use-insertion-effect-with-fallbacks@1.0.1
@emotion/utils@1.2.1
@emotion/weak-memoize@0.3.1
@emotion/use-insertion-effect-with-fallbacks@1.2.0
@emotion/utils@1.4.2
@emotion/weak-memoize@0.4.0
@hello-pangea/dnd@18.0.1
@jridgewell/gen-mapping@0.3.5
@jridgewell/resolve-uri@3.1.2
Expand Down
Loading