cave-cli 3.5.4__tar.gz → 3.6.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 (54) hide show
  1. {cave_cli-3.5.4/cave_cli.egg-info → cave_cli-3.6.0}/PKG-INFO +29 -16
  2. {cave_cli-3.5.4 → cave_cli-3.6.0}/README.md +19 -15
  3. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/cli.py +29 -3
  4. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/create.py +6 -1
  5. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/doctor.py +17 -10
  6. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/list_cmd.py +9 -2
  7. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/list_versions.py +9 -8
  8. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/prettify.py +1 -1
  9. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/purge.py +13 -5
  10. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/reset.py +4 -2
  11. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/run.py +67 -8
  12. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/sync_cmd.py +14 -8
  13. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/test.py +1 -1
  14. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/uninstall.py +7 -8
  15. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/update.py +21 -15
  16. cave_cli-3.6.0/cave_cli/commands/upgrade.py +352 -0
  17. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/version.py +24 -14
  18. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/cache.py +3 -9
  19. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/display.py +188 -49
  20. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/docker.py +76 -7
  21. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/env.py +1 -2
  22. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/logger.py +2 -0
  23. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/subprocess.py +1 -21
  24. cave_cli-3.6.0/cave_cli/utils/sync.py +135 -0
  25. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/validate.py +60 -13
  26. cave_cli-3.6.0/cave_cli/utils/version.py +138 -0
  27. {cave_cli-3.5.4 → cave_cli-3.6.0/cave_cli.egg-info}/PKG-INFO +29 -16
  28. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli.egg-info/SOURCES.txt +10 -2
  29. cave_cli-3.6.0/cave_cli.egg-info/requires.txt +10 -0
  30. {cave_cli-3.5.4 → cave_cli-3.6.0}/pyproject.toml +16 -1
  31. cave_cli-3.6.0/setup.cfg +4 -0
  32. cave_cli-3.6.0/test/test_display.py +116 -0
  33. cave_cli-3.6.0/test/test_env.py +118 -0
  34. cave_cli-3.6.0/test/test_net.py +33 -0
  35. cave_cli-3.6.0/test/test_sync.py +123 -0
  36. cave_cli-3.6.0/test/test_upgrade.py +66 -0
  37. cave_cli-3.6.0/test/test_validate.py +115 -0
  38. cave_cli-3.6.0/utils/prettify.py +25 -0
  39. cave_cli-3.5.4/cave_cli/commands/upgrade.py +0 -75
  40. cave_cli-3.5.4/cave_cli/utils/sync.py +0 -89
  41. cave_cli-3.5.4/setup.cfg +0 -10
  42. {cave_cli-3.5.4 → cave_cli-3.6.0}/LICENSE +0 -0
  43. {cave_cli-3.5.4 → cave_cli-3.6.0}/NOTICE.md +0 -0
  44. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/__init__.py +0 -0
  45. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/__init__.py +0 -0
  46. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/kill.py +0 -0
  47. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/commands/theme.py +0 -0
  48. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/__init__.py +0 -0
  49. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/constants.py +0 -0
  50. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/git.py +0 -0
  51. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli/utils/net.py +0 -0
  52. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli.egg-info/dependency_links.txt +0 -0
  53. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli.egg-info/entry_points.txt +0 -0
  54. {cave_cli-3.5.4 → cave_cli-3.6.0}/cave_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cave_cli
3
- Version: 3.5.4
3
+ Version: 3.6.0
4
4
  Summary: CLI for creating and managing Docker-based CAVE web applications.
5
5
  Author-email: MIT-CAVE <cave-contact@mit.edu>
6
6
  License: Apache-2.0
@@ -13,6 +13,15 @@ Requires-Python: >=3.11
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  License-File: NOTICE.md
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
18
+ Requires-Dist: autoflake>=2.3.1; extra == "dev"
19
+ Requires-Dist: black>=25.1.0; extra == "dev"
20
+ Requires-Dist: build>=1.4.0; extra == "dev"
21
+ Requires-Dist: nox>=2026.4.10; extra == "dev"
22
+ Requires-Dist: nox-uv>=0.8.0; extra == "dev"
23
+ Requires-Dist: pdoc>=15.0.1; extra == "dev"
24
+ Requires-Dist: twine>=6.2.0; extra == "dev"
16
25
  Dynamic: license-file
