feat: updated to new crossversion lockfile parser to facilitate upgrading to yarn 3

Signed-off-by: Mark David Avery <mark@webark.cc>
This commit is contained in:
Mark David Avery
2022-05-07 17:30:09 -07:00
parent d06fbf7bb0
commit 4f73352608
6 changed files with 152 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Updated Lockfile to support new versions of yarn as well as the legacy 1 version
+1
View File
@@ -58,6 +58,7 @@
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^3.0.0-rc.4",
"bfj": "^7.0.2",
"buffer": "^6.0.3",
"chalk": "^4.0.0",
+6 -1
View File
@@ -25,9 +25,14 @@ describe('createPackageVersionProvider', () => {
mockFs.restore();
});
const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
`;
it('should provide package versions', async () => {
mockFs({
'yarn.lock': `
'yarn.lock': `${HEADER}
"a@^0.1.0":
version "0.1.5"
@@ -153,3 +153,92 @@ describe('Lockfile', () => {
expect(lockfile.toString()).toBe(mockBDedup);
});
});
const newHeader = `# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 6
cacheKey: 8
`;
const mockANew = `${newHeader}
a@^1:
version: 1.0.1
dependencies:
b: ^2
integrity: sha512-xyz
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
b@2.0.x:
version: 2.0.1
b@^2:
version: 2.0.0
`;
const mockANewDedup = `${newHeader}
a@^1:
version: 1.0.1
dependencies:
b: ^2
integrity: sha512-xyz
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
b@2.0.x:
version: 2.0.1
b@^2:
version: 2.0.1
`;
describe('New Lockfile', () => {
afterEach(() => {
mockFs.restore();
});
it('should load and serialize mockANew', async () => {
mockFs({
'/yarn.lock': mockANew,
});
const lockfile = await Lockfile.load('/yarn.lock');
expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]);
expect(lockfile.get('b')).toEqual([
{ range: '2.0.x', version: '2.0.1' },
{ range: '^2', version: '2.0.0' },
]);
expect(lockfile.toString()).toBe(mockANew);
});
it('should deduplicate and save mockANew', async () => {
mockFs({
'/yarn.lock': mockANew,
});
const lockfile = await Lockfile.load('/yarn.lock');
const result = lockfile.analyze();
expect(result).toEqual({
invalidRanges: [],
newRanges: [],
newVersions: [
{
name: 'b',
range: '^2',
oldVersion: '2.0.0',
newVersion: '2.0.1',
},
],
});
expect(lockfile.toString()).toBe(mockANew);
lockfile.replaceVersions(result.newVersions);
expect(lockfile.toString()).toBe(mockANewDedup);
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockANew);
await expect(lockfile.save()).resolves.toBeUndefined();
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(
mockANewDedup,
);
});
});
+38 -8
View File
@@ -16,10 +16,8 @@
import fs from 'fs-extra';
import semver from 'semver';
import {
parse as parseLockfile,
stringify as stringifyLockfile,
} from '@yarnpkg/lockfile';
import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
@@ -66,9 +64,40 @@ type AnalyzeResult = {
newRanges: AnalyzeResultNewRange[];
};
function parseLockfile(lockfileContents: string) {
try {
return {
object: parseSyml(lockfileContents),
type: 'success',
};
} catch (err) {
return {
object: null,
type: err,
};
}
}
// the new yarn header is handled out of band of the parsing
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
const NEW_HEADER = `${[
`# This file is generated by running "yarn install" inside your project.\n`,
`# Manual changes might be lost - proceed with caution!\n`,
].join(``)}\n`;
function stringifyLockfile(data: LockfileData, legacy: boolean) {
return legacy
? legacyStringifyLockfile(data)
: NEW_HEADER + stringifySyml(data);
}
// taken from yarn parser package
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136
const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
export class Lockfile {
static async load(path: string) {
const lockfileContents = await fs.readFile(path, 'utf8');
const legacy = LEGACY_REGEX.test(lockfileContents);
const lockfile = parseLockfile(lockfileContents);
if (lockfile.type !== 'success') {
throw new Error(`Failed yarn.lock parse with ${lockfile.type}`);
@@ -79,7 +108,7 @@ export class Lockfile {
for (const [key, value] of Object.entries(data)) {
const [, name, range] = ENTRY_PATTERN.exec(key) ?? [];
if (!name) {
if (!name && key !== '__metadata') {
throw new Error(`Failed to parse yarn.lock entry '${key}'`);
}
@@ -91,13 +120,14 @@ export class Lockfile {
queries.push({ range, version: value.version });
}
return new Lockfile(path, packages, data);
return new Lockfile(path, packages, data, legacy);
}
private constructor(
private readonly path: string,
private readonly packages: Map<string, LockfileQueryEntry[]>,
private readonly data: LockfileData,
private readonly legacy: boolean = false,
) {}
/** Get the entries for a single package in the lockfile */
@@ -120,7 +150,7 @@ export class Lockfile {
};
for (const [name, allEntries] of this.packages) {
if (filter && !filter(name)) {
if (typeof name !== 'string' || (filter && !filter(name))) {
continue;
}
@@ -267,6 +297,6 @@ export class Lockfile {
}
toString() {
return stringifyLockfile(this.data);
return stringifyLockfile(this.data, this.legacy);
}
}
+13 -5
View File
@@ -6524,9 +6524,9 @@
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/react-dom@*", "@types/react-dom@<18.0.0", "@types/react-dom@^17":
version "17.0.15"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.15.tgz#f2c8efde11521a4b7991e076cb9c70ba3bb0d156"
integrity sha512-Tr9VU9DvNoHDWlmecmcsE5ZZiUkYx+nKBzum4Oxe1K0yJVyBlfbq7H3eXjxXqJczBKqPGq3EgfTru4MgKb9+Yw==
version "17.0.16"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.16.tgz#7caba93cf2806c51e64d620d8dff4bae57e06cc4"
integrity sha512-DWcXf8EbMrO/gWnQU7Z88Ws/p16qxGpPyjTKTpmBSFKeE+HveVubqGO1CVK7FrwlWD5MuOcvh8gtd0/XO38NdQ==
dependencies:
"@types/react" "^17"
@@ -7262,6 +7262,14 @@
resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
"@yarnpkg/parsers@^3.0.0-rc.4":
version "3.0.0-rc.4"
resolved "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.4.tgz#d7b19fa22ce7ff2423e5cbf008f7be0a85e9f1e3"
integrity sha512-ScXXCUwGdx+aEIP20U8VEGtuXmxcMPFdJTb9G9a8MRpQPxIkky/GYXEL5Hf4oqJJXGyCv/DN31zfmxyj31SLKw==
dependencies:
js-yaml "^3.10.0"
tslib "^1.13.0"
JSONStream@^1.0.4:
version "1.3.5"
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
@@ -15818,7 +15826,7 @@ js-yaml@4.1.0, js-yaml@=4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.6.1, js-yaml@^3.8.3:
js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.6.1, js-yaml@^3.8.3:
version "3.14.1"
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
@@ -24318,7 +24326,7 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==