diff --git a/bench/app.py b/bench/app.py index 59d9b799..c667c155 100755 --- a/bench/app.py +++ b/bench/app.py @@ -14,6 +14,7 @@ from urllib.parse import urlparse # imports - third party imports import click +import requests # imports - module imports import bench @@ -206,6 +207,12 @@ class App(AppMeta): restart_bench=restart_bench ) + @step(title="Cloning and installing {repo}", success="App {repo} Installed") + def install_resolved_apps(self, *args, **kwargs): + self.get() + self.install(*args, **kwargs, resolved=True) + self.bench.apps.update_apps_states(self.repo, self.tag) + @step(title="Uninstalling App {repo}", success="App {repo} Uninstalled") def uninstall(self): self.bench.run(f"{self.bench.python} -m pip uninstall -y {self.repo}") @@ -404,6 +411,49 @@ def get_app( ): app.install(verbose=verbose, skip_assets=skip_assets) +def resolve_and_install( + git_url, + branch=None, + bench_path=".", + skip_assets=False, + verbose=False, + init_bench=False, +): + from bench.cli import Bench + from bench.utils.system import init + from bench.utils.app import check_existing_dir + + bench = Bench(bench_path) + app = App(git_url, branch=branch, bench=bench) + + resolution = make_resolution_plan(app, bench) + if "frappe" in resolution: + # Terminal dependency + del resolution["frappe"] + + if init_bench: + bench_path = get_available_folder_name(f"{app.repo}-bench", bench_path) + init( + path=bench_path, + frappe_branch=branch, + skip_assets=skip_assets, + verbose=verbose, + ) + os.chdir(bench_path) + + for repo_name, app in reversed(resolution.items()): + existing_dir, cloned_path = check_existing_dir(bench_path, repo_name) + if existing_dir: + if click.confirm( + f"A directory for the application '{repo_name}' already exists. " + "Do you want to continue and overwrite it?" + ): + click.secho(f"Removing {repo_name}", fg="yellow") + shutil.rmtree(cloned_path) + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) + + continue + app.install_resolved_apps(skip_assets=skip_assets, verbose=verbose) def new_app(app, no_git=None, bench_path="."): if bench.FRAPPE_VERSION in (0, None): diff --git a/bench/bench.py b/bench/bench.py index f359be7c..4e82f402 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -2,6 +2,7 @@ import functools import os import shutil +import json import sys import logging from typing import List, MutableSequence, TYPE_CHECKING @@ -30,6 +31,7 @@ from bench.utils.bench import ( get_env_cmd, ) from bench.utils.render import job, step +from bench.utils.app import get_current_version if TYPE_CHECKING: @@ -55,6 +57,7 @@ class Bench(Base, Validator): def __init__(self, path): self.name = path self.cwd = os.path.abspath(path) + self.apps_states = os.path.join(self.name, "sites", "apps_states.json") self.exists = is_bench_directory(self.name) self.setup = BenchSetup(self) @@ -122,6 +125,7 @@ class Bench(Base, Validator): self.validate_app_uninstall(app) self.apps.remove(App(app, bench=self, to_clone=False)) self.apps.sync() + self.apps.update_apps_states() # self.build() - removed because it seems unnecessary self.reload() @@ -156,6 +160,38 @@ class BenchApps(MutableSequence): def __init__(self, bench: Bench): self.bench = bench self.initialize_apps() + self.initialize_states() + + def initialize_states(self): + try: + with open(self.bench.apps_states, "r") as f: + self.states = json.loads(f.read() or "{}") + except FileNotFoundError: + with open(self.bench.apps_states, "w") as f: + self.states = json.loads(f.read() or "{}") + + def update_apps_states(self, app_name: str = None, resolution: str = None): + # Only tracking for app state for apps installed via `bench resolve-and-install`, + # Not all states can be maintained as some apps may not be install via bench resolve-and-install + # or may not be compatible with versioning + self.initialize_apps() + apps_to_remove = [] + for app in self.states: + if app not in self.apps: + apps_to_remove.append(app) + + for app in apps_to_remove: + del self.states[app] + + if app_name and resolution: + version = get_current_version(app_name) + self.states[app_name] = { + "resolution": resolution, + "version": version, + } + + with open(self.bench.apps_states, "w") as f: + f.write(json.dumps(self.states, indent=4)) def sync(self): self.initialize_apps()