17
26
 
18
27
  # CAVE CLI
@@ -56,38 +65,42 @@ docker run hello-world
56
65
 
57
66
  ## Installation
58
67
 
59
- Install with [pipx](https://pipx.pypa.io/) (recommended keeps the CLI isolated from your system Python):
68
+ Install with [uv](https://docs.astral.sh/uv/) (required to run cave update and cave uninstall):
60
69
 
61
70
  ```sh
62
- pipx install cave_cli
71
+ uv tool install cave_cli
63
72
  ```
64
73
 
65
- If you don't have pipx, install it first:
74
+ If you don't have uv, install it first:
66
75
 
67
76
  <details>
68
77
  <summary>macOS</summary>
69
78
 
70
79
  ```sh
71
80
  # With Homebrew (recommended)
72
- brew install pipx
73
- pipx ensurepath
81
+ brew install uv
74
82
 
75
- # Or with pip
76
- pip3 install --user pipx
77
- pipx ensurepath
83
+ # Or with the official installer
84
+ curl -LsSf https://astral.sh/uv/install.sh | sh
78
85
  ```
79
86
 
80
87
  </details>
81
88
  <details>
82
- <summary>Other Linux / Windows</summary>
89
+ <summary>Linux</summary>
83
90
 
84
91
  ```sh
85
- python3 -m pip install --user pipx
86
- pipx ensurepath
92
+ curl -LsSf https://astral.sh/uv/install.sh | sh
93
+ ```
94
+ </details>
95
+ <details>
96
+ <summary>Windows</summary>
97
+
98
+ ```powershell
99
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
87
100
  ```
88
101
  </details>
89
102
 
90
- For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
103
+ For more options see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
91
104
 
92
105
  Verify the installation and check your environment health:
93
106
 
@@ -116,7 +129,7 @@ cave --help
116
129
  |---|---|
117
130
  | `cave create <name>` | Create a new CAVE app from the template repository |
118
131
  | `cave run` | Build Docker image and run the app with a live TUI dashboard |
119
- | `cave doctor` | Check the health of your Docker, Git, and Pipx environment |
132
+ | `cave doctor` | Check the health of your Docker, Git, and uv environment |
120
133
 
121
134
  ### Peripheral Commands
122
135
 
@@ -163,10 +176,10 @@ cave --help
163
176
  cave update
164
177
  ```
165
178
 
166
- Or directly via pipx:
179
+ Or directly via uv:
167
180
 
168
181
  ```sh
169
- pipx upgrade cave_cli
182
+ uv tool upgrade cave_cli
170
183
  ```
171
184
 
172
185
  ## License Notice
@@ -39,38 +39,42 @@ docker run hello-world
39
39
 
40
40
  ## Installation
41
41
 
42
- Install with [pipx](https://pipx.pypa.io/) (recommended keeps the CLI isolated from your system Python):
42
+ Install with [uv](https://docs.astral.sh/uv/) (required to run cave update and cave uninstall):
43
43
 
44
44
  ```sh
45
- pipx install cave_cli
45
+ uv tool install cave_cli
46
46
  ```
47
47
 
48
- If you don't have pipx, install it first:
48
+ If you don't have uv, install it first:
49
49
 
50
50
  <details>
51
51
  <summary>macOS</summary>
52
52
 
53
53
  ```sh
54
54
  # With Homebrew (recommended)
55
- brew install pipx
56
- pipx ensurepath
55
+ brew install uv
57
56
 
58
- # Or with pip
59
- pip3 install --user pipx
60
- pipx ensurepath
57
+ # Or with the official installer
58
+ curl -LsSf https://astral.sh/uv/install.sh | sh
61
59
  ```
62
60
 
63
61
  </details>
64
62
  <details>
65
- <summary>Other Linux / Windows</summary>
63
+ <summary>Linux</summary>
66
64
 
67
65
  ```sh
68
- python3 -m pip install --user pipx
69
- pipx ensurepath
66
+ curl -LsSf https://astral.sh/uv/install.sh | sh
67
+ ```
68
+ </details>
69
+ <details>
70
+ <summary>Windows</summary>
71
+
72
+ ```powershell
73
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
70
74
  ```
71
75
  </details>
72
76
 
73
- For more options see the [pipx installation guide](https://pipx.pypa.io/stable/installation/).
77
+ For more options see the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/).
74
78
 
75
79
  Verify the installation and check your environment health:
76
80
 
@@ -99,7 +103,7 @@ cave --help
99
103
  |---|---|
100
104
  | `cave create <name>` | Create a new CAVE app from the template repository |
101
105
  | `cave run` | Build Docker image and run the app with a live TUI dashboard |
102
- | `cave doctor` | Check the health of your Docker, Git, and Pipx environment |
106
+ | `cave doctor` | Check the health of your Docker, Git, and uv environment |
103
107
 
104
108
  ### Peripheral Commands
105
109
 
@@ -146,10 +150,10 @@ cave --help
146
150
  cave update
147
151
  ```
148
152
 
149
- Or directly via pipx:
153
+ Or directly via uv:
150
154
 
151
155
  ```sh
152
- pipx upgrade cave_cli
156
+ uv tool upgrade cave_cli
153
157
  ```
154
158
 
155
159
  ## License Notice
@@ -38,6 +38,13 @@ def add_global_args(parser: argparse.ArgumentParser) -> None:
38
38
  default=False,
39
39
  help="Enable verbose logging output (shorthand for --loglevel DEBUG)",
40
40
  )
41
+ parser.add_argument(
42
+ "-q",
43
+ "--quiet",
44
+ action="store_true",
45
+ default=False,
46
+ help="Enable quiet mode (suppress most output, shorthand for --loglevel SILENT)",
47
+ )
41
48
  parser.add_argument(
42
49
  "--loglevel",
43
50
  "--ll",
@@ -157,11 +164,16 @@ def main():
157
164
  "reset",
158
165
  aliases=["reset-db"],
159
166
  help=(
160
- "Remove Docker containers and volumes, "
161
- "then rebuild from scratch"
167
+ "Remove Docker containers and volumes, " "then rebuild from scratch"
162
168
  ),
163
169
  )
164
170
  add_global_args(p_reset)
171
+ p_reset.add_argument(
172
+ "--docker-args",
173
+ default="",
174
+ metavar="ARGS",
175
+ help="Additional arguments to pass to docker run",
176
+ )
165
177
 
166
178
  # ------------------------------------------------------------------ #
167
179
  # upgrade #
@@ -233,6 +245,12 @@ def main():
233
245
  help="Run tests in cave_api/tests/",
234
246
  )
