fix: Fix versions-check cli command

Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
Gustaf Räntilä
2024-04-22 14:16:52 +02:00
parent 533dd309ef
commit 009da479d5
6 changed files with 82 additions and 23 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fix `versions:check --fix` when `yarn.lock` has multiple joint versions in the same section
+1 -1
View File
@@ -35,7 +35,7 @@
"@backstage/errors": "workspace:^",
"@backstage/types": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
"@yarnpkg/parsers": "^3.0.0-rc.4",
"@yarnpkg/parsers": "^3.0.0",
"fs-extra": "^11.2.0",
"semver": "^7.5.3",
"zod": "^3.22.4"
+1 -1
View File
@@ -79,7 +79,7 @@
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.7.2",
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^3.0.0-rc.4",
"@yarnpkg/parsers": "^3.0.0",
"bfj": "^8.0.0",
"buffer": "^6.0.3",
"chalk": "^4.0.0",
+67 -13
View File
@@ -98,6 +98,24 @@ export class Lockfile {
return Lockfile.parse(lockfileContents);
}
static #getRangesFromDataKey(key: string): string[] {
const [, name, ranges] = key.match(ENTRY_PATTERN) ?? [];
if (!name) {
throw new Error(`Failed to parse yarn.lock entry '${key}'`);
}
return ranges.split(/\s*,\s*/).map(rangePart => {
let range = rangePart;
if (range.startsWith(`${name}@`)) {
range = range.slice(`${name}@`.length);
}
if (range.startsWith('npm:')) {
range = range.slice('npm:'.length);
}
return range;
});
}
static parse(content: string) {
const legacy = LEGACY_REGEX.test(content);
@@ -113,7 +131,7 @@ export class Lockfile {
for (const [key, value] of Object.entries(data)) {
if (SPECIAL_OBJECT_KEYS.includes(key)) continue;
const [, name, ranges] = ENTRY_PATTERN.exec(key) ?? [];
const [, name] = ENTRY_PATTERN.exec(key) ?? [];
if (!name) {
throw new Error(`Failed to parse yarn.lock entry '${key}'`);
}
@@ -123,13 +141,8 @@ export class Lockfile {
queries = [];
packages.set(name, queries);
}
for (let range of ranges.split(/\s*,\s*/)) {
if (range.startsWith(`${name}@`)) {
range = range.slice(`${name}@`.length);
}
if (range.startsWith('npm:')) {
range = range.slice('npm:'.length);
}
const ranges = Lockfile.#getRangesFromDataKey(key);
for (const range of ranges) {
queries.push({ range, version: value.version, dataKey: key });
}
}
@@ -276,9 +289,34 @@ export class Lockfile {
}
remove(name: string, range: string): boolean {
const query = `${name}@${range}`;
const simpleQuery = `${name}@${range}`;
const query = this.getEntryOf(name, range);
const existed = Boolean(this.data[query]);
delete this.data[query];
if (simpleQuery === query) {
// Single-versioned entry, just delete
delete this.data[query];
} else {
// Remove this version from the entry key. This modifies the key, so the
// package queries' <dataKey> needs to be updated too.
const newRanges = Lockfile.#getRangesFromDataKey(query).filter(
q => q !== simpleQuery,
);
const newQuery = newRanges.join(', ');
// Replace the entry with a new one without this particular range
const entry = this.data[query];
delete this.data[query];
this.data[newQuery] = entry;
// Fix all package queries pointing to the old query
this.packages.get(name)?.forEach(q => {
if (q.dataKey === query) {
q.dataKey = newQuery;
}
});
}
const newEntries = this.packages.get(name)?.filter(e => e.range !== range);
if (newEntries) {
@@ -288,19 +326,35 @@ export class Lockfile {
return existed;
}
getEntryOf(name: string, range: string) {
const query = this.packages.get(name)?.find(q => q.range === range);
if (!query) {
throw new Error(`No entry data for ${name}@${range}`);
}
return query.dataKey;
}
/** Modifies the lockfile by bumping packages to the suggested versions */
replaceVersions(results: AnalyzeResultNewVersion[]) {
// When replacing versions, we might replace the same version multiple times,
// as a query may contain multiple versions. This keeps the original version,
// to ensure we don't make mistakes.
const oldVersions = Object.fromEntries(
Object.entries(this.data).map(([key, val]) => [key, val.version]),
);
for (const { name, range, oldVersion, newVersion } of results) {
const query = `${name}@${range}`;
const query = this.getEntryOf(name, range);
// Update the backing data
const entryData = this.data[query];
if (!entryData) {
throw new Error(`No entry data for ${query}`);
}
if (entryData.version !== oldVersion) {
if (oldVersions[query] !== oldVersion) {
throw new Error(
`Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
`Expected existing version data for ${query} to be ${oldVersion}, was ${oldVersions[query]}`,
);
}
+1 -1
View File
@@ -41,7 +41,7 @@
"@manypkg/get-packages": "^1.1.3",
"@types/express": "*",
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^3.0.0-rc.4",
"@yarnpkg/parsers": "^3.0.0",
"express": "^4.18.1",
"express-promise-router": "^4.1.0",
"fs-extra": "^11.0.0",
+7 -7
View File
@@ -3642,7 +3642,7 @@ __metadata:
"@backstage/errors": "workspace:^"
"@backstage/types": "workspace:^"
"@manypkg/get-packages": ^1.1.3
"@yarnpkg/parsers": ^3.0.0-rc.4
"@yarnpkg/parsers": ^3.0.0
fs-extra: ^11.2.0
semver: ^7.5.3
zod: ^3.22.4
@@ -3716,7 +3716,7 @@ __metadata:
"@typescript-eslint/parser": ^6.7.2
"@vitejs/plugin-react": ^4.0.4
"@yarnpkg/lockfile": ^1.1.0
"@yarnpkg/parsers": ^3.0.0-rc.4
"@yarnpkg/parsers": ^3.0.0
bfj: ^8.0.0
buffer: ^6.0.3
chalk: ^4.0.0
@@ -5777,7 +5777,7 @@ __metadata:
"@types/supertest": ^2.0.8
"@types/yarnpkg__lockfile": ^1.1.4
"@yarnpkg/lockfile": ^1.1.0
"@yarnpkg/parsers": ^3.0.0-rc.4
"@yarnpkg/parsers": ^3.0.0
express: ^4.18.1
express-promise-router: ^4.1.0
fs-extra: ^11.0.0
@@ -19325,13 +19325,13 @@ __metadata:
languageName: node
linkType: hard
"@yarnpkg/parsers@npm:^3.0.0-rc.4":
version: 3.0.0-rc.21
resolution: "@yarnpkg/parsers@npm:3.0.0-rc.21"
"@yarnpkg/parsers@npm:^3.0.0":
version: 3.0.0
resolution: "@yarnpkg/parsers@npm:3.0.0"
dependencies:
js-yaml: ^3.10.0
tslib: ^2.4.0
checksum: c0741ef01089a7d452dfb75b8c24a82ddcc3e218dac22b794a06f2e50c7070378c460f63f63fb7ed0f62f634114e448e3d85a75ccd4db84e1526242ae9b4a7d5
checksum: fefe5ecafb5bfa2b678ac9ba9259810fdda40142afd9d0b7e0e5cc1cce1fd824dffc52217c5e429807481d8fd18ead074bd317e64fd626335d3c9f1a320bade2
languageName: node
linkType: hard