@backstage/cli
Version:
CLI for developing Backstage plugins and apps
272 lines (261 loc) • 8.36 kB
JavaScript
;
var fs = require('fs-extra');
require('semver');
require('@yarnpkg/parsers');
require('@yarnpkg/lockfile');
var packages = require('./packages-692a0890.cjs.js');
var index = require('./index-3decf946.cjs.js');
var chalk = require('chalk');
var sortBy = require('lodash/sortBy');
var groupBy = require('lodash/groupBy');
var run = require('./run-168542e8.cjs.js');
require('minimatch');
require('@manypkg/get-packages');
require('./yarn-6cd89e16.cjs.js');
require('@backstage/errors');
require('child_process');
require('util');
require('commander');
require('@backstage/cli-common');
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 sortBy__default = /*#__PURE__*/_interopDefaultLegacy(sortBy);
var groupBy__default = /*#__PURE__*/_interopDefaultLegacy(groupBy);
function createStepDefinition(config) {
return config;
}
class AppRouteStep {
constructor(data) {
this.data = data;
}
async run() {
var _a;
const { path, element, packageName } = this.data;
const appTsxPath = index.paths.resolveTargetRoot("packages/app/src/App.tsx");
const contents = await fs__default["default"].readFile(appTsxPath, "utf-8");
let failed = false;
const contentsWithRoute = contents.replace(
/(\s*)<\/FlatRoutes>/,
`$1 <Route path="${path}" element={${element}} />$1</FlatRoutes>`
);
if (contentsWithRoute === contents) {
failed = true;
}
const componentName = (_a = element.match(/[A-Za-z0-9]+/)) == null ? void 0 : _a[0];
if (!componentName) {
throw new Error(`Could not find component name in ${element}`);
}
const contentsWithImport = contentsWithRoute.replace(
/^import /m,
`import { ${componentName} } from '${packageName}';
import `
);
if (contentsWithImport === contentsWithRoute) {
failed = true;
}
if (failed) {
console.log(
"Failed to automatically add a route to package/app/src/App.tsx"
);
console.log(`Action needed, add the following:`);
console.log(`1. import { ${componentName} } from '${packageName}';`);
console.log(`2. <Route path="${path}" element={${element}} />`);
} else {
await fs__default["default"].writeFile(appTsxPath, contentsWithImport);
}
}
}
const appRoute = createStepDefinition({
type: "app-route",
deserialize(obj, pkg) {
const { path, element } = obj;
if (!path || typeof path !== "string") {
throw new Error("Invalid install step, 'path' must be a string");
}
if (!element || typeof element !== "string") {
throw new Error("Invalid install step, 'element' must be a string");
}
return new AppRouteStep({ path, element, packageName: pkg.name });
},
create(data) {
return new AppRouteStep(data);
}
});
class DependenciesStep {
constructor(data) {
this.data = data;
}
async run() {
const { dependencies: dependencies2 } = this.data;
const byTarget = groupBy__default["default"](dependencies2, "target");
for (const [target, deps] of Object.entries(byTarget)) {
const pkgPath = index.paths.resolveTargetRoot(target, "package.json");
const pkgJson = await fs__default["default"].readJson(pkgPath);
const depTypes = /* @__PURE__ */ new Set();
for (const dep of deps) {
depTypes.add(dep.type);
pkgJson[dep.type][dep.name] = dep.query;
}
for (const depType of depTypes) {
pkgJson[depType] = Object.fromEntries(
sortBy__default["default"](Object.entries(pkgJson[depType]), ([key]) => key)
);
}
await fs__default["default"].writeJson(pkgPath, pkgJson, { spaces: 2 });
}
console.log();
console.log(
`Running ${chalk__default["default"].blue("yarn install")} to install new versions`
);
console.log();
await run.run("yarn", ["install"]);
}
}
const dependencies = createStepDefinition({
type: "dependencies",
deserialize() {
throw new Error("The dependency step may not be defined in JSON");
},
create(data) {
return new DependenciesStep(data);
}
});
class MessageStep {
constructor(data) {
this.data = data;
}
async run() {
console.log(this.data.message);
}
}
const message = createStepDefinition({
type: "message",
deserialize(obj) {
const { message: msg } = obj;
if (!msg || typeof msg !== "string" && !Array.isArray(msg)) {
throw new Error(
"Invalid install step, 'message' must be a string or array"
);
}
return new MessageStep({ message: [msg].flat().join("") });
},
create(data) {
return new MessageStep(data);
}
});
var stepDefinitionMap = /*#__PURE__*/Object.freeze({
__proto__: null,
appRoute: appRoute,
dependencies: dependencies,
message: message
});
const stepDefinitions = Object.values(stepDefinitionMap);
async function fetchPluginPackage(id) {
const searchNames = [`@backstage/plugin-${id}`, `backstage-plugin-${id}`, id];
for (const name of searchNames) {
try {
const packageInfo = await packages.fetchPackageInfo(
name
);
return packageInfo;
} catch (error) {
if (error.name !== "NotFoundError") {
throw error;
}
}
}
throw new index.NotFoundError(
`No matching package found for '${id}', tried ${searchNames.join(", ")}`
);
}
class PluginInstaller {
constructor(steps) {
this.steps = steps;
}
static async resolveSteps(pkg, versionToInstall) {
var _a, _b;
const steps = [];
const dependencies$1 = [];
dependencies$1.push({
target: "packages/app",
type: "dependencies",
name: pkg.name,
query: versionToInstall || `^${pkg.version}`
});
steps.push({
type: "dependencies",
step: dependencies.create({ dependencies: dependencies$1 })
});
for (const step of (_b = (_a = pkg.experimentalInstallationRecipe) == null ? void 0 : _a.steps) != null ? _b : []) {
const { type } = step;
const definition = stepDefinitions.find((d) => d.type === type);
if (definition) {
steps.push({
type,
step: definition.deserialize(step, pkg)
});
} else {
throw new Error(`Unsupported step type: ${type}`);
}
}
return steps;
}
async run() {
for (const { type, step } of this.steps) {
console.log(`Running step ${type}`);
await step.run();
}
}
}
async function installPluginAndPeerPlugins(pkg) {
const pluginDeps = /* @__PURE__ */ new Map();
pluginDeps.set(pkg.name, { pkg });
await loadPeerPluginDeps(pkg, pluginDeps);
console.log(`Installing ${pkg.name} AND any peer plugin dependencies.`);
for (const [_pluginDepName, pluginDep] of pluginDeps.entries()) {
const { pkg: pluginDepPkg, versionToInstall } = pluginDep;
console.log(
`Installing plugin: ${pluginDepPkg.name}: ${versionToInstall || pluginDepPkg.version}`
);
const steps = await PluginInstaller.resolveSteps(
pluginDepPkg,
versionToInstall
);
const installer = new PluginInstaller(steps);
await installer.run();
}
}
async function loadPackageJson(plugin) {
if (plugin.endsWith("package.json")) {
return await fs__default["default"].readJson(plugin);
}
return await fetchPluginPackage(plugin);
}
async function loadPeerPluginDeps(pkg, pluginMap) {
var _a, _b;
for (const [pluginId, pluginVersion] of Object.entries(
(_b = (_a = pkg.experimentalInstallationRecipe) == null ? void 0 : _a.peerPluginDependencies) != null ? _b : {}
)) {
const depPkg = await loadPackageJson(pluginId);
if (!pluginMap.get(depPkg.name)) {
pluginMap.set(depPkg.name, {
pkg: depPkg,
versionToInstall: pluginVersion
});
await loadPeerPluginDeps(depPkg, pluginMap);
}
}
}
var install = async (pluginId, cmd) => {
const from = pluginId || (cmd == null ? void 0 : cmd.from);
if (!from) {
throw new Error(
"Missing both <plugin-id> or a package.json file path in the --from flag."
);
}
const pkg = await loadPackageJson(from);
await installPluginAndPeerPlugins(pkg);
};
exports["default"] = install;
//# sourceMappingURL=install-80c2ac08.cjs.js.map