diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 90765ae2e3..2be566f85f 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -223,15 +223,51 @@ def install_app(context, apps): sys.exit(exit_code) -@click.command('list-apps') +@click.command("list-apps") @pass_context def list_apps(context): "List apps in site" - site = get_site(context) - frappe.init(site=site) - frappe.connect() - print("\n".join(frappe.get_installed_apps())) - frappe.destroy() + + def fix_whitespaces(text): + if site == context.sites[-1]: + text = text.rstrip() + if len(context.sites) == 1: + text = text.lstrip() + return text + + for site in context.sites: + frappe.init(site=site) + frappe.connect() + site_title = ( + click.style(f"{site}", fg="green") if len(context.sites) > 1 else "" + ) + apps = frappe.get_single("Installed Applications").installed_applications + + if apps: + name_len, ver_len = [ + max([len(x.get(y)) for x in apps]) + for y in ["app_name", "app_version"] + ] + template = "{{0:{0}}} {{1:{1}}} {{2}}".format(name_len, ver_len) + + installed_applications = [ + template.format(app.app_name, app.app_version, app.git_branch) + for app in apps + ] + applications_summary = "\n".join(installed_applications) + summary = f"{site_title}\n{applications_summary}\n" + + else: + applications_summary = "\n".join(frappe.get_installed_apps()) + summary = f"{site_title}\n{applications_summary}\n" + + summary = fix_whitespaces(summary) + + if applications_summary and summary: + print(summary) + + frappe.destroy() + @click.command('add-system-manager') @click.argument('email') diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 4e02de795a..9757a823a6 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -143,5 +143,24 @@ class TestCommands(BaseTestCommands): # test 1: remove app from installed_apps global default self.execute("bench --site {site} remove-from-installed-apps {app}", {"app": app}) + self.assertEquals(self.returncode, 0) self.execute("bench --site {site} list-apps") self.assertNotIn(app, self.stdout) + + def test_list_apps(self): + # test 1: sanity check for command + self.execute("bench --site all list-apps") + self.assertEquals(self.returncode, 0) + + # test 2: bare functionality for single site + self.execute("bench --site {site} list-apps") + self.assertEquals(self.returncode, 0) + list_apps = set([ + _x.split()[0] for _x in self.stdout.split("\n") + ]) + doctype = frappe.get_single("Installed Applications").installed_applications + if doctype: + installed_apps = set([x.app_name for x in doctype]) + else: + installed_apps = set(frappe.get_installed_apps()) + self.assertSetEqual(list_apps, installed_apps)