@backstage/cli
Version:
CLI for developing Backstage plugins and apps
227 lines (221 loc) • 7.74 kB
JavaScript
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;
}
}
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-eced6070.cjs.js.map
;