From 54aac0c55ea51228ef7a65fea1e7be8f3d5baf5e Mon Sep 17 00:00:00 2001 From: Ainhoa Larumbe Date: Mon, 13 Dec 2021 09:52:17 +0000 Subject: [PATCH 1/3] add peerPluginDependencies to install other necessary plugins Signed-off-by: Ainhoa Larumbe --- packages/cli/src/commands/install/install.ts | 78 ++++++++++++++++---- packages/cli/src/commands/install/types.ts | 9 +++ 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/install/install.ts b/packages/cli/src/commands/install/install.ts index cc6e46cfa2..f60201a174 100644 --- a/packages/cli/src/commands/install/install.ts +++ b/packages/cli/src/commands/install/install.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { Step, PackageWithInstallRecipe } from './types'; +import { + Step, + PackageWithInstallRecipe, + PeerPluginDependencies, +} from './types'; import { fetchPackageInfo } from '../../lib/versioning'; import { NotFoundError } from '../../lib/errors'; import * as stepDefinitionMap from './steps'; @@ -52,7 +56,10 @@ type Steps = Array<{ }>; class PluginInstaller { - static async resolveSteps(pkg: PackageWithInstallRecipe) { + static async resolveSteps( + pkg: PackageWithInstallRecipe, + versionToInstall?: string, + ) { const steps: Steps = []; // collectDependencies @@ -62,7 +69,7 @@ class PluginInstaller { target: 'packages/app', type: 'dependencies' as const, name: pkg.name, - query: `^${pkg.version}`, + query: versionToInstall || `^${pkg.version}`, }); steps.push({ type: 'dependencies', @@ -97,22 +104,63 @@ class PluginInstaller { } } +async function installPluginAndPeerPlugins(pkg: PackageWithInstallRecipe) { + const pluginDeps: PeerPluginDependencies = 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: string, +): Promise { + if (plugin.endsWith('package.json')) { + // Install from local package if pluginId is a package.json file - needs to be absolute path + return await fs.readJson(plugin); + } + return await fetchPluginPackage(plugin); +} + +async function loadPeerPluginDeps( + pkg: PackageWithInstallRecipe, + pluginMap: PeerPluginDependencies, +) { + for (const [pluginId, pluginVersion] of Object.entries( + pkg.experimentalInstallationRecipe?.peerPluginDependencies ?? {}, + )) { + const depPkg = await loadPackageJson(pluginId); + if (!pluginMap.get(depPkg.name)) { + pluginMap.set(depPkg.name, { + pkg: depPkg, + versionToInstall: pluginVersion, + }); + await loadPeerPluginDeps(depPkg, pluginMap); + } + } +} + export default async (pluginId?: string, cmd?: Command) => { // TODO(himanshu): If no plugin id is provided, it should list all plugins available. Maybe in some other command? - - let pkg: PackageWithInstallRecipe; - if (pluginId) { - pkg = await fetchPluginPackage(pluginId); - } else if (cmd?.from) { - // TODO(himanshu): Also support reading directly from url - pkg = await fs.readJson(cmd.from); - } else { + if (!pluginId && !cmd?.from) { throw new Error( 'Missing both or a package.json file path in the --from flag.', ); } - - const steps = await PluginInstaller.resolveSteps(pkg); - const installer = new PluginInstaller(steps); - await installer.run(); + const pkg = await loadPackageJson(pluginId || cmd?.from); + await installPluginAndPeerPlugins(pkg); }; diff --git a/packages/cli/src/commands/install/types.ts b/packages/cli/src/commands/install/types.ts index 3105f78822..1fa52a5e82 100644 --- a/packages/cli/src/commands/install/types.ts +++ b/packages/cli/src/commands/install/types.ts @@ -38,6 +38,7 @@ export type SerializedStep = { export type InstallationRecipe = { type?: 'frontend' | 'backend'; steps: SerializedStep[]; + peerPluginDependencies: { [pluginId: string]: string }; }; /** package.json data */ @@ -46,6 +47,14 @@ export type PackageWithInstallRecipe = YarnInfoInspectData & { experimentalInstallationRecipe?: InstallationRecipe; }; +export type PeerPluginDependencies = Map< + string, + { + pkg: PackageWithInstallRecipe; + versionToInstall?: string; + } +>; + export interface Step { run(): Promise; } From dfc1110dc483bdbbafd50ef21888e6c559a20ed7 Mon Sep 17 00:00:00 2001 From: Ainhoa Larumbe Date: Mon, 13 Dec 2021 13:32:48 +0000 Subject: [PATCH 2/3] add changeset Signed-off-by: Ainhoa Larumbe --- .changeset/fluffy-grapes-decide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-grapes-decide.md diff --git a/.changeset/fluffy-grapes-decide.md b/.changeset/fluffy-grapes-decide.md new file mode 100644 index 0000000000..17a6cc965f --- /dev/null +++ b/.changeset/fluffy-grapes-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added peerPluginDependencies option to experimentalInstallationRecipe for install command to install plugins it depends on. From e2f35adc7acd719234f1e2d7beb40a19f7b5710a Mon Sep 17 00:00:00 2001 From: Ainhoa Larumbe Date: Mon, 20 Dec 2021 16:03:06 +0100 Subject: [PATCH 3/3] clearer code in install command Signed-off-by: Ainhoa Larumbe --- packages/cli/src/commands/install/install.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/install/install.ts b/packages/cli/src/commands/install/install.ts index f60201a174..bd3a05ebc1 100644 --- a/packages/cli/src/commands/install/install.ts +++ b/packages/cli/src/commands/install/install.ts @@ -155,12 +155,13 @@ async function loadPeerPluginDeps( } export default async (pluginId?: string, cmd?: Command) => { + const from = pluginId || cmd?.from; // TODO(himanshu): If no plugin id is provided, it should list all plugins available. Maybe in some other command? - if (!pluginId && !cmd?.from) { + if (!from) { throw new Error( 'Missing both or a package.json file path in the --from flag.', ); } - const pkg = await loadPackageJson(pluginId || cmd?.from); + const pkg = await loadPackageJson(from); await installPluginAndPeerPlugins(pkg); };