odooflow-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. odooflow_cli-0.1.0/LICENSE +21 -0
  2. odooflow_cli-0.1.0/PKG-INFO +174 -0
  3. odooflow_cli-0.1.0/README.md +141 -0
  4. odooflow_cli-0.1.0/odooflow/__init__.py +1 -0
  5. odooflow_cli-0.1.0/odooflow/cli.py +105 -0
  6. odooflow_cli-0.1.0/odooflow/commands/__init__.py +0 -0
  7. odooflow_cli-0.1.0/odooflow/commands/clone_module.py +160 -0
  8. odooflow_cli-0.1.0/odooflow/commands/config.py +63 -0
  9. odooflow_cli-0.1.0/odooflow/commands/init_module_env.py +37 -0
  10. odooflow_cli-0.1.0/odooflow/commands/keygen.py +56 -0
  11. odooflow_cli-0.1.0/odooflow/commands/push.py +132 -0
  12. odooflow_cli-0.1.0/odooflow/commands/remote.py +133 -0
  13. odooflow_cli-0.1.0/odooflow/commands/sync_env.py +38 -0
  14. odooflow_cli-0.1.0/odooflow/config_manager.py +56 -0
  15. odooflow_cli-0.1.0/odooflow/utils/env.py +40 -0
  16. odooflow_cli-0.1.0/odooflow/utils/ssh.py +137 -0
  17. odooflow_cli-0.1.0/odooflow_cli.egg-info/PKG-INFO +174 -0
  18. odooflow_cli-0.1.0/odooflow_cli.egg-info/SOURCES.txt +31 -0
  19. odooflow_cli-0.1.0/odooflow_cli.egg-info/dependency_links.txt +1 -0
  20. odooflow_cli-0.1.0/odooflow_cli.egg-info/entry_points.txt +2 -0
  21. odooflow_cli-0.1.0/odooflow_cli.egg-info/requires.txt +15 -0
  22. odooflow_cli-0.1.0/odooflow_cli.egg-info/top_level.txt +4 -0
  23. odooflow_cli-0.1.0/pyproject.toml +54 -0
  24. odooflow_cli-0.1.0/setup.cfg +4 -0
  25. odooflow_cli-0.1.0/tests/__init__.py +0 -0
  26. odooflow_cli-0.1.0/tests/test_commands_config.py +104 -0
  27. odooflow_cli-0.1.0/tests/test_commands_init_module_env.py +74 -0
  28. odooflow_cli-0.1.0/tests/test_commands_keygen.py +23 -0
  29. odooflow_cli-0.1.0/tests/test_commands_remote.py +32 -0
  30. odooflow_cli-0.1.0/tests/test_commands_sync_env.py +70 -0
  31. odooflow_cli-0.1.0/tests/test_config_manager.py +123 -0
  32. odooflow_cli-0.1.0/tests/test_utils_env.py +106 -0
  33. odooflow_cli-0.1.0/tests/test_utils_ssh.py +23 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mohammad Hamdan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: odooflow-cli