235
247
  add_global_args(p_test)
248
+ p_test.add_argument(
249
+ "--docker-args",
250
+ default="",
251
+ metavar="ARGS",
252
+ help="Additional arguments to pass to docker run",
253
+ )
236
254
  p_test.add_argument(
237
255
  "remaining",
238
256
  nargs="*",
@@ -247,6 +265,12 @@ def main():
247
265
  help="Format code with autoflake and black",
248
266
  )
249
267
  add_global_args(p_prettify)
268
+ p_prettify.add_argument(
269
+ "--docker-args",
270
+ default="",
271
+ metavar="ARGS",
272
+ help="Additional arguments to pass to docker run",
273
+ )
250
274
 
251
275
  # ------------------------------------------------------------------ #
252
276
  # list #
@@ -401,7 +425,9 @@ def main():
401
425
  # Configure logging
402
426
  from cave_cli.utils.logger import logger
403
427
 
404
- if args.verbose:
428
+ if getattr(args, "quiet", False):
429
+ logger.set_level("SILENT")
430
+ elif args.verbose:
405
431
  logger.set_level("DEBUG")
406
432
  else:
407
433
  logger.set_level(args.loglevel)
@@ -8,7 +8,12 @@ from pathlib import Path
8
8
  from cave_cli.commands.reset import reset
9
9
  from cave_cli.commands.run import run_cave
