youca-setup 0.1.0__py3-none-any.whl
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.
- youca_setup/__about__.py +9 -0
- youca_setup/__init__.py +16 -0
- youca_setup/cli.py +143 -0
- youca_setup/commands/__init__.py +21 -0
- youca_setup/commands/_port_utils.py +48 -0
- youca_setup/commands/_state.py +33 -0
- youca_setup/commands/backup.py +47 -0
- youca_setup/commands/doctor.py +82 -0
- youca_setup/commands/export.py +43 -0
- youca_setup/commands/import_cmd.py +61 -0
- youca_setup/commands/info.py +42 -0
- youca_setup/commands/init.py +318 -0
- youca_setup/commands/logs.py +43 -0
- youca_setup/commands/module.py +63 -0
- youca_setup/commands/projects.py +71 -0
- youca_setup/commands/remove.py +87 -0
- youca_setup/commands/restart.py +41 -0
- youca_setup/commands/restore.py +49 -0
- youca_setup/commands/shell.py +84 -0
- youca_setup/commands/start.py +48 -0
- youca_setup/commands/status.py +53 -0
- youca_setup/commands/stop.py +51 -0
- youca_setup/diagnostics/__init__.py +8 -0
- youca_setup/diagnostics/checks.py +241 -0
- youca_setup/diagnostics/fixer.py +131 -0
- youca_setup/docker/__init__.py +8 -0
- youca_setup/docker/compose.py +155 -0
- youca_setup/docker/manager.py +246 -0
- youca_setup/postgres/__init__.py +7 -0
- youca_setup/postgres/manager.py +183 -0
- youca_setup/project/__init__.py +13 -0
- youca_setup/project/backup.py +351 -0
- youca_setup/project/config.py +127 -0
- youca_setup/project/generator.py +146 -0
- youca_setup/project/loader.py +30 -0
- youca_setup/project/odoo_module.py +167 -0
- youca_setup/project/registry.py +69 -0
- youca_setup/project/templates/.gitignore.j2 +18 -0
- youca_setup/project/templates/README.md.j2 +27 -0
- youca_setup/project/templates/__init__.py +1 -0
- youca_setup/project/templates/docker-compose.yml.j2 +43 -0
- youca_setup/project/templates/env.j2 +6 -0
- youca_setup/project/templates/modules/__manifest__.py.j2 +14 -0
- youca_setup/project/templates/modules/models/__init__.py.j2 +1 -0
- youca_setup/project/templates/modules/models/model.py.j2 +10 -0
- youca_setup/project/templates/modules/security/ir.model.access.csv.j2 +2 -0
- youca_setup/project/templates/modules/tests/__init__.py.j2 +1 -0
- youca_setup/project/templates/modules/tests/test_model.py.j2 +7 -0
- youca_setup/project/templates/modules/views/model_views.xml.j2 +32 -0
- youca_setup/project/templates/odoo.conf.j2 +13 -0
- youca_setup/project/transport.py +107 -0
- youca_setup/utils/__init__.py +16 -0
- youca_setup/utils/filesystem.py +48 -0
- youca_setup/utils/ports.py +84 -0
- youca_setup/utils/secrets.py +20 -0
- youca_setup/utils/system.py +89 -0
- youca_setup/utils/tar.py +32 -0
- youca_setup-0.1.0.dist-info/METADATA +135 -0
- youca_setup-0.1.0.dist-info/RECORD +62 -0
- youca_setup-0.1.0.dist-info/WHEEL +4 -0
- youca_setup-0.1.0.dist-info/entry_points.txt +2 -0
- youca_setup-0.1.0.dist-info/licenses/LICENSE +21 -0
youca_setup/__about__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Metadata about the Youca Setup distribution."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
__title__ = "youca-setup"
|
|
5
|
+
__summary__ = (
|
|
6
|
+
"Youca Setup for Odoo - CLI tool to create, configure and manage Odoo development environments."
|
|
7
|
+
)
|
|
8
|
+
__license__ = "MIT"
|
|
9
|
+
__uri__ = "https://github.com/Fitiafenohaja/youca-setup"
|
youca_setup/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Youca Setup for Odoo - CLI tool to create, configure and manage Odoo development environments.
|
|
2
|
+
|
|
3
|
+
Youca Setup orchestrates existing tools (Docker, PostgreSQL, Git) to bootstrap and
|
|
4
|
+
maintain an Odoo development environment with a minimal set of commands:
|
|
5
|
+
|
|
6
|
+
youca-setup init my-project
|
|
7
|
+
youca-setup start
|
|
8
|
+
youca-setup stop
|
|
9
|
+
youca-setup doctor
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from youca_setup.__about__ import __version__
|
|
15
|
+
|
|
16
|
+
__all__ = ["__version__"]
|
youca_setup/cli.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Main CLI entry point for Youca Setup.
|
|
2
|
+
|
|
3
|
+
Builds the Typer application, registers every subcommand from the
|
|
4
|
+
:mod:`youca_setup.commands` package and wires the Rich error rendering.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import rich
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.traceback import Traceback
|
|
15
|
+
|
|
16
|
+
from youca_setup.__about__ import __title__, __version__
|
|
17
|
+
|
|
18
|
+
APP_NAME = __title__
|
|
19
|
+
APP_VERSION = __version__
|
|
20
|
+
|
|
21
|
+
_VERBOSE = False
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
name=APP_NAME,
|
|
25
|
+
help="Youca Setup for Odoo — create, configure and manage Odoo development environments.",
|
|
26
|
+
no_args_is_help=True,
|
|
27
|
+
pretty_exceptions_show_locals=False,
|
|
28
|
+
pretty_exceptions_short=True,
|
|
29
|
+
add_completion=True,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
console = Console(stderr=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _version_callback(value: bool) -> None:
|
|
36
|
+
"""Print the current version and exit when ``--version`` is given."""
|
|
37
|
+
if value:
|
|
38
|
+
console.print(f"{APP_NAME} version {APP_VERSION}")
|
|
39
|
+
raise typer.Exit()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.callback()
|
|
43
|
+
def main(
|
|
44
|
+
ctx: typer.Context,
|
|
45
|
+
verbose: Annotated[
|
|
46
|
+
bool,
|
|
47
|
+
typer.Option(
|
|
48
|
+
"--verbose", "-v", help="Enable verbose mode: print full tracebacks on errors."
|
|
49
|
+
),
|
|
50
|
+
] = False,
|
|
51
|
+
version: Annotated[
|
|
52
|
+
bool,
|
|
53
|
+
typer.Option(
|
|
54
|
+
"--version",
|
|
55
|
+
help="Show the version and exit.",
|
|
56
|
+
callback=_version_callback,
|
|
57
|
+
is_eager=True,
|
|
58
|
+
),
|
|
59
|
+
] = False,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""CLI entry point.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
ctx: Typer context shared across subcommands.
|
|
65
|
+
verbose: Whether to display full tracebacks on errors.
|
|
66
|
+
version: Whether the ``--version`` flag was passed.
|
|
67
|
+
"""
|
|
68
|
+
ctx.obj = {"verbose": verbose}
|
|
69
|
+
global _VERBOSE
|
|
70
|
+
_VERBOSE = verbose
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def run() -> None:
|
|
74
|
+
"""Run the CLI and render any uncaught error with Rich.
|
|
75
|
+
|
|
76
|
+
In debug mode (``--verbose``) the full traceback is shown; otherwise a
|
|
77
|
+
concise, actionable error message is printed.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
app()
|
|
81
|
+
except typer.Exit:
|
|
82
|
+
raise
|
|
83
|
+
except KeyboardInterrupt:
|
|
84
|
+
console.print("\n[bold yellow]Aborted by user.[/]")
|
|
85
|
+
raise typer.Exit(130) from None
|
|
86
|
+
except Exception as exc: # noqa: BLE001 - top-level safety net.
|
|
87
|
+
if _VERBOSE:
|
|
88
|
+
console.print(Traceback(show_locals=False, suppress=[typer, rich]))
|
|
89
|
+
else:
|
|
90
|
+
console.print(f"[bold red]Error:[/] {exc}")
|
|
91
|
+
console.print("Re-run with [bold]--verbose[/] for a full traceback.")
|
|
92
|
+
raise typer.Exit(1) from exc
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _register_commands() -> None:
|
|
96
|
+
"""Register every subcommand on the CLI.
|
|
97
|
+
|
|
98
|
+
Single-command modules register their function directly as a top-level
|
|
99
|
+
command (``youca-setup start``, ``youca-setup info``, ...). The ``module`` group is
|
|
100
|
+
the only one with real subcommands and stays nested
|
|
101
|
+
(``youca-setup module create ...``).
|
|
102
|
+
"""
|
|
103
|
+
from youca_setup.commands import (
|
|
104
|
+
backup,
|
|
105
|
+
doctor,
|
|
106
|
+
export,
|
|
107
|
+
import_cmd,
|
|
108
|
+
info,
|
|
109
|
+
init,
|
|
110
|
+
logs,
|
|
111
|
+
module,
|
|
112
|
+
projects,
|
|
113
|
+
remove,
|
|
114
|
+
restart,
|
|
115
|
+
restore,
|
|
116
|
+
shell,
|
|
117
|
+
start,
|
|
118
|
+
status,
|
|
119
|
+
stop,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
app.command(name="init")(init.init)
|
|
123
|
+
app.command(name="start")(start.start)
|
|
124
|
+
app.command(name="stop")(stop.stop)
|
|
125
|
+
app.command(name="restart")(restart.restart)
|
|
126
|
+
app.command(name="status")(status.status)
|
|
127
|
+
app.command(name="logs")(logs.logs)
|
|
128
|
+
app.command(name="info")(info.info)
|
|
129
|
+
app.command(name="projects")(projects.projects)
|
|
130
|
+
app.command(name="remove")(remove.remove)
|
|
131
|
+
app.command(name="doctor")(doctor.doctor)
|
|
132
|
+
app.add_typer(module.app, name="module")
|
|
133
|
+
app.command(name="backup")(backup.backup)
|
|
134
|
+
app.command(name="restore")(restore.restore)
|
|
135
|
+
app.command(name="shell")(shell.shell)
|
|
136
|
+
app.command(name="export")(export.export)
|
|
137
|
+
app.command(name="import")(import_cmd.import_env)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
_register_commands()
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
run()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Youca Setup CLI subcommands.
|
|
2
|
+
|
|
3
|
+
Each subcommand lives in its own module and exposes a ``typer.Typer``
|
|
4
|
+
instance named ``app``. The main application in :mod:`youca_setup.cli` registers
|
|
5
|
+
them all at import time.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"init",
|
|
12
|
+
"start",
|
|
13
|
+
"stop",
|
|
14
|
+
"restart",
|
|
15
|
+
"status",
|
|
16
|
+
"logs",
|
|
17
|
+
"doctor",
|
|
18
|
+
"module",
|
|
19
|
+
"backup",
|
|
20
|
+
"restore",
|
|
21
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Shared helpers for the lifecycle commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.prompt import Confirm
|
|
10
|
+
|
|
11
|
+
from youca_setup.docker.compose import ComposeProject
|
|
12
|
+
from youca_setup.project.config import ProjectConfig
|
|
13
|
+
from youca_setup.utils.ports import find_free_port, is_port_free
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_port_conflict(config: ProjectConfig, compose_file: Path) -> int | None:
|
|
19
|
+
"""Check the HTTP port and, if occupied, switch to a free one.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
config: Project configuration.
|
|
23
|
+
compose_file: Location of the Compose file.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The resolved HTTP port, or ``None`` when nothing could be checked.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
typer.Exit: If the user declines the proposed alternative.
|
|
30
|
+
"""
|
|
31
|
+
compose = ComposeProject(config, compose_file)
|
|
32
|
+
try:
|
|
33
|
+
port = compose.http_port()
|
|
34
|
+
except (FileNotFoundError, ValueError):
|
|
35
|
+
return None
|
|
36
|
+
if is_port_free(port):
|
|
37
|
+
return port
|
|
38
|
+
alternative = find_free_port(port)
|
|
39
|
+
accepted = Confirm.ask(
|
|
40
|
+
f"Port {port} is occupied. Use {alternative} instead?",
|
|
41
|
+
default=True,
|
|
42
|
+
)
|
|
43
|
+
if not accepted:
|
|
44
|
+
console.print("[bold red]Aborting: no free HTTP port chosen.[/]")
|
|
45
|
+
raise typer.Exit(code=1)
|
|
46
|
+
compose.set_http_port(alternative)
|
|
47
|
+
console.print(f"[yellow]Updated docker-compose.yml to use port {alternative}.[/]")
|
|
48
|
+
return alternative
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Shared helpers for the ``info`` and ``projects`` commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from youca_setup.docker.manager import DockerManager
|
|
6
|
+
from youca_setup.project.config import ProjectConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def project_state(config: ProjectConfig) -> str:
|
|
10
|
+
"""Return a human-readable state of the project's services.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
config: Project configuration.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
``"running"`` when both services are up, ``"partial"`` when only one
|
|
17
|
+
is, ``"stopped"`` when none exist, ``"unknown"`` when Docker is
|
|
18
|
+
unreachable, or ``"system"`` for non-Docker projects.
|
|
19
|
+
"""
|
|
20
|
+
if not config.docker:
|
|
21
|
+
return "system"
|
|
22
|
+
manager = DockerManager(config)
|
|
23
|
+
try:
|
|
24
|
+
db = manager.container_status(f"{config.name}-db-1")
|
|
25
|
+
odoo = manager.container_status(f"{config.name}-odoo-1")
|
|
26
|
+
except RuntimeError:
|
|
27
|
+
return "unknown"
|
|
28
|
+
statuses = [status for status in (db, odoo) if status is not None]
|
|
29
|
+
if len(statuses) == 2 and all(status == "running" for status in statuses):
|
|
30
|
+
return "running"
|
|
31
|
+
if statuses:
|
|
32
|
+
return "partial"
|
|
33
|
+
return "stopped"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""The ``backup`` command - backup database, filestore and configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from youca_setup.project.backup import BackupManager
|
|
12
|
+
from youca_setup.project.loader import load_project
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(help="Backup the Odoo project (database + filestore + config).")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.command()
|
|
19
|
+
def backup(
|
|
20
|
+
database: Annotated[
|
|
21
|
+
str | None,
|
|
22
|
+
typer.Option("--database", "-d", help="Database name. Defaults to the project name."),
|
|
23
|
+
] = None,
|
|
24
|
+
output_dir: Annotated[
|
|
25
|
+
str, typer.Option("--output", "-o", help="Directory where the archive is written.")
|
|
26
|
+
] = "backups",
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Create a timestamped archive of the current project.
|
|
29
|
+
|
|
30
|
+
The archive contains the database dump (custom format), the filestore
|
|
31
|
+
volume, ``config/`` and ``youca-setup.yaml``.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
database: Database to dump.
|
|
35
|
+
output_dir: Destination directory for the archive.
|
|
36
|
+
"""
|
|
37
|
+
config = load_project()
|
|
38
|
+
manager = BackupManager(config)
|
|
39
|
+
try:
|
|
40
|
+
path = manager.create_backup(
|
|
41
|
+
database=database,
|
|
42
|
+
output_dir=Path(output_dir),
|
|
43
|
+
)
|
|
44
|
+
except (FileNotFoundError, RuntimeError) as exc:
|
|
45
|
+
console.print(f"[bold red]Backup failed:[/] {exc}")
|
|
46
|
+
raise typer.Exit(code=1) from None
|
|
47
|
+
console.print(f"[green]Backup created:[/] {path}")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""The ``doctor`` command - diagnose the development environment.
|
|
2
|
+
|
|
3
|
+
Runs :mod:`youca_setup.diagnostics.checks` and, when asked, applies fixes from
|
|
4
|
+
:mod:`youca_setup.diagnostics.fixer`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
from youca_setup.diagnostics.checks import run_checks
|
|
16
|
+
from youca_setup.diagnostics.fixer import Fixer, propose_fixes
|
|
17
|
+
from youca_setup.project.config import ProjectConfig
|
|
18
|
+
from youca_setup.project.loader import load_project
|
|
19
|
+
|
|
20
|
+
app = typer.Typer(help="Check the environment for common issues.")
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _render(results: list[tuple[str, bool, str]]) -> None:
|
|
25
|
+
"""Render check results as a Rich table.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
results: ``(name, passed, message)`` rows.
|
|
29
|
+
"""
|
|
30
|
+
table = Table(show_header=True, header_style="bold")
|
|
31
|
+
table.add_column("Check")
|
|
32
|
+
table.add_column("Status")
|
|
33
|
+
table.add_column("Detail")
|
|
34
|
+
for name, passed, message in results:
|
|
35
|
+
status = "[green]OK[/]" if passed else "[bold red]FAIL[/]"
|
|
36
|
+
table.add_row(name, status, message)
|
|
37
|
+
console.print(table)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command()
|
|
41
|
+
def doctor(
|
|
42
|
+
fix: Annotated[
|
|
43
|
+
bool,
|
|
44
|
+
typer.Option(
|
|
45
|
+
"--fix",
|
|
46
|
+
"-f",
|
|
47
|
+
help="Attempt to fix detected issues (destructive actions ask for confirmation).",
|
|
48
|
+
),
|
|
49
|
+
] = False,
|
|
50
|
+
check: Annotated[
|
|
51
|
+
str | None,
|
|
52
|
+
typer.Option(
|
|
53
|
+
"--check", help="Run only the given check (e.g. 'docker', 'postgres', 'ports')."
|
|
54
|
+
),
|
|
55
|
+
] = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Diagnose Python, Docker, PostgreSQL, ports and project configuration.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
fix: Whether issues should be auto-fixed where possible.
|
|
61
|
+
check: Restrict the run to a single named check.
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
config: ProjectConfig | None = load_project()
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
config = None
|
|
67
|
+
|
|
68
|
+
results = run_checks(config)
|
|
69
|
+
if check is not None:
|
|
70
|
+
results = [result for result in results if result.name == check]
|
|
71
|
+
|
|
72
|
+
_render([(result.name, result.passed, result.message) for result in results])
|
|
73
|
+
|
|
74
|
+
if not fix:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
fixes = propose_fixes(config, results)
|
|
78
|
+
if not fixes:
|
|
79
|
+
console.print("[green]Nothing to fix.[/]")
|
|
80
|
+
return
|
|
81
|
+
Fixer().run(fixes)
|
|
82
|
+
console.print("[green]Done. Re-run 'youca-setup doctor' to confirm.[/]")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""The ``export`` command - export the project environment as a portable archive.
|
|
2
|
+
|
|
3
|
+
Creates a self-contained ``.tar.gz`` with the configuration files and addons,
|
|
4
|
+
omitting the database dump, filestore and backups.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
|
|
16
|
+
from youca_setup.project.loader import load_project
|
|
17
|
+
from youca_setup.project.transport import export_environment
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="Export the current project environment.")
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command()
|
|
24
|
+
def export(
|
|
25
|
+
output: Annotated[
|
|
26
|
+
Path | None,
|
|
27
|
+
typer.Option("--output", "-o", help="Output archive path."),
|
|
28
|
+
] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Export the current project environment to a portable ``.tar.gz`` file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
output: Destination path for the archive; defaults to ``<name>.tar.gz``
|
|
34
|
+
in the current directory.
|
|
35
|
+
"""
|
|
36
|
+
config = load_project()
|
|
37
|
+
archive = output or Path.cwd() / f"{config.name}-env.tar.gz"
|
|
38
|
+
try:
|
|
39
|
+
path = export_environment(config, archive)
|
|
40
|
+
except FileNotFoundError as exc:
|
|
41
|
+
console.print(f"[bold red]Error:[/] {exc}")
|
|
42
|
+
raise typer.Exit(1) from exc
|
|
43
|
+
console.print(Panel(f"[bold green]Environment exported to {path}.[/]"))
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""The ``import`` command - import a portable environment archive.
|
|
2
|
+
|
|
3
|
+
Extracts the archive into a new project directory, registers the project and
|
|
4
|
+
prints the next steps.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
|
|
16
|
+
from youca_setup.project import registry
|
|
17
|
+
from youca_setup.project.transport import import_environment
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="Import a Youca Setup environment archive.")
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command(name="import")
|
|
24
|
+
def import_env(
|
|
25
|
+
archive: Annotated[Path, typer.Argument(help="Path to the ``.tar.gz`` environment archive.")],
|
|
26
|
+
directory: Annotated[
|
|
27
|
+
Path | None,
|
|
28
|
+
typer.Option("--directory", "-d", help="Parent directory for the new project."),
|
|
29
|
+
] = None,
|
|
30
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation prompts.")] = False,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Import a Youca Setup environment archive into a new project directory.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
archive: Path to the ``.tar.gz`` archive.
|
|
36
|
+
directory: Parent directory for the project; defaults to cwd.
|
|
37
|
+
yes: Skip the confirmation prompt.
|
|
38
|
+
"""
|
|
39
|
+
target_base = directory or Path.cwd()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
project_dir, config = import_environment(archive, target_base)
|
|
43
|
+
except FileNotFoundError as exc:
|
|
44
|
+
console.print(f"[bold red]Error:[/] {exc}")
|
|
45
|
+
raise typer.Exit(1) from exc
|
|
46
|
+
except FileExistsError as exc:
|
|
47
|
+
console.print(f"[bold red]Error:[/] {exc}")
|
|
48
|
+
raise typer.Exit(1) from exc
|
|
49
|
+
except ValueError as exc:
|
|
50
|
+
console.print(f"[bold red]Error:[/] {exc}")
|
|
51
|
+
raise typer.Exit(1) from exc
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
registry.register(config, project_dir)
|
|
55
|
+
except OSError as exc:
|
|
56
|
+
console.print(f"[yellow]Could not register the project: {exc}[/]")
|
|
57
|
+
|
|
58
|
+
console.print(Panel(f"[bold green]Project '{config.name}' imported into {project_dir}.[/]"))
|
|
59
|
+
console.print("Next steps:\n")
|
|
60
|
+
console.print(f" [bold]cd {project_dir}[/]")
|
|
61
|
+
console.print(" [bold]youca-setup start[/]")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""The ``info`` command - show details of the current project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from youca_setup.commands._state import project_state
|
|
10
|
+
from youca_setup.project.loader import load_project
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Show details of the current project.")
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("info")
|
|
17
|
+
def info() -> None:
|
|
18
|
+
"""Print the configuration and state of the current project.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
FileNotFoundError: When run outside a Youca Setup project.
|
|
22
|
+
"""
|
|
23
|
+
config = load_project()
|
|
24
|
+
|
|
25
|
+
table = Table(title=f"Youca Setup for Odoo — {config.name}", show_header=False)
|
|
26
|
+
table.add_column("Setting", style="cyan")
|
|
27
|
+
table.add_column("Value")
|
|
28
|
+
|
|
29
|
+
rows = [
|
|
30
|
+
("Project name", config.name),
|
|
31
|
+
("Odoo version", config.odoo_version),
|
|
32
|
+
("PostgreSQL version", config.postgres_version),
|
|
33
|
+
("PostgreSQL backend", config.postgres_backend),
|
|
34
|
+
("Docker Compose", "yes" if config.docker else "no"),
|
|
35
|
+
("HTTP port", str(config.http_port)),
|
|
36
|
+
("Addons", ", ".join(config.addons) or "—"),
|
|
37
|
+
("State", project_state(config)),
|
|
38
|
+
]
|
|
39
|
+
for setting, value in rows:
|
|
40
|
+
table.add_row(setting, value)
|
|
41
|
+
|
|
42
|
+
console.print(table)
|