3
+ Version: 0.1.0
4
+ Summary: OdooFlow CLI - streamline your Odoo development workflow
5
+ Author: Mohammad A. Hamdan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/anomalyco/odooflow
8
+ Project-URL: Repository, https://github.com/anomalyco/odooflow
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: typer[all]
19
+ Requires-Dist: rich
20
+ Requires-Dist: GitPython>=3.1.44
21
+ Requires-Dist: requests>=2.32.3
22
+ Requires-Dist: paramiko>=3.5.1
23
+ Requires-Dist: tqdm>=4.67.1
24
+ Requires-Dist: bcrypt>=4.3.0
25
+ Requires-Dist: cryptography>=45.0.3
26
+ Requires-Dist: PyNaCl>=1.5.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
30
+ Requires-Dist: build>=1.2.2; extra == "dev"
31
+ Requires-Dist: twine>=6.1.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # 🌀 Odooflow CLI
35
+
36
+ **Odooflow CLI** is a command-line interface tool designed to streamline the development workflow for Odoo projects. It helps clone Odoo modules (and their dependencies), handles GitLab lookups, and provides options for deep recursive cloning.
37
+
38
+ ## 🚀 Features
39
+
40
+ - Clone an Odoo module by Git URL
41
+ - Recursively resolve and clone all dependencies
42
+ - Smart skip of Odoo core modules
43
+ - Branch selection for cloning
44
+ - Helpful and colorful CLI output
45
+ - Built using [Typer](https://typer.tiangolo.com/) and Python 3.9+
46
+
47
+ ---
48
+
49
+ ## đŸ“Ļ Installation
50
+
51
+ ```bash
52
+ git clone https://github.com/YOUR_USERNAME/odooflow-cli.git
53
+ cd odooflow-cli
54
+ pip install .
55
+ ```
56
+
57
+ Or install directly from source for development:
58
+
59
+ ```bash
60
+ pip install -e .
61
+ ```
62
+
63
+ ---
64
+
65
+ ## đŸ› ī¸ Usage
66
+
67
+ Once installed, you can use the CLI by running:
68
+
69
+ ```bash
70
+ odooflow --help
71
+ ```
72
+
73
+ ### Available Commands:
74
+
75
+ - **`init`**: Initialize the Odoo module environment file and sync metadata with manifest
76
+ - **`sync-env`**: Sync the environment file from manifest
77
+ - **`config`**: Update or show OdooFlow CLI configuration
78
+ - **`clone`**: Clone a module and its dependencies from a git repository
79
+ - **`remote`**: Manage remote connections for Git and deployment server
80
+ - **`ssh-keygen`**: Generate a secure SSH key pair
81
+ - **`push`**: Push the current Git branch and upload the project to the test server
82
+
83
+ ### Clone Command Options:
84
+
85
+ | Flag | Description |
86
+ |-------------|------------------------------------------|
87
+ | `--url` | Full HTTP URL of the module repo |
88
+ | `--branch` | (Optional) Git branch to clone from |
89
+ | `--deep` | Recursively clone all dependencies |
90
+
91
+ ### 🔍 Examples:
92
+
93
+ Clone a single module:
94
+
95
+ ```bash
96
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git
97
+ ```
98
+
99
+ Clone with specific branch:
100
+
101
+ ```bash
102
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git --branch 17.0
103
+ ```
104
+
105
+ Clone deeply with dependencies:
106
+
107
+ ```bash
108
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git --deep
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 📁 Project Structure
114
+
115
+ ```
116
+ odooflow/
117
+ ├── odooflow/
118
+ │ ├── __init__.py
119
+ │ ├── cli.py
120
+ │ ├── config_manager.py
121
+ │ ├── commands/
122
+ │ │ ├── __init__.py
123
+ │ │ ├── clone_module.py
124
+ │ │ ├── config.py
125
+ │ │ ├── init_module_env.py
126
+ │ │ ├── keygen.py
127
+ │ │ ├── push.py
128
+ │ │ ├── remote.py
129
+ │ │ └── sync_env.py
130
+ │ └── utils/
131
+ │ ├── env.py
132
+ │ └── ssh.py
133
+ ├── tests/
134
+ ├── README.md
135
+ ├── requirements.txt
136
+ ├── setup.py
137
+ └── LICENSE
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 🤝 Contributing
143
+
144
+ Contributions are welcome! Please open an issue or submit a pull request with any improvements, bug fixes, or new features.
145
+
146
+ 1. Fork the repository
147
+ 2. Create a new branch (`git checkout -b feature/your-feature`)
148
+ 3. Commit your changes (`git commit -am 'Add new feature'`)
149
+ 4. Push to the branch (`git push origin feature/your-feature`)
150
+ 5. Open a Pull Request
151
+
152
+ ---
153
+
154
+ ## 📝 License
155
+
156
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
157
+
158
+ ---
159
+
160
+ ## WISH-LIST:
161
+
162
+ Move this CLI into fully-integrated Odoo environment, using Odoo, users can create issues, add the amount of details, then sync these issues with Odooflow.
163
+
164
+ We can do integration with any code agent to help developers to achieve these issues
165
+
166
+ same thing for pipelines, I think it will be amazing if developers can build pipelines using Odoo, then apply the same pipelines using Odooflow.
167
+
168
+ I have many things in my head, I will back soon to this project.
169
+
170
+ ## 👨‍đŸ’ģ Author
171
+
172
+ Made with â¤ī¸ by Mohammad A. Hamdan
173
+
174
+ ---
@@ -0,0 +1,141 @@
1
+ # 🌀 Odooflow CLI
2
+
3
+ **Odooflow CLI** is a command-line interface tool designed to streamline the development workflow for Odoo projects. It helps clone Odoo modules (and their dependencies), handles GitLab lookups, and provides options for deep recursive cloning.
4
+
5
+ ## 🚀 Features
6
+
7
+ - Clone an Odoo module by Git URL
8
+ - Recursively resolve and clone all dependencies
9
+ - Smart skip of Odoo core modules
10
+ - Branch selection for cloning
11
+ - Helpful and colorful CLI output
12
+ - Built using [Typer](https://typer.tiangolo.com/) and Python 3.9+
13
+
14
+ ---
15
+
16
+ ## đŸ“Ļ Installation
17
+
18
+ ```bash
19
+ git clone https://github.com/YOUR_USERNAME/odooflow-cli.git
20
+ cd odooflow-cli
21
+ pip install .
22
+ ```
23
+
24
+ Or install directly from source for development:
25
+
26
+ ```bash
27
+ pip install -e .
28
+ ```
29
+
30
+ ---
31
+
32
+ ## đŸ› ī¸ Usage
33
+
34
+ Once installed, you can use the CLI by running:
35
+
36
+ ```bash
37
+ odooflow --help
38
+ ```
39
+
40
+ ### Available Commands:
41
+
42
+ - **`init`**: Initialize the Odoo module environment file and sync metadata with manifest
43
+ - **`sync-env`**: Sync the environment file from manifest
44
+ - **`config`**: Update or show OdooFlow CLI configuration
45
+ - **`clone`**: Clone a module and its dependencies from a git repository
46
+ - **`remote`**: Manage remote connections for Git and deployment server
47
+ - **`ssh-keygen`**: Generate a secure SSH key pair
48
+ - **`push`**: Push the current Git branch and upload the project to the test server
49
+
50
+ ### Clone Command Options:
51
+
52
+ | Flag | Description |
53
+ |-------------|------------------------------------------|
54
+ | `--url` | Full HTTP URL of the module repo |
55
+ | `--branch` | (Optional) Git branch to clone from |
56
+ | `--deep` | Recursively clone all dependencies |
57
+
58
+ ### 🔍 Examples:
59
+
60
+ Clone a single module:
61
+
62
+ ```bash
63
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git
64
+ ```
65
+
66
+ Clone with specific branch:
67
+
68
+ ```bash
69
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git --branch 17.0
70
+ ```
71
+
72
+ Clone deeply with dependencies:
73
+
74
+ ```bash
75
+ odooflow clone --url https://gitlab.com/mygroup/my_odoo_module.git --deep
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 📁 Project Structure
81
+
82
+ ```
83
+ odooflow/
84
+ ├── odooflow/
85
+ │ ├── __init__.py
86
+ │ ├── cli.py
87
+ │ ├── config_manager.py
88
+ │ ├── commands/
89
+ │ │ ├── __init__.py
90
+ │ │ ├── clone_module.py
91
+ │ │ ├── config.py
92
+ │ │ ├── init_module_env.py
93
+ │ │ ├── keygen.py
94
+ │ │ ├── push.py
95
+ │ │ ├── remote.py
96
+ │ │ └── sync_env.py
97
+ │ └── utils/
98
+ │ ├── env.py
99
+ │ └── ssh.py
100
+ ├── tests/
101
+ ├── README.md
102
+ ├── requirements.txt
103
+ ├── setup.py
104
+ └── LICENSE
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 🤝 Contributing
110
+
111
+ Contributions are welcome! Please open an issue or submit a pull request with any improvements, bug fixes, or new features.
112
+
113
+ 1. Fork the repository
114
+ 2. Create a new branch (`git checkout -b feature/your-feature`)
115
+ 3. Commit your changes (`git commit -am 'Add new feature'`)
116
+ 4. Push to the branch (`git push origin feature/your-feature`)
117
+ 5. Open a Pull Request
118
+
119
+ ---
120
+
121
+ ## 📝 License
122
+
123
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
124
+
125
+ ---
126
+
127
+ ## WISH-LIST:
128
+
129
+ Move this CLI into fully-integrated Odoo environment, using Odoo, users can create issues, add the amount of details, then sync these issues with Odooflow.
130
+
131
+ We can do integration with any code agent to help developers to achieve these issues
132
+
133
+ same thing for pipelines, I think it will be amazing if developers can build pipelines using Odoo, then apply the same pipelines using Odooflow.
134
+
135
+ I have many things in my head, I will back soon to this project.
136
+
137
+ ## 👨‍đŸ’ģ Author
138
+
139
+ Made with â¤ī¸ by Mohammad A. Hamdan
140
+
141
+ ---
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,105 @@
1
+ import typer
2
+ from typing import List, Optional
3
+
4
+ from odooflow.commands.init_module_env import init_module_env
5
+ from odooflow.commands.sync_env import sync_env as sync_env_command
6
+ from odooflow.commands.config import config as config_command
7
+ from odooflow.commands.clone_module import clone_module_command
8
+ from odooflow.commands.remote import remote as remote_command
9
+ from odooflow.commands.keygen import generate_ssh_key as keygen_command
10
+ from odooflow.commands.push import push_command
11
+
12
+ app = typer.Typer(help="OdooFlow CLI — streamline your Odoo development workflow.")
13
+
14
+ @app.command(name="init")
15
+ def init_manifest(
16
+ author: Optional[str] = typer.Option(None, help="Author name"),
17
+ website : Optional[str] = typer.Option(None, help="Website"),
18
+ odoo_version: Optional[str] = typer.Option(None, help="Odoo version"),
19
+ license_name: Optional[str] = typer.Option(None, help="License"),
20
+ ):
21
+ """
22
+ Initialize the Odoo module environment file and sync metadata with manifest.
23
+ """
24
+ init_module_env(author=author, odoo_version=odoo_version, license_name=license_name, website=website)
25
+
26
+ @app.command(name="sync-env")
27
+ def sync_env(
28
+ keys: Optional[str] = typer.Option(None, "--keys", help="Comma-separated keys to sync from manifest to env file.")
29
+ ):
30
+ """
31
+ Sync the environment file (.odooflow.env.json) from manifest.
32
+ """
33
+ sync_env_command(keys)
34
+
35
+ @app.command("config", help="Update configuration")
36
+ def config(
37
+ env_file: Optional[str] = typer.Option(None, help="Set custom env file name"),
38
+ manifest_file: Optional[str] = typer.Option(None, help="Set custom manifest file name"),
39
+ access_token : Optional[str] = typer.Option(None, help="Set Git access token"),
40
+ add_core_module: Optional[str] = typer.Option(None, "--add-core-module", help="Comma-separated list of core modules to add"),
41
+ sync_keys: Optional[str] = typer.Option(None, help="Set default keys to sync from manifest to .env (comma-separated)"),
42
+ show: Optional[bool] = typer.Option(False, "--show", help="Show current config")
43
+ ):
44
+ """
45
+ Update or show OdooFlow CLI configuration (.odooflowrc)
46
+ """
47
+ config_command(env_file, manifest_file, access_token, add_core_module, sync_keys,show)
48
+
49
+
50
+ @app.command("clone")
51
+ def clone_command(
52
+ repo_url: str = typer.Argument(..., help="HTTP URL of the module repository."),
53
+ branch: Optional[str] = typer.Option(None, "--branch", '-b', help="Branch to clone"),
54
+ depth: int = typer.Option(1, "--depth", "-d", help="Max dependency depth to clone. 1 clones only the target module, 2 clones target + immediate dependencies, etc.")
55
+ ):
56
+ """
57
+ Clone a module and its dependencies from a git repository.
58
+ """
59
+ clone_module_command(repo_url, branch, depth)
60
+
61
+
62
+ @app.command()
63
+ def remote(
64
+ add_repo: Optional[str] = typer.Option(None, help="Add Git remote URL"),
65
+ branch: Optional[str] = typer.Option(None, help="Target Git branch (defaults to current)"),
66
+ server_json: Optional[str] = typer.Option(None, help="Server config as JSON: '{\"host\": \"127.0.0.1\", \"port\": 22}'")
67
+ ):
68
+ """
69
+ Manage remote connections for Git and deployment server.
70
+ """
71
+ remote_command(
72
+ add_repo=add_repo,
73
+ server_json=server_json,
74
+ branch=branch
75
+ )
76
+
77
+ @app.command("ssh-keygen")
78
+ def generate_ssh_key(
79
+ key_name: str = typer.Option("odooflow_rsa", help="Name of the SSH key file to generate (without extension)."),
80
+ output_dir: Optional[str] = typer.Option(None, help="Directory to save the SSH key. Defaults to ~/.ssh"),
81
+ overwrite: bool = typer.Option(False, help="Overwrite existing key files if they exist."),
82
+ ):
83
+ """
84
+ Generate a secure SSH key pair.
85
+ """
86
+ keygen_command(key_name=key_name, output_dir=output_dir, overwrite=overwrite)
87
+
88
+
89
+ @app.command()
90
+ def push(
91
+ remote_only: bool = typer.Option(False, "--remote-only", help="Skip Git push and only upload to server"),
92
+ exec_cmd: Optional[str] = typer.Option(None, "--exec", help="Custom shell command to execute on the server after pushing"),
93
+ ):
94
+ """
95
+ Push the current Git branch and upload the project to the test server.
96
+ """
97
+ push_command(remote_only=remote_only, exec_cmd=exec_cmd)
98
+
99
+
100
+
101
+ def main():
102
+ app()
103
+
104
+ if __name__ == "__main__":
105
+ main()
File without changes
@@ -0,0 +1,160 @@
1
+ import ast
2
+ import typer
3
+ import requests
4
+ import threading
5
+
6
+ from typing import Optional
7
+ from concurrent.futures import ThreadPoolExecutor
8
+
9
+ from urllib.parse import urlparse, urlunparse
10
+ from git import Repo, GitCommandError
11
+ from pathlib import Path
12
+
13
+ from odooflow.config_manager import get_access_token, get_core_modules_from_config, load_config
14
+
15
+
16
+ def get_project_url_from_gitlab(module_name: str, base_url: Optional[str] = None) -> Optional[str]:
17
+ """
18
+ Search GitLab for a project by name and return its HTTPS URL.
19
+ """
20
+ if base_url is None:
21
+ config = load_config()
22
+ base_url = config.get("gitlab_url", "https://gitlab.ebtech-solution.com")
23
+
24
+ api_url = f"{base_url}/api/v4/projects"
25
+ params = {"search": module_name, "simple": "true", "per_page": 100, "access_token": get_access_token()}
26
+ headers = {"Accept": "application/json"}
27
+
28
+ try:
29
+ typer.secho(f"🔍 Searching GitLab for module: {module_name}", fg="cyan")
30
+ response = requests.get(api_url, headers=headers, params=params)
31
+ response.raise_for_status()
32
+
33
+ for project in response.json():
34
+ if project["name"] == module_name or project["path"] == module_name:
35
+ typer.secho(f"✅ Found module '{module_name}'", fg="green")
36
+ return project["http_url_to_repo"]
37
+
38
+ typer.secho(f"❌ Module '{module_name}' not found in GitLab.", fg="yellow")
39
+ return None
40
+
41
+ except requests.RequestException as e:
42
+ typer.secho(f"❌ GitLab API error: {e}", fg="red")
43
+ return None
44
+
45
+
46
+ def inject_token_into_url(url: str, token: str) -> str:
47
+ parsed = urlparse(url)
48
+ if not parsed.netloc:
49
+ raise ValueError(f"Invalid URL: {url}")
50
+ netloc = f"oauth2:{token}@{parsed.netloc}"
51
+ return urlunparse(parsed._replace(netloc=netloc, scheme="https"))
52
+
53
+
54
+ def extract_module_name_from_url(url: str) -> str:
55
+ return url.rstrip("/").split("/")[-1].removesuffix(".git")
56
+
57
+
58
+ def safe_eval_manifest(content: str) -> dict:
59
+ try:
60
+ return ast.literal_eval(content)
61
+ except Exception as e:
62
+ typer.secho(f"❌ Failed to evaluate manifest: {e}", fg="red")
63
+ return {}
64
+
65
+
66
+ def clone_repo(url: str, target_dir: Path, branch: str = "main") -> bool:
67
+ if target_dir.exists():
68
+ typer.secho(f"âš ī¸ Skipping '{target_dir.name}': already exists.", fg="yellow")
69
+ return True
70
+ try:
71
+ access_token = get_access_token()
72
+ url_with_token = inject_token_into_url(url, access_token)
73
+ typer.secho(f"đŸ“Ĩ Cloning into '{target_dir}'...", fg="cyan")
74
+ Repo.clone_from(url_with_token, target_dir, branch=branch)
75
+ typer.secho(f"✅ Successfully cloned '{url}'", fg="green")
76
+ return True
77
+ except GitCommandError as e:
78
+ typer.secho(f"❌ Git error: Failed to clone '{url}'\n{e}", fg="red")
79
+ return False
80
+ except Exception as e:
81
+ typer.secho(f"❌ Unexpected error: {e}", fg="red")
82
+ return False
83
+
84
+
85
+ def clone_module_command(
86
+ url: str = typer.Option(..., "--url", help="Full HTTP URL of the module repo."),
87
+ branch: Optional[str] = None,
88
+ depth: int = typer.Option(1, "--depth", "-d", help="Max dependency depth to clone. 1 clones only the target module, 2 clones target + immediate dependencies, etc."),
89
+ ):
90
+ """
91
+ Clone a module and its dependencies into the current directory.
92
+ """
93
+ typer.secho("🚀 Starting module cloning process...", fg="cyan", bold=True)
94
+ core_modules = get_core_modules_from_config()
95
+ visited = set()
96
+ fail_count = 0
97
+ lock = threading.Lock()
98
+
99
+ def clone_recursive(module_url: str, current_branch: Optional[str], current_depth: int):
100
+ nonlocal fail_count
101
+ module_name = extract_module_name_from_url(module_url)
102
+
103
+ with lock:
104
+ if module_name in visited:
105
+ typer.secho(f"🔁 Already processed '{module_name}', skipping.", fg="yellow")
106
+ return False
107
+ visited.add(module_name)
108
+
109
+ target_path = Path.cwd() / module_name
110
+
111
+ if not clone_repo(module_url, target_path, current_branch or "main"):
112
+ typer.secho(f"❌ Failed to clone '{module_name}'. Skipping its dependencies.", fg="red")
113
+ with lock:
114
+ fail_count += 1
115
+ return False
116
+
117
+ if current_depth <= 0:
118
+ return False
119
+
120
+ manifest_path = target_path / "__manifest__.py"
121
+ if not manifest_path.exists():
122
+ typer.secho(f"đŸ“Ļ No manifest found in '{module_name}', skipping dependencies.", fg="yellow")
123
+ return True
124
+
125
+ manifest_data = safe_eval_manifest(manifest_path.read_text())
126
+ dependencies = manifest_data.get("depends", [])
127
+
128
+ if not dependencies:
129
+ typer.secho(f"â„šī¸ No dependencies for '{module_name}'.", fg="blue")
130
+ return True
131
+
132
+ candidate_deps = [dep for dep in dependencies if dep not in core_modules]
133
+ if not candidate_deps:
134
+ return True
135
+
136
+ next_depth = current_depth - 1
137
+
138
+ def _resolve_and_run(dep_name: str):
139
+ dep_url = get_project_url_from_gitlab(module_name=dep_name)
140
+ if not dep_url:
141
+ typer.secho(f"❗ Could not resolve dependency: '{dep_name}'", fg="red")
142
+ nonlocal_assign = True
143
+ with lock:
144
+ fail_count += 1
145
+ return
146
+ clone_recursive(dep_url, current_branch, next_depth)
147
+
148
+ with ThreadPoolExecutor(max_workers=4) as executor:
149
+ futures = [executor.submit(_resolve_and_run, dep) for dep in candidate_deps]
150
+ for f in futures:
151
+ f.result()
152
+
153
+ return True
154
+
155
+ clone_recursive(url, branch, depth)
156
+
157
+ if fail_count > 0:
158
+ typer.secho(f"âš ī¸ Finished with {fail_count} failed clones.", fg="yellow", bold=True)
159
+ else:
160
+ typer.secho("✅ All done without errors!", fg="green", bold=True)
@@ -0,0 +1,63 @@
1
+ import typer
2
+ from rich import print
3
+ from typing import Optional
4
+
5
+ from odooflow import config_manager
6
+
7
+ def config(
8
+ env_file: str = typer.Option(None),
9
+ manifest_file: str = typer.Option(None),
10
+ access_token: str = typer.Option(None),
11
+ add_core_module: Optional[str] = typer.Option(None),
12
+ sync_keys: Optional[str] = typer.Option(None),
13
+ show: bool = typer.Option(False, "--show", help="Display current configuration")
14
+ ):
15
+ current_config = config_manager.load_config()
16
+ updated = False
17
+
18
+ if show:
19
+ typer.secho("đŸ“Ļ Current Configuration:", fg="cyan", bold=True)
20
+ print(current_config)
21
+ raise typer.Exit()
22
+
23
+ if env_file:
24
+ current_config["env_file"] = env_file
25
+ updated = True
26
+
27
+ if manifest_file:
28
+ current_config["manifest_file"] = manifest_file
29
+ updated = True
30
+
31
+ if access_token:
32
+ current_config["access_token"] = access_token
33
+ updated = True
34
+
35
+ if add_core_module:
36
+ modules = [m.strip() for m in add_core_module.split(",") if m.strip()]
37
+ if modules:
38
+ existing_modules = set(current_config.get(
39
+ "core_modules",
40
+ config_manager.DEFAULT_CONFIG.get("core_modules", [])
41
+ ))
42
+ new_modules = set(modules)
43
+ combined_modules = sorted(existing_modules.union(new_modules))
44
+ current_config["core_modules"] = combined_modules
45
+ updated = True
46
+ typer.secho(f"✅ Added core module(s): {', '.join(new_modules)}", fg="green")
47
+
48
+ if sync_keys:
49
+ new_keys = [k.strip() for k in sync_keys.split(",") if k.strip()]
50
+ existing_keys = set(current_config.get(
51
+ "sync_keys",
52
+ config_manager.DEFAULT_CONFIG['sync_keys']
53
+ ))
54
+ combined_keys = sorted(existing_keys.union(new_keys))
55
+ current_config["sync_keys"] = combined_keys
56
+ updated = True
57
+ typer.secho(f"🔑 Updated sync keys: {', '.join(new_keys)}", fg="cyan")
58
+
59
+ if updated:
60
+ config_manager.save_config(current_config)
61
+ typer.secho("💾 Configuration updated successfully.", fg="green")
62
+ else:
63
+ typer.secho("âš ī¸ No changes provided. Use --help to see available options.", fg="yellow")