10
10
  from cave_cli.utils.constants import HTTPS_URL
11
- from cave_cli.utils.display import print_section, step_done, step_fail, step_start
11
+ from cave_cli.utils.display import (
12
+ print_section,
13
+ step_done,
14
+ step_fail,
15
+ step_start,
16
+ )
12
17
  from cave_cli.utils.docker import build_image, generate_secret_key
13
18
  from cave_cli.utils.env import create_env_interactive
14
19
  from cave_cli.utils.git import add, branch_rename, clone, commit, init
@@ -34,31 +34,38 @@ def check_git() -> tuple[bool, str]:
34
34
  )
35
35
 
36
36
 
37
- def check_pipx() -> tuple[bool, str]:
37
+ def check_uv(return_path: bool = True) -> tuple[bool, str, str | None]:
38
38
  """
39
39
  Usage:
40
40
 
41
- - Validates that pipx is installed
41
+ - Validates that uv is installed
42
42
 
43
43
  Returns:
44
44
 
45
45
  - ``success``: bool
46
46
  - ``message``: str remediation or version
47
+ - ``path``: str|None path to uv executable if found
47
48
  """
48
49
  import shutil
49
50
 
50
- pipx = shutil.which("pipx")
51
- if pipx:
51
+ uv_path = shutil.which("uv")
52
+ if uv_path:
52
53
  try:
53
- result = run([pipx, "--version"])
54
+ result = run([uv_path, "--version"])
54
55
  if result.returncode == 0:
55
- return True, f"pipx version {result.stdout.strip()}"
56
+ output = True, f"uv version {result.stdout.strip()}"
57
+ if return_path:
58
+ return output + (uv_path,)
59
+ return output
56
60
  except Exception:
57
61
  pass
