Skip to content
Snippets Groups Projects
Unverified Commit b184d1ea authored by ylemkimon's avatar ylemkimon Committed by GitHub
Browse files

refactor(npm): lockfile extraction code (#7703)

parent 1c2e6498
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@
exports[`manager/npm/extract/npm .getNpmLock() extracts 1`] = `
Object {
"lockedVersions": Object {
"ansi-styles": "3.2.1",
"chalk": "2.4.1",
"color-convert": "1.9.1",
......@@ -9,5 +10,6 @@ Object {
"escape-string-regexp": "1.0.5",
"has-flag": "3.0.0",
"supports-color": "5.4.0",
},
}
`;
......@@ -13,3 +13,9 @@ export type LockFileEntry = Record<
string,
{ version: string; integrity?: boolean }
>;
export interface LockFile {
lockedVersions: Record<string, string>;
lockfileVersion?: number; // cache version for Yarn
isYarn1?: boolean;
}
......@@ -15,7 +15,7 @@ describe('manager/npm/extract/locked-versions', () => {
async (yarnVersion) => {
yarn.getYarnLock.mockReturnValue({
isYarn1: yarnVersion === '1.22.0',
cacheVersion: yarnVersion === '2.2.0' ? 6 : NaN,
lockfileVersion: yarnVersion === '2.2.0' ? 6 : undefined,
lockedVersions: {
'a@1.0.0': '1.0.0',
'b@2.0.0': '2.0.0',
......@@ -46,9 +46,11 @@ describe('manager/npm/extract/locked-versions', () => {
it('uses package-lock.json', async () => {
npm.getNpmLock.mockReturnValue({
lockedVersions: {
a: '1.0.0',
b: '2.0.0',
c: '3.0.0',
},
});
const packageFiles = [
{
......
import { valid } from 'semver';
import { logger } from '../../../logger';
import { PackageFile } from '../../common';
import { LockFile } from './common';
import { getNpmLock } from './npm';
import { getYarnLock } from './yarn';
export async function getLockedVersions(
packageFiles: PackageFile[]
): Promise<void> {
const lockFileCache: Record<string, YarnLockCache> = {};
const lockFileCache: Record<string, LockFile> = {};
logger.debug('Finding locked versions');
for (const packageFile of packageFiles) {
const { yarnLock, npmLock, pnpmShrinkwrap } = packageFile;
......@@ -17,9 +18,9 @@ export async function getLockedVersions(
logger.trace('Retrieving/parsing ' + yarnLock);
lockFileCache[yarnLock] = await getYarnLock(yarnLock);
}
const { cacheVersion, isYarn1 } = lockFileCache[yarnLock];
const { lockfileVersion, isYarn1 } = lockFileCache[yarnLock];
if (!isYarn1) {
if (cacheVersion >= 6) {
if (lockfileVersion >= 6) {
// https://github.com/yarnpkg/berry/commit/f753790380cbda5b55d028ea84b199445129f9ba
packageFile.constraints.yarn = '>= 2.2.0';
} else {
......@@ -36,7 +37,7 @@ export async function getLockedVersions(
logger.debug('Found ' + npmLock + ' for ' + packageFile.packageFile);
if (!lockFileCache[npmLock]) {
logger.trace('Retrieving/parsing ' + npmLock);
lockFileCache[npmLock] = { lockedVersions: await getNpmLock(npmLock) };
lockFileCache[npmLock] = await getNpmLock(npmLock);
}
for (const dep of packageFile.deps) {
dep.lockedVersion = valid(
......@@ -48,9 +49,3 @@ export async function getLockedVersions(
}
}
}
interface YarnLockCache {
lockedVersions: Record<string, string>;
cacheVersion?: number;
isYarn1?: boolean;
}
......@@ -9,7 +9,7 @@ describe('manager/npm/extract/npm', () => {
it('returns empty if failed to parse', async () => {
fs.readLocalFile.mockResolvedValueOnce('abcd');
const res = await getNpmLock('package.json');
expect(Object.keys(res)).toHaveLength(0);
expect(Object.keys(res.lockedVersions)).toHaveLength(0);
});
it('extracts', async () => {
const plocktest1Lock = readFileSync(
......@@ -18,12 +18,12 @@ describe('manager/npm/extract/npm', () => {
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock as never);
const res = await getNpmLock('package.json');
expect(res).toMatchSnapshot();
expect(Object.keys(res)).toHaveLength(7);
expect(Object.keys(res.lockedVersions)).toHaveLength(7);
});
it('returns empty if no deps', async () => {
fs.readLocalFile.mockResolvedValueOnce('{}');
const res = await getNpmLock('package.json');
expect(Object.keys(res)).toHaveLength(0);
expect(Object.keys(res.lockedVersions)).toHaveLength(0);
});
});
});
import { logger } from '../../../logger';
import { readLocalFile } from '../../../util/fs';
import { LockFileEntry } from './common';
import { LockFile, LockFileEntry } from './common';
export async function getNpmLock(
filePath: string
): Promise<Record<string, string>> {
export async function getNpmLock(filePath: string): Promise<LockFile> {
const lockRaw = await readLocalFile(filePath, 'utf8');
try {
const lockParsed = JSON.parse(lockRaw);
const lockFile: Record<string, string> = {};
const lockedVersions: Record<string, string> = {};
for (const [entry, val] of Object.entries(
(lockParsed.dependencies || {}) as LockFileEntry
)) {
logger.trace({ entry, version: val.version });
lockFile[entry] = val.version;
lockedVersions[entry] = val.version;
}
return lockFile;
return { lockedVersions };
} catch (err) {
logger.debug({ filePath, err }, 'Warning: Exception parsing npm lock file');
return {};
return { lockedVersions: {} };
}
}
......@@ -21,7 +21,7 @@ describe('manager/npm/extract/yarn', () => {
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock);
const res = await getYarnLock('package.json');
expect(res.isYarn1).toBe(true);
expect(res.cacheVersion).toBe(NaN);
expect(res.lockfileVersion).toBeUndefined();
expect(res.lockedVersions).toMatchSnapshot();
expect(Object.keys(res.lockedVersions)).toHaveLength(7);
});
......@@ -34,7 +34,7 @@ describe('manager/npm/extract/yarn', () => {
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock);
const res = await getYarnLock('package.json');
expect(res.isYarn1).toBe(false);
expect(res.cacheVersion).toBe(NaN);
expect(res.lockfileVersion).toBe(NaN);
expect(res.lockedVersions).toMatchSnapshot();
expect(Object.keys(res.lockedVersions)).toHaveLength(8);
});
......@@ -47,7 +47,7 @@ describe('manager/npm/extract/yarn', () => {
fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock);
const res = await getYarnLock('package.json');
expect(res.isYarn1).toBe(false);
expect(res.cacheVersion).toBe(6);
expect(res.lockfileVersion).toBe(6);
expect(res.lockedVersions).toMatchSnapshot();
expect(Object.keys(res.lockedVersions)).toHaveLength(10);
});
......
......@@ -2,24 +2,19 @@ import { structUtils } from '@yarnpkg/core';
import { parseSyml } from '@yarnpkg/parsers';
import { logger } from '../../../logger';
import { readLocalFile } from '../../../util/fs';
import { LockFile } from './common';
export async function getYarnLock(
filePath: string
): Promise<{
isYarn1: boolean;
cacheVersion: number;
lockedVersions: Record<string, string>;
}> {
export async function getYarnLock(filePath: string): Promise<LockFile> {
const yarnLockRaw = await readLocalFile(filePath, 'utf8');
try {
const parsed = parseSyml(yarnLockRaw);
const lockFile: Record<string, string> = {};
let cacheVersion = NaN;
const lockedVersions: Record<string, string> = {};
let lockfileVersion: number;
for (const [key, val] of Object.entries(parsed)) {
if (key === '__metadata') {
// yarn 2
cacheVersion = parseInt(val.cacheKey, 10);
lockfileVersion = parseInt(val.cacheKey, 10);
} else {
for (const entry of key.split(', ')) {
const { scope, name, range } = structUtils.parseDescriptor(entry);
......@@ -27,17 +22,17 @@ export async function getYarnLock(
const { selector } = structUtils.parseRange(range);
logger.trace({ entry, version: val.version });
lockFile[packageName + '@' + selector] = parsed[key].version;
lockedVersions[packageName + '@' + selector] = parsed[key].version;
}
}
}
return {
isYarn1: !('__metadata' in parsed),
cacheVersion,
lockedVersions: lockFile,
lockfileVersion,
lockedVersions,
};
} catch (err) {
logger.debug({ filePath, err }, 'Warning: Exception parsing yarn.lock');
return { isYarn1: true, cacheVersion: NaN, lockedVersions: {} };
return { isYarn1: true, lockedVersions: {} };
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment