@backstage/cli
Version:
CLI for developing Backstage plugins and apps
413 lines (407 loc) • 14.3 kB
JavaScript
var fs = require('fs-extra');
var chalk = require('chalk');
var ora = require('ora');
var semver = require('semver');
var minimatch = require('minimatch');
var errors = require('@backstage/errors');
var path = require('path');
var run = require('./run-a0658306.cjs.js');
var index = require('./index-ce56dce5.cjs.js');
var Lockfile = require('./Lockfile-eced6070.cjs.js');
var packages = require('./packages-30cb9ed9.cjs.js');
var lint = require('./lint-ff1e8d45.cjs.js');
var cliCommon = require('@backstage/cli-common');
var parallel = require('./parallel-a8f6219c.cjs.js');
var releaseManifests = require('@backstage/release-manifests');
require('global-agent/bootstrap');
var cliNode = require('@backstage/cli-node');
require('child_process');
require('util');
require('commander');
require('@yarnpkg/parsers');
require('@yarnpkg/lockfile');
require('@manypkg/get-packages');
require('./yarn-6cd89e16.cjs.js');
require('lodash/partition');
require('os');
require('worker_threads');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var ora__default = /*#__PURE__*/_interopDefaultLegacy(ora);
var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
var minimatch__default = /*#__PURE__*/_interopDefaultLegacy(minimatch);
const DEP_TYPES = [
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies"
];
const DEFAULT_PATTERN_GLOB = "@backstage/*";
var bump = async (opts) => {
var _a;
const lockfilePath = index.paths.resolveTargetRoot("yarn.lock");
const lockfile = await Lockfile.Lockfile.load(lockfilePath);
let pattern = opts.pattern;
if (!pattern) {
console.log(`Using default pattern glob ${DEFAULT_PATTERN_GLOB}`);
pattern = DEFAULT_PATTERN_GLOB;
} else {
console.log(`Using custom pattern glob ${pattern}`);
}
let findTargetVersion;
let releaseManifest;
if (semver__default["default"].valid(opts.release)) {
releaseManifest = await releaseManifests.getManifestByVersion({ version: opts.release });
findTargetVersion = createStrictVersionFinder({
releaseManifest
});
} else {
if (opts.release === "next") {
const next = await releaseManifests.getManifestByReleaseLine({
releaseLine: "next"
});
const main = await releaseManifests.getManifestByReleaseLine({
releaseLine: "main"
});
releaseManifest = semver__default["default"].gt(next.releaseVersion, main.releaseVersion) ? next : main;
} else {
releaseManifest = await releaseManifests.getManifestByReleaseLine({
releaseLine: opts.release
});
}
findTargetVersion = createVersionFinder({
releaseLine: opts.releaseLine,
releaseManifest
});
}
const dependencyMap = await packages.mapDependencies(index.paths.targetDir, pattern);
const versionBumps = /* @__PURE__ */ new Map();
const unlocked = Array();
await parallel.runParallelWorkers({
parallelismFactor: 4,
items: dependencyMap.entries(),
async worker([name, pkgs]) {
var _a2;
let target;
try {
target = await findTargetVersion(name);
} catch (error) {
if (errors.isError(error) && error.name === "NotFoundError") {
console.log(`Package info not found, ignoring package ${name}`);
return;
}
throw error;
}
for (const pkg of pkgs) {
versionBumps.set(
pkg.name,
((_a2 = versionBumps.get(pkg.name)) != null ? _a2 : []).concat({
name,
location: pkg.location,
range: `^${target}`,
// TODO(Rugvip): Option to use something else than ^?
target
})
);
}
}
});
const filter = (name) => minimatch__default["default"](name, pattern);
await parallel.runParallelWorkers({
parallelismFactor: 4,
items: lockfile.keys(),
async worker(name) {
var _a2;
if (!filter(name)) {
return;
}
let target;
try {
target = await findTargetVersion(name);
} catch (error) {
if (errors.isError(error) && error.name === "NotFoundError") {
console.log(`Package info not found, ignoring package ${name}`);
return;
}
throw error;
}
for (const entry of (_a2 = lockfile.get(name)) != null ? _a2 : []) {
if (!semver__default["default"].satisfies(target, entry.range)) {
continue;
}
unlocked.push({ name, range: entry.range, target });
}
}
});
console.log();
if (versionBumps.size === 0 && unlocked.length === 0) {
console.log(chalk__default["default"].green("All Backstage packages are up to date!"));
} else {
console.log(chalk__default["default"].yellow("Some packages are outdated, updating"));
console.log();
if (unlocked.length > 0) {
const removed = /* @__PURE__ */ new Set();
for (const { name, range, target } of unlocked) {
const existingEntry = (_a = lockfile.get(name)) == null ? void 0 : _a.find((e) => e.range === range);
if ((existingEntry == null ? void 0 : existingEntry.version) === target) {
continue;
}
const key = JSON.stringify({ name, range });
if (!removed.has(key)) {
removed.add(key);
console.log(
`${chalk__default["default"].magenta("unlocking")} ${name}@${chalk__default["default"].yellow(
range
)} ~> ${chalk__default["default"].yellow(target)}`
);
lockfile.remove(name, range);
}
}
await lockfile.save(lockfilePath);
}
const breakingUpdates = /* @__PURE__ */ new Map();
await parallel.runParallelWorkers({
parallelismFactor: 4,
items: versionBumps.entries(),
async worker([name, deps]) {
var _a2;
const pkgPath = path.resolve(deps[0].location, "package.json");
const pkgJson = await fs__default["default"].readJson(pkgPath);
for (const dep of deps) {
console.log(
`${chalk__default["default"].cyan("bumping")} ${dep.name} in ${chalk__default["default"].cyan(
name
)} to ${chalk__default["default"].yellow(dep.range)}`
);
for (const depType of DEP_TYPES) {
if (depType in pkgJson && dep.name in pkgJson[depType]) {
const oldRange = pkgJson[depType][dep.name];
pkgJson[depType][dep.name] = dep.range;
const lockfileEntry = (_a2 = lockfile.get(dep.name)) == null ? void 0 : _a2.find((entry) => entry.range === oldRange);
if (lockfileEntry) {
const from = lockfileEntry.version;
const to = dep.target;
if (!semver__default["default"].satisfies(to, `^${from}`)) {
breakingUpdates.set(dep.name, { from, to });
}
}
}
}
}
await fs__default["default"].writeJson(pkgPath, pkgJson, { spaces: 2 });
}
});
console.log();
if (pattern === DEFAULT_PATTERN_GLOB) {
await bumpBackstageJsonVersion(releaseManifest.releaseVersion);
} else {
console.log(
chalk__default["default"].yellow(
`Skipping backstage.json update as custom pattern is used`
)
);
}
if (!opts.skipInstall) {
await runYarnInstall();
} else {
console.log();
console.log(chalk__default["default"].yellow(`Skipping yarn install`));
}
if (breakingUpdates.size > 0) {
console.log();
console.log(
chalk__default["default"].yellow("\u26A0\uFE0F The following packages may have breaking changes:")
);
console.log();
for (const [name, { from, to }] of Array.from(
breakingUpdates.entries()
).sort()) {
console.log(
` ${chalk__default["default"].yellow(name)} : ${chalk__default["default"].yellow(from)} ~> ${chalk__default["default"].yellow(
to
)}`
);
let path;
if (name.startsWith("@backstage/plugin-")) {
path = `plugins/${name.replace("@backstage/plugin-", "")}`;
} else if (name.startsWith("@backstage/")) {
path = `packages/${name.replace("@backstage/", "")}`;
}
if (path) {
console.log(
` https://github.com/backstage/backstage/blob/master/${path}/CHANGELOG.md`
);
}
console.log();
}
} else {
console.log();
}
console.log(chalk__default["default"].green("Version bump complete!"));
}
console.log();
const dedupLockfile = await Lockfile.Lockfile.load(lockfilePath);
const result = dedupLockfile.analyze({
filter,
localPackages: cliNode.PackageGraph.fromPackages(
await cliNode.PackageGraph.listTargetPackages()
)
});
if (result.newVersions.length > 0) {
throw new Error("Duplicate versions present after package bump");
}
const forbiddenNewRanges = result.newRanges.filter(
({ name }) => lint.forbiddenDuplicatesFilter(name)
);
if (forbiddenNewRanges.length > 0) {
throw new Error(
`Version bump failed for ${forbiddenNewRanges.map((i) => i.name).join(", ")}`
);
}
};
function createStrictVersionFinder(options) {
const releasePackages = new Map(
options.releaseManifest.packages.map((p) => [p.name, p.version])
);
return async function findTargetVersion(name) {
console.log(`Checking for updates of ${name}`);
const manifestVersion = releasePackages.get(name);
if (manifestVersion) {
return manifestVersion;
}
throw new errors.NotFoundError(`Package ${name} not found in release manifest`);
};
}
function createVersionFinder(options) {
const {
releaseLine = "latest",
packageInfoFetcher = packages.fetchPackageInfo,
releaseManifest
} = options;
const distTag = releaseLine === "main" ? "latest" : releaseLine;
const found = /* @__PURE__ */ new Map();
const releasePackages = new Map(
releaseManifest == null ? void 0 : releaseManifest.packages.map((p) => [p.name, p.version])
);
return async function findTargetVersion(name) {
const existing = found.get(name);
if (existing) {
return existing;
}
console.log(`Checking for updates of ${name}`);
const manifestVersion = releasePackages.get(name);
if (manifestVersion) {
return manifestVersion;
}
const info = await packageInfoFetcher(name);
const latestVersion = info["dist-tags"].latest;
if (!latestVersion) {
throw new Error(`No target 'latest' version found for ${name}`);
}
const taggedVersion = info["dist-tags"][distTag];
if (distTag === "latest" || !taggedVersion) {
found.set(name, latestVersion);
return latestVersion;
}
const latestVersionDateStr = info.time[latestVersion];
const taggedVersionDateStr = info.time[taggedVersion];
if (!latestVersionDateStr) {
throw new Error(
`No time available for version '${latestVersion}' of ${name}`
);
}
if (!taggedVersionDateStr) {
throw new Error(
`No time available for version '${taggedVersion}' of ${name}`
);
}
const latestVersionRelease = new Date(latestVersionDateStr).getTime();
const taggedVersionRelease = new Date(taggedVersionDateStr).getTime();
if (latestVersionRelease > taggedVersionRelease) {
found.set(name, latestVersion);
return latestVersion;
}
found.set(name, taggedVersion);
return taggedVersion;
};
}
async function bumpBackstageJsonVersion(version) {
const backstageJsonPath = index.paths.resolveTargetRoot(cliCommon.BACKSTAGE_JSON);
const backstageJson = await fs__default["default"].readJSON(backstageJsonPath).catch((e) => {
if (e.code === "ENOENT") {
return;
}
throw e;
});
const prevVersion = backstageJson == null ? void 0 : backstageJson.version;
if (prevVersion === version) {
return;
}
const { yellow, cyan, green } = chalk__default["default"];
if (prevVersion) {
const from = encodeURIComponent(prevVersion);
const to = encodeURIComponent(version);
const link = `https://backstage.github.io/upgrade-helper/?from=${from}&to=${to}`;
console.log(
yellow(
`Upgraded from release ${green(prevVersion)} to ${green(
version
)}, please review these template changes:`
)
);
console.log();
console.log(` ${cyan(link)}`);
console.log();
} else {
console.log(
yellow(
`Your project is now at version ${version}, which has been written to ${cliCommon.BACKSTAGE_JSON}`
)
);
}
await fs__default["default"].writeJson(
backstageJsonPath,
{ ...backstageJson, version },
{
spaces: 2,
encoding: "utf8"
}
);
}
async function runYarnInstall() {
const spinner = ora__default["default"]({
prefixText: `Running ${chalk__default["default"].blue("yarn install")} to install new versions`,
spinner: "arc",
color: "green"
}).start();
const installOutput = new Array();
try {
await run.run("yarn", ["install"], {
env: {
FORCE_COLOR: "true",
// We filter out all of the npm_* environment variables that are added when
// executing through yarn. This works around an issue where these variables
// incorrectly override local yarn or npm config in the project directory.
...Object.fromEntries(
Object.entries(process.env).map(
([name, value]) => name.startsWith("npm_") ? [name, void 0] : [name, value]
)
)
},
stdoutLogFunc: (data) => installOutput.push(data),
stderrLogFunc: (data) => installOutput.push(data)
});
spinner.succeed();
} catch (error) {
spinner.fail();
process.stdout.write(Buffer.concat(installOutput));
throw error;
}
}
exports.bumpBackstageJsonVersion = bumpBackstageJsonVersion;
exports.createStrictVersionFinder = createStrictVersionFinder;
exports.createVersionFinder = createVersionFinder;
exports["default"] = bump;
//# sourceMappingURL=bump-bfe1e58e.cjs.js.map
;