58
- return (
62
+ output = (
59
63
  False,
60
- "pipx is not installed. Please install pipx: https://pipx.pypa.io/",
64
+ "uv is not installed. Please install uv: https://github.com/astral-sh/uv",
61
65
  )
66
+ if return_path:
67
+ return output + (None,)
68
+ return output
62
69
 
63
70
 
64
71
  def check_docker() -> tuple[bool, str]:
@@ -126,7 +133,7 @@ def check_all(exit_on_fail: bool = True) -> dict[str, tuple[bool, str]]:
126
133
  results = {
127
134
  "Docker": check_docker(),
128
135
  "Git": check_git(),
129
- "Pipx": check_pipx(),
136
+ "uv": check_uv(return_path=False),
130
137
  }
131
138
 
132
139
  if exit_on_fail:
@@ -145,7 +152,7 @@ def doctor(args: argparse.Namespace) -> None:
145
152
  """
146
153
  Usage:
147
154
 
148
- - Checks the health of the CAVE environment (Docker, Git, Pipx)
155
+ - Checks the health of the CAVE environment (Docker, Git, uv)
149
156
  """
150
157
  print_section("CAVE Doctor")
151
158
 
@@ -20,9 +20,12 @@ def list_cmd(args: argparse.Namespace) -> None:
20
20
 
21
21
  if show_all:
22
22
  print_section("CAVE App Containers (All)")
23
+ from cave_cli.utils.display import _QUIET
24
+
23
25
  for suffix in ("_django", "_db_host", "_redis_host", "_nginx_host"):
24
26
  for name in get_all_containers(suffix):
25
- print(f" ● {name}")
27
+ if not _QUIET:
28
+ print(f" ● {name}")
26
29
  else:
27
30
  apps = get_running_apps()
28
31
  if not apps:
@@ -40,4 +43,8 @@ def list_cmd(args: argparse.Namespace) -> None:
40
43
  else:
41
44
  port = get_container_host_port(f"{app_name}_django")
42
45
  print_key_value(app_name, f"http://localhost:{port}")
43
- print("")
46
+
47
+ from cave_cli.utils.display import _QUIET
48
+
49
+ if not _QUIET:
50
+ print("")
@@ -14,14 +14,11 @@ from cave_cli.utils.display import (
14
14
  )
15
15
  from cave_cli.utils.git import ls_remote_tags
16
16
  from cave_cli.utils.logger import logger
17
+ from cave_cli.utils.version import version_tuple
17
18
 
18
19
  RECENT_LIMIT = 5
19
20
 
20
21
 
21
- def _version_sort_key(v: str) -> list[int]:
22
- return [int(x) for x in re.findall(r"\d+", v)]
23
-
24
-
25
22
  def list_versions(args: argparse.Namespace) -> None:
26
23
  """
27
24
  Usage:
@@ -51,10 +48,11 @@ def list_versions(args: argparse.Namespace) -> None:
51
48
 
52
49
  all_versions = sorted(
53
50
  (
54
- v for v in set().union(*repo_tags.values())
51
+ v
52
+ for v in set().union(*repo_tags.values())
55
53
  if not pattern or fnmatch.fnmatch(v, pattern)
56
54
  ),
57
- key=_version_sort_key,
55
+ key=version_tuple,
58
56
  reverse=True,
59
57
  )
60
58
 
@@ -64,7 +62,7 @@ def list_versions(args: argparse.Namespace) -> None:
64
62
 
65
63
  by_major: dict[int, list[str]] = defaultdict(list)
66
64
  for v in all_versions:
67
- major = _version_sort_key(v)[0]
65
+ major = version_tuple(v)[0]
68
66
  by_major[major].append(v)
69
67
 
70
68
  repos = list(VALID_REPOS)
@@ -91,7 +89,10 @@ def list_versions(args: argparse.Namespace) -> None:
91
89
  row = indent + f"{v:<{version_w}}"
92
90
  for r, w in zip(repos, col_w):
93
91
  mark = f"{GREEN}✓{RESET}" if v in repo_tags[r] else ""
94
- row += " " * pad + f"{mark:^{w + len(GREEN) + len(RESET) if mark else w}}"
92
+ row += (
93
+ " " * pad
94
+ + f"{mark:^{w + len(GREEN) + len(RESET) if mark else w}}"
95
+ )
95
96
  print(row)
96
97
 
97
98
  if hidden:
@@ -18,7 +18,7 @@ def prettify(args: argparse.Namespace) -> None:
18
18
  entrypoint="./utils/prettify.sh",
19
19
  interactive=False,
20
20
  it=False,
21
- docker_args="",
21
+ docker_args=getattr(args, "docker_args", "") or "",
22
22
  ip_port=None,
23
23
  yes=True,
24
24
  verbose=getattr(args, "verbose", False),
@@ -64,11 +64,19 @@ def purge(args: argparse.Namespace) -> None:
64
64
  "Some files are owned by root (created by Docker). "
65
65
  "Removing via Docker..."
66
66
  )
67
- run_and_log([
68
- "docker", "run", "--rm",
69
- "-v", f"{abs_path}:/purge",
70
- "alpine", "rm", "-rf", "/purge",
71
- ])
67
+ run_and_log(
68
+ [
69
+ "docker",
70
+ "run",
71
+ "--rm",
72
+ "-v",
73
+ f"{abs_path}:/purge",
74
+ "alpine",
75
+ "rm",
76
+ "-rf",
77
+ "/purge",
78
+ ]
79
+ )
72
80
  if os.path.isdir(abs_path):
73
81
  try:
74
82
  shutil.rmtree(abs_path)
@@ -40,11 +40,13 @@ def reset(
40
40
  entrypoint="./utils/reset_db.sh",
41
41
  interactive=False,
42
42
  it=False,
43
- docker_args="",
43
+ docker_args=getattr(args, "docker_args", "") or "",
44
44
  ip_port=None,
45
45
  yes=True,
46
46
  verbose=getattr(args, "verbose", False),
47
47
  loglevel=getattr(args, "loglevel", "INFO"),
48
48
  )
49
- run_cave(app_dir, app_name, reset_args, skip_header=True, skip_build=skip_build)
49
+ run_cave(
50
+ app_dir, app_name, reset_args, skip_header=True, skip_build=skip_build
51
+ )
50
52
  step_done("Reset complete")
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import shlex
2
3
  import signal
3
4
  import sys
4
5
  import threading
@@ -8,12 +9,14 @@ from cave_cli.utils.display import (
8
9
  RunDashboard,
9
10
  print_section,
10
11
  step_done,
12
+ step_fail,
11
13
  step_start,
12
14
  )
13
15
  from cave_cli.utils.docker import (
14
16
  build_image,
15
17
  create_network,
16
18
  remove_containers,
19
+ run_and_log,
17
20
  run_detached,
18
21
  run_detached_logged,
19
22
  run_interactive,
@@ -46,13 +49,21 @@ def run_cave(
46
49
  interactive = getattr(args, "interactive", False) or getattr(
47
50
  args, "it", False
48
51
  )
49
- show_all = getattr(args, "show_all", False) or getattr(args, "verbose", False) or getattr(args, "loglevel", "INFO").upper() == "DEBUG"
52
+ show_all = (
53
+ getattr(args, "show_all", False)
54
+ or getattr(args, "verbose", False)
55
+ or getattr(args, "loglevel", "INFO").upper() == "DEBUG"
56
+ )
50
57
  entrypoint = getattr(args, "entrypoint", None) or "./utils/run_server.sh"
51
58
  docker_args_str = getattr(args, "docker_args", "") or ""
52
- extra_docker_args = docker_args_str.split() if docker_args_str else []
59
+ extra_docker_args = shlex.split(docker_args_str) if docker_args_str else []
53
60
  ip_port_arg = getattr(args, "ip_port", None)
54
61
  command_args = getattr(args, "command_args", []) or []
55
62
  extra_env = getattr(args, "extra_env", {}) or {}
63
+ quiet = (
64
+ getattr(args, "quiet", False)
65
+ or getattr(args, "loglevel", "INFO").upper() == "SILENT"
66
+ )
56
67
 
57
68
  is_server_run = entrypoint == "./utils/run_server.sh" and not interactive
58
69
  use_tui = is_server_run and not show_all
@@ -136,15 +147,60 @@ def run_cave(
136
147
 
137
148
  django_volumes = [f"{app_dir}:/app"]
138
149
 
150
+ # Check if database needs initialization
151
+ if is_server_run and "postgres" in db_image.lower():
152
+ from cave_cli.utils.docker import is_container_ready, is_db_initialized
153
+ from cave_cli.utils.validate import confirm_action
154
+
155
+ db_container = f"{app_name}_db_host"
156
+ db_user = f"{app_name}_user"
157
+ db_name = f"{app_name}_name"
158
+
159
+ step_start("Checking database")
160
+ import time
161
+
162
+ ready = False
163
+ for _ in range(30):
164
+ if is_container_ready(db_container, db_user):
165
+ ready = True
166
+ break
167
+ time.sleep(1)
168
+
169
+ if ready:
170
+ if not is_db_initialized(db_container, db_user, db_name):
171
+ step_done("Checking database")
172
+ confirm_action(
173
+ "The database for this app is not set up. "
174
+ "Would you like to set it up now?"
175
+ )
176
+ step_start("Initializing database")
177
+ run_interactive(
178
+ name=f"{app_name}_django_setup",
179
+ image=f"cave-app:{app_name}",
180
+ network=network,
181
+ volumes=django_volumes,
182
+ env_vars=django_env,
183
+ command=["./utils/reset_db.sh"],
184
+ )
185
+ run_and_log(["docker", "rm", f"{app_name}_django_setup"])
186
+ step_done("Initializing database")
187
+ else:
188
+ step_done("Checking database")
189
+ else:
190
+ step_fail(
191
+ "Checking database",
192
+ "Database container failed to become ready.",
193
+ )
194
+ remove_containers(app_name)
195
+ sys.exit(1)
196
+
139
197
  parsed = parse_ip_port(ip_port_arg) if ip_port_arg else None
140
198
  django_container = f"{app_name}_django"
141
199
 
142
200
  if parsed:
143
201
  ip, port = parsed
144
202
  if not is_port_available(port):
145
- logger.error(
146
- "The specified port is in use. Please try another."
147
- )
203
+ logger.error("The specified port is in use. Please try another.")
148
204
  sys.exit(1)
149
205
 
150
206
  if is_server_run and not interactive:
@@ -177,7 +233,7 @@ def run_cave(
177
233
  url = f"https://{ip}:{port}"
178
234
 
179
235
  if interactive:
180
- logger.header("CAVE App: (Interactive)")
236
+ logger.header("CAVE App (Interactive)")
181
237
  run_interactive(
182
238
  name=django_container,
183
239
  image=f"cave-app:{app_name}",
@@ -189,8 +245,8 @@ def run_cave(
189
245
  command=server_command,
190
246
  )
191
247
  remove_containers(app_name)
192
-
193
248
  elif use_tui:
249
+ logger.header("CAVE App (TUI)")
194
250
  _run_tui(
195
251
  app_name=app_name,
196
252
  django_container=django_container,
@@ -205,6 +261,7 @@ def run_cave(
205
261
  )
206
262
 
207
263
  else:
264
+ logger.header("CAVE App (CLI)")
208
265
  if is_server_run:
209
266
  logger.info(
210
267
  f"Your Cave App can be accessed from Chrome at:\n{url}"
@@ -226,7 +283,7 @@ def run_cave(
226
283
  url = f"http://localhost:{port}"
227
284
 
228
285
  if interactive:
229
- logger.header("CAVE App: (Interactive)")
286
+ logger.header("CAVE App (Interactive)")
230
287
  run_interactive(
231
288
  name=django_container,
232
289
  image=f"cave-app:{app_name}",
@@ -240,6 +297,7 @@ def run_cave(
240
297
  remove_containers(app_name)
241
298
 
242
299
  elif use_tui:
300
+ logger.header("CAVE App (TUI)")
243
301
  _run_tui(
244
302
  app_name=app_name,
245
303
  django_container=django_container,
@@ -254,6 +312,7 @@ def run_cave(
254
312
  )
255
313
 
256
314
  else:
315
+ logger.header("CAVE App (CLI)")
257
316
  if is_server_run:
258
317
  logger.info(
259
318
  f"Your Cave App can be accessed from Chrome at:\n{url}"
@@ -5,14 +5,19 @@ import tempfile
5
5
 
6
6
  from cave_cli.commands.reset import reset
7
7
  from cave_cli.utils.constants import HTTPS_URL
8
- from cave_cli.utils.display import print_section, step_done, step_start, print_key_value
8
+ from cave_cli.utils.display import (
9
+ print_section,
10
+ step_done,
11
+ step_start,
12
+ print_key_value,
13
+ )
9
14
  from cave_cli.utils.git import clone
10
15
  from cave_cli.utils.logger import logger
11
16
  from cave_cli.utils.sync import sync_files
12
17
  from cave_cli.utils.validate import confirm_action, find_app_dir
13
18
 
14
19
 
15
- def sync_cmd(args: argparse.Namespace) -> None:
20
+ def sync_cmd(args: argparse.Namespace, do_reset: bool = True) -> None:
16
21
  """
17
22
  Usage:
18
23
 
@@ -69,11 +74,12 @@ def sync_cmd(args: argparse.Namespace) -> None:
69
74
 
70
75
  shutil.rmtree(temp_dir, ignore_errors=True)
71
76
 
72
- reset_args = argparse.Namespace(
73
- yes=True,
74
- verbose=getattr(args, "verbose", False),
75
- loglevel=getattr(args, "loglevel", "INFO"),
76
- )
77
- reset(reset_args)
77
+ if do_reset:
78
+ reset_args = argparse.Namespace(
79
+ yes=True,
80
+ verbose=getattr(args, "verbose", False),
81
+ loglevel=getattr(args, "loglevel", "INFO"),
82
+ )
83
+ reset(reset_args)
78
84
 
79
85
  logger.success("Sync complete.")
@@ -30,7 +30,7 @@ def test(args: argparse.Namespace) -> None:
30
30
  extra_env=extra_env,
31
31
  interactive=False,
32
32
  it=False,
33
- docker_args="",
33
+ docker_args=getattr(args, "docker_args", "") or "",
34
34
  ip_port=None,
35
35
  yes=True,
36
36
  verbose=getattr(args, "verbose", False),