UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

291 lines (285 loc) • 10.1 kB
'use strict'; var fs = require('fs-extra'); var semver = require('semver'); var parsers = require('@yarnpkg/parsers'); var lockfile = require('@yarnpkg/lockfile'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver); const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/; const NEW_HEADER = `${[ `# This file is generated by running "yarn install" inside your project. `, `# Manual changes might be lost - proceed with caution! ` ].join(``)} `; const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i; const SPECIAL_OBJECT_KEYS = [ `__metadata`, `version`, `resolution`, `dependencies`, `peerDependencies`, `dependenciesMeta`, `peerDependenciesMeta`, `binaries` ]; class Lockfile { constructor(packages, data, legacy = false) { this.packages = packages; this.data = data; this.legacy = legacy; } static async load(path) { const lockfileContents = await fs__default["default"].readFile(path, "utf8"); return Lockfile.parse(lockfileContents); } static parse(content) { var _a; const legacy = LEGACY_REGEX.test(content); let data; try { data = parsers.parseSyml(content); } catch (err) { throw new Error(`Failed yarn.lock parse, ${err}`); } const packages = /* @__PURE__ */ new Map(); for (const [key, value] of Object.entries(data)) { if (SPECIAL_OBJECT_KEYS.includes(key)) continue; const [, name, ranges] = (_a = ENTRY_PATTERN.exec(key)) != null ? _a : []; if (!name) { throw new Error(`Failed to parse yarn.lock entry '${key}'`); } let queries = packages.get(name); if (!queries) { 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); } queries.push({ range, version: value.version, dataKey: key }); } } return new Lockfile(packages, data, legacy); } /** Get the entries for a single package in the lockfile */ get(name) { return this.packages.get(name); } /** Returns the name of all packages available in the lockfile */ keys() { return this.packages.keys(); } /** Analyzes the lockfile to identify possible actions and warnings for the entries */ analyze(options) { var _a; const { filter, localPackages } = options; const result = { invalidRanges: [], newVersions: [], newRanges: [] }; for (const [name, allEntries] of this.packages) { if (filter && !filter(name)) { continue; } const invalid = allEntries.filter( (e) => !semver__default["default"].validRange(e.range) && !e.range.startsWith("workspace:") ); result.invalidRanges.push( ...invalid.map(({ range }) => ({ name, range })) ); const entries = allEntries.filter((e) => semver__default["default"].validRange(e.range)); if (entries.length < 2) { continue; } const versions = Array.from(new Set(entries.map((e) => e.version))).map((v) => { if (v === "0.0.0-use.local") { const local = localPackages.get(name); if (!local) { throw new Error(`No local package found for ${name}`); } if (!local.packageJson.version) { throw new Error(`No version found for local package ${name}`); } return { entryVersion: v, actualVersion: local.packageJson.version }; } return { entryVersion: v, actualVersion: v }; }).sort((v1, v2) => semver__default["default"].rcompare(v1.actualVersion, v2.actualVersion)); if (versions.length < 2) { continue; } const acceptedVersions = /* @__PURE__ */ new Set(); for (const { version, range } of entries) { const acceptedVersion = versions.find( (v) => semver__default["default"].satisfies(v.actualVersion, range) ); if (!acceptedVersion) { throw new Error( `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}` ); } if (acceptedVersion.entryVersion !== version) { result.newVersions.push({ name, range, newVersion: acceptedVersion.entryVersion, oldVersion: version }); } acceptedVersions.add(acceptedVersion.actualVersion); } if (acceptedVersions.size === 1) { continue; } const maxVersion = Array.from(acceptedVersions).sort(semver__default["default"].rcompare)[0]; const maxEntry = (_a = entries.filter((e) => semver__default["default"].satisfies(maxVersion, e.range)).map((e) => ({ e, min: semver__default["default"].minVersion(e.range) })).filter((p) => p.min).sort((a, b) => semver__default["default"].rcompare(a.min, b.min))[0]) == null ? void 0 : _a.e; if (!maxEntry) { throw new Error( `No entry found that satisfies max version '${maxVersion}'` ); } for (const { version, range } of entries) { if (semver__default["default"].satisfies(maxVersion, range)) { continue; } result.newRanges.push({ name, oldRange: range, newRange: maxEntry.range, oldVersion: version, newVersion: maxVersion }); } } return result; } remove(name, range) { var _a; const query = `${name}@${range}`; const existed = Boolean(this.data[query]); delete this.data[query]; const newEntries = (_a = this.packages.get(name)) == null ? void 0 : _a.filter((e) => e.range !== range); if (newEntries) { this.packages.set(name, newEntries); } return existed; } /** Modifies the lockfile by bumping packages to the suggested versions */ replaceVersions(results) { var _a; for (const { name, range, oldVersion, newVersion } of results) { const query = `${name}@${range}`; const entryData = this.data[query]; if (!entryData) { throw new Error(`No entry data for ${query}`); } if (entryData.version !== oldVersion) { throw new Error( `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}` ); } const matchingEntry = Object.entries(this.data).find( ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion ); if (!matchingEntry) { throw new Error( `No matching entry found for ${name} at version ${newVersion}` ); } this.data[query] = matchingEntry[1]; const entry = (_a = this.packages.get(name)) == null ? void 0 : _a.find((e) => e.range === range); if (!entry) { throw new Error(`No entry data for ${query}`); } if (entry.version !== oldVersion) { throw new Error( `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}` ); } entry.version = newVersion; } } createSimplifiedDependencyGraph() { const graph = /* @__PURE__ */ new Map(); for (const [name, entries] of this.packages) { const dependencies = new Set( entries.flatMap((e) => { var _a, _b; const data = this.data[e.dataKey]; return [ ...Object.keys((_a = data == null ? void 0 : data.dependencies) != null ? _a : {}), ...Object.keys((_b = data == null ? void 0 : data.peerDependencies) != null ? _b : {}) ]; }) ); graph.set(name, dependencies); } return graph; } /** * Diff with another lockfile, returning entries that have been * added, changed, and removed compared to the other lockfile. */ diff(otherLockfile) { var _a; const diff = { added: new Array(), changed: new Array(), removed: new Array() }; const remainingOldNames = new Set(this.packages.keys()); for (const [name, otherQueries] of otherLockfile.packages) { remainingOldNames.delete(name); const thisQueries = this.packages.get(name); if (!thisQueries) { diff.removed.push(...otherQueries.map((q) => ({ name, range: q.range }))); continue; } const remainingOldRanges = new Set(thisQueries.map((q) => q.range)); for (const otherQuery of otherQueries) { remainingOldRanges.delete(otherQuery.range); const thisQuery = thisQueries.find((q) => q.range === otherQuery.range); if (!thisQuery) { diff.removed.push({ name, range: otherQuery.range }); continue; } const otherPkg = otherLockfile.data[otherQuery.dataKey]; const thisPkg = this.data[thisQuery.dataKey]; if (otherPkg && thisPkg) { const thisCheck = thisPkg.integrity || thisPkg.checksum; const otherCheck = otherPkg.integrity || otherPkg.checksum; if (thisCheck !== otherCheck) { diff.changed.push({ name, range: otherQuery.range }); } } } for (const thisRange of remainingOldRanges) { diff.added.push({ name, range: thisRange }); } } for (const name of remainingOldNames) { const queries = (_a = this.packages.get(name)) != null ? _a : []; diff.added.push(...queries.map((q) => ({ name, range: q.range }))); } return diff; } async save(path) { await fs__default["default"].writeFile(path, this.toString(), "utf8"); } toString() { return this.legacy ? lockfile.stringify(this.data) : NEW_HEADER + parsers.stringifySyml(this.data); } } exports.Lockfile = Lockfile; //# sourceMappingURL=Lockfile-e5943b84.cjs.js.map