cave-cli 3.5.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.
- cave_cli/__init__.py +3 -0
- cave_cli/cli.py +469 -0
- cave_cli/commands/__init__.py +0 -0
- cave_cli/commands/create.py +168 -0
- cave_cli/commands/kill.py +23 -0
- cave_cli/commands/list_cmd.py +37 -0
- cave_cli/commands/list_versions.py +93 -0
- cave_cli/commands/prettify.py +27 -0
- cave_cli/commands/purge.py +70 -0
- cave_cli/commands/reset.py +43 -0
- cave_cli/commands/run.py +189 -0
- cave_cli/commands/sync_cmd.py +79 -0
- cave_cli/commands/test.py +38 -0
- cave_cli/commands/uninstall.py +39 -0
- cave_cli/commands/update.py +38 -0
- cave_cli/commands/upgrade.py +70 -0
- cave_cli/commands/version.py +64 -0
- cave_cli/utils/__init__.py +0 -0
- cave_cli/utils/cache.py +235 -0
- cave_cli/utils/constants.py +43 -0
- cave_cli/utils/docker.py +530 -0
- cave_cli/utils/env.py +267 -0
- cave_cli/utils/git.py +240 -0
- cave_cli/utils/logger.py +77 -0
- cave_cli/utils/net.py +85 -0
- cave_cli/utils/subprocess.py +129 -0
- cave_cli/utils/sync.py +89 -0
- cave_cli/utils/validate.py +218 -0
- cave_cli-3.5.0.dist-info/METADATA +149 -0
- cave_cli-3.5.0.dist-info/RECORD +35 -0
- cave_cli-3.5.0.dist-info/WHEEL +5 -0
- cave_cli-3.5.0.dist-info/entry_points.txt +2 -0
- cave_cli-3.5.0.dist-info/licenses/LICENSE +201 -0
- cave_cli-3.5.0.dist-info/licenses/NOTICE.md +7 -0
- cave_cli-3.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from cave_cli.utils.logger import logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run(
|
|
8
|
+
args: list[str],
|
|
9
|
+
capture: bool = True,
|
|
10
|
+
check: bool = False,
|
|
11
|
+
cwd: str | None = None,
|
|
12
|
+
inherit_io: bool = False,
|
|
13
|
+
) -> subprocess.CompletedProcess:
|
|
14
|
+
"""
|
|
15
|
+
Usage:
|
|
16
|
+
|
|
17
|
+
- Runs a subprocess command with standardized handling
|
|
18
|
+
|
|
19
|
+
Requires:
|
|
20
|
+
|
|
21
|
+
- ``args``:
|
|
22
|
+
- Type: list[str]
|
|
23
|
+
- What: The command and arguments to run
|
|
24
|
+
|
|
25
|
+
Optional:
|
|
26
|
+
|
|
27
|
+
- ``capture``:
|
|
28
|
+
- Type: bool
|
|
29
|
+
- What: Whether to capture stdout/stderr
|
|
30
|
+
- Default: True
|
|
31
|
+
|
|
32
|
+
- ``check``:
|
|
33
|
+
- Type: bool
|
|
34
|
+
- What: Whether to raise on non-zero exit
|
|
35
|
+
- Default: False
|
|
36
|
+
|
|
37
|
+
- ``cwd``:
|
|
38
|
+
- Type: str | None
|
|
39
|
+
- What: Working directory for the command
|
|
40
|
+
- Default: None
|
|
41
|
+
|
|
42
|
+
- ``inherit_io``:
|
|
43
|
+
- Type: bool
|
|
44
|
+
- What: Whether to pass through stdin/stdout/stderr for
|
|
45
|
+
interactive processes
|
|
46
|
+
- Default: False
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
|
|
50
|
+
- ``result``:
|
|
51
|
+
- Type: subprocess.CompletedProcess
|
|
52
|
+
- What: The completed process result
|
|
53
|
+
"""
|
|
54
|
+
kwargs: dict = {"cwd": cwd}
|
|
55
|
+
if inherit_io:
|
|
56
|
+
kwargs["stdin"] = sys.stdin
|
|
57
|
+
kwargs["stdout"] = sys.stdout
|
|
58
|
+
kwargs["stderr"] = sys.stderr
|
|
59
|
+
elif capture:
|
|
60
|
+
kwargs["stdout"] = subprocess.PIPE
|
|
61
|
+
kwargs["stderr"] = subprocess.PIPE
|
|
62
|
+
kwargs["text"] = True
|
|
63
|
+
return subprocess.run(args, check=check, **kwargs)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_and_log(
|
|
67
|
+
args: list[str],
|
|
68
|
+
level: str = "DEBUG",
|
|
69
|
+
cwd: str | None = None,
|
|
70
|
+
) -> subprocess.CompletedProcess:
|
|
71
|
+
"""
|
|
72
|
+
Usage:
|
|
73
|
+
|
|
74
|
+
- Runs a subprocess and pipes each output line through the logger
|
|
75
|
+
|
|
76
|
+
Requires:
|
|
77
|
+
|
|
78
|
+
- ``args``:
|
|
79
|
+
- Type: list[str]
|
|
80
|
+
- What: The command and arguments to run
|
|
81
|
+
|
|
82
|
+
Optional:
|
|
83
|
+
|
|
84
|
+
- ``level``:
|
|
85
|
+
- Type: str
|
|
86
|
+
- What: Log level for output lines
|
|
87
|
+
- Default: "DEBUG"
|
|
88
|
+
|
|
89
|
+
- ``cwd``:
|
|
90
|
+
- Type: str | None
|
|
91
|
+
- What: Working directory for the command
|
|
92
|
+
- Default: None
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
|
|
96
|
+
- ``result``:
|
|
97
|
+
- Type: subprocess.CompletedProcess
|
|
98
|
+
- What: The completed process result
|
|
99
|
+
"""
|
|
100
|
+
result = run(args, cwd=cwd, capture=True)
|
|
101
|
+
log_fn = getattr(logger, level.lower(), logger.debug)
|
|
102
|
+
if result.stdout:
|
|
103
|
+
for line in result.stdout.strip().splitlines():
|
|
104
|
+
log_fn(line)
|
|
105
|
+
if result.stderr:
|
|
106
|
+
for line in result.stderr.strip().splitlines():
|
|
107
|
+
log_fn(line)
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def version_tuple(v: str) -> tuple[int, ...]:
|
|
112
|
+
"""
|
|
113
|
+
Usage:
|
|
114
|
+
|
|
115
|
+
- Parses a version string into a comparable tuple of integers
|
|
116
|
+
|
|
117
|
+
Requires:
|
|
118
|
+
|
|
119
|
+
- ``v``:
|
|
120
|
+
- Type: str
|
|
121
|
+
- What: A version string like "23.0.6"
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
|
|
125
|
+
- ``parts``:
|
|
126
|
+
- Type: tuple[int, ...]
|
|
127
|
+
- What: A tuple of integers for comparison
|
|
128
|
+
"""
|
|
129
|
+
return tuple(int(x) for x in v.split(".") if x.isdigit())
|
cave_cli/utils/sync.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from cave_cli.utils.logger import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sync_files(
|
|
10
|
+
source: str,
|
|
11
|
+
dest: str,
|
|
12
|
+
includes: list[str] | None = None,
|
|
13
|
+
excludes: list[str] | None = None,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Usage:
|
|
17
|
+
|
|
18
|
+
- Syncs files from a source directory to a destination directory
|
|
19
|
+
|
|
20
|
+
Requires:
|
|
21
|
+
|
|
22
|
+
- ``source``:
|
|
23
|
+
- Type: str
|
|
24
|
+
- What: The source directory to copy from
|
|
25
|
+
|
|
26
|
+
- ``dest``:
|
|
27
|
+
- Type: str
|
|
28
|
+
- What: The destination directory to copy to
|
|
29
|
+
|
|
30
|
+
Optional:
|
|
31
|
+
|
|
32
|
+
- ``includes``:
|
|
33
|
+
- Type: list[str] | None
|
|
34
|
+
- What: Patterns for files that should always be included
|
|
35
|
+
(overrides excludes)
|
|
36
|
+
- Default: None
|
|
37
|
+
|
|
38
|
+
- ``excludes``:
|
|
39
|
+
- Type: list[str] | None
|
|
40
|
+
- What: Patterns for files that should be excluded
|
|
41
|
+
- Default: None
|
|
42
|
+
|
|
43
|
+
Notes:
|
|
44
|
+
|
|
45
|
+
- Always excludes ``.git``
|
|
46
|
+
- Include patterns override exclude patterns (matching rsync semantics)
|
|
47
|
+
- Uses ``shutil.copytree`` with ``dirs_exist_ok=True`` for merge behavior
|
|
48
|
+
"""
|
|
49
|
+
clean_includes = [strip_quotes(p) for p in (includes or [])]
|
|
50
|
+
clean_excludes = [strip_quotes(p) for p in (excludes or [])]
|
|
51
|
+
clean_excludes.append(".git")
|
|
52
|
+
|
|
53
|
+
def ignore_fn(directory: str, contents: list[str]) -> set[str]:
|
|
54
|
+
rel_dir = os.path.relpath(directory, source)
|
|
55
|
+
ignored: set[str] = set()
|
|
56
|
+
for name in contents:
|
|
57
|
+
if rel_dir == ".":
|
|
58
|
+
rel_path = name
|
|
59
|
+
else:
|
|
60
|
+
rel_path = os.path.join(rel_dir, name)
|
|
61
|
+
if matches_any(rel_path, name, clean_includes):
|
|
62
|
+
continue
|
|
63
|
+
if matches_any(rel_path, name, clean_excludes):
|
|
64
|
+
ignored.add(name)
|
|
65
|
+
return ignored
|
|
66
|
+
|
|
67
|
+
shutil.copytree(
|
|
68
|
+
source, dest, ignore=ignore_fn, dirs_exist_ok=True
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def strip_quotes(pattern: str) -> str:
|
|
73
|
+
pattern = pattern.strip()
|
|
74
|
+
if (pattern.startswith("'") and pattern.endswith("'")) or (
|
|
75
|
+
pattern.startswith('"') and pattern.endswith('"')
|
|
76
|
+
):
|
|
77
|
+
return pattern[1:-1]
|
|
78
|
+
return pattern
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def matches_any(
|
|
82
|
+
rel_path: str, name: str, patterns: list[str]
|
|
83
|
+
) -> bool:
|
|
84
|
+
for pattern in patterns:
|
|
85
|
+
if fnmatch.fnmatch(name, pattern):
|
|
86
|
+
return True
|
|
87
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from cave_cli.utils.constants import (
|
|
6
|
+
CHAR_LINE,
|
|
7
|
+
CURRENT_ENV_VARIABLES,
|
|
8
|
+
RETIRED_ENV_VARIABLES,
|
|
9
|
+
INVALID_NAME_END_RE,
|
|
10
|
+
INVALID_NAME_HYPHEN_UNDER_RE,
|
|
11
|
+
INVALID_NAME_START_RE,
|
|
12
|
+
INVALID_NAME_UNDER_HYPHEN_RE,
|
|
13
|
+
VALID_NAME_RE,
|
|
14
|
+
)
|
|
15
|
+
from cave_cli.utils.logger import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_app_name(name: str) -> str | None:
|
|
19
|
+
"""
|
|
20
|
+
Usage:
|
|
21
|
+
|
|
22
|
+
- Validates a CAVE app name against naming rules
|
|
23
|
+
|
|
24
|
+
Requires:
|
|
25
|
+
|
|
26
|
+
- ``name``:
|
|
27
|
+
- Type: str
|
|
28
|
+
- What: The app name to validate
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
|
|
32
|
+
- ``error``:
|
|
33
|
+
- Type: str | None
|
|
34
|
+
- What: An error message if invalid, or None if valid
|
|
35
|
+
"""
|
|
36
|
+
if len(name) < 2 or len(name) > 255:
|
|
37
|
+
return "The app name needs to be two to 255 characters"
|
|
38
|
+
if not VALID_NAME_RE.match(name):
|
|
39
|
+
return (
|
|
40
|
+
"The app name can only contain lowercase letters, "
|
|
41
|
+
"numbers, hyphens (-), and underscores (_)"
|
|
42
|
+
)
|
|
43
|
+
if INVALID_NAME_START_RE.match(name):
|
|
44
|
+
return (
|
|
45
|
+
"The app name cannot start with a hyphen (-) "
|
|
46
|
+
"or an underscore (_)"
|
|
47
|
+
)
|
|
48
|
+
if INVALID_NAME_END_RE.match(name):
|
|
49
|
+
return (
|
|
50
|
+
"The app name cannot end with a hyphen (-) "
|
|
51
|
+
"or an underscore (_)"
|
|
52
|
+
)
|
|
53
|
+
if INVALID_NAME_HYPHEN_UNDER_RE.search(name):
|
|
54
|
+
return (
|
|
55
|
+
"The app name cannot contain a hyphen (-) "
|
|
56
|
+
"followed by an underscore (_)"
|
|
57
|
+
)
|
|
58
|
+
if INVALID_NAME_UNDER_HYPHEN_RE.search(name):
|
|
59
|
+
return (
|
|
60
|
+
"The app name cannot contain an underscore (_) "
|
|
61
|
+
"followed by a hyphen (-)"
|
|
62
|
+
)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def validate_app_dir(path: str) -> list[str]:
|
|
67
|
+
"""
|
|
68
|
+
Usage:
|
|
69
|
+
|
|
70
|
+
- Checks if a directory is a valid CAVE app directory
|
|
71
|
+
|
|
72
|
+
Requires:
|
|
73
|
+
|
|
74
|
+
- ``path``:
|
|
75
|
+
- Type: str
|
|
76
|
+
- What: The path to validate
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
|
|
80
|
+
- ``errors``:
|
|
81
|
+
- Type: list[str]
|
|
82
|
+
- What: A list of error messages. Empty if the directory is valid.
|
|
83
|
+
"""
|
|
84
|
+
p = Path(path)
|
|
85
|
+
errors: list[str] = []
|
|
86
|
+
|
|
87
|
+
if not (p / "manage.py").is_file() or not (p / "cave_core").is_dir():
|
|
88
|
+
return ["Not a CAVE app directory"]
|
|
89
|
+
|
|
90
|
+
for folder in ("cave_api", "cave_app", "cave_core"):
|
|
91
|
+
if not (p / folder).is_dir():
|
|
92
|
+
errors.append(
|
|
93
|
+
f"The folder '{folder}' is missing "
|
|
94
|
+
"in the root project directory."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
for file in (".env", "manage.py", "requirements.txt", "Dockerfile"):
|
|
98
|
+
if not (p / file).is_file():
|
|
99
|
+
errors.append(
|
|
100
|
+
f"The file '{file}' is missing "
|
|
101
|
+
"in the root project directory."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
env_path = p / ".env"
|
|
105
|
+
if env_path.is_file():
|
|
106
|
+
from cave_cli.utils.env import parse_env
|
|
107
|
+
|
|
108
|
+
env_vars = parse_env(str(env_path))
|
|
109
|
+
for var in CURRENT_ENV_VARIABLES:
|
|
110
|
+
if var not in env_vars:
|
|
111
|
+
errors.append(
|
|
112
|
+
f"The env variable '{var}' is missing "
|
|
113
|
+
"from the '.env' file."
|
|
114
|
+
)
|
|
115
|
+
for var in RETIRED_ENV_VARIABLES:
|
|
116
|
+
if var in env_vars:
|
|
117
|
+
errors.append(
|
|
118
|
+
f"The env variable '{var}' is retired and "
|
|
119
|
+
"should be removed from the '.env' file."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if not (p / "Dockerfile").is_file():
|
|
123
|
+
errors.append("No Dockerfile found in current directory.")
|
|
124
|
+
|
|
125
|
+
return errors
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def find_app_dir(start: str | None = None) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Usage:
|
|
131
|
+
|
|
132
|
+
- Walks up the directory tree to find a valid CAVE app directory
|
|
133
|
+
|
|
134
|
+
Optional:
|
|
135
|
+
|
|
136
|
+
- ``start``:
|
|
137
|
+
- Type: str | None
|
|
138
|
+
- What: The starting directory to search from
|
|
139
|
+
- Default: None (uses current working directory)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
|
|
143
|
+
- ``path``:
|
|
144
|
+
- Type: str
|
|
145
|
+
- What: The absolute path to the CAVE app directory
|
|
146
|
+
"""
|
|
147
|
+
path = Path(start or os.getcwd()).resolve()
|
|
148
|
+
while True:
|
|
149
|
+
errors = validate_app_dir(str(path))
|
|
150
|
+
if not errors:
|
|
151
|
+
return str(path)
|
|
152
|
+
parent = path.parent
|
|
153
|
+
if parent == path:
|
|
154
|
+
for err in errors:
|
|
155
|
+
logger.error(err)
|
|
156
|
+
logger.error("Ensure you are in a valid CAVE app directory")
|
|
157
|
+
sys.exit(1)
|
|
158
|
+
path = parent
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def get_app(start: str | None = None) -> tuple[str, str]:
|
|
162
|
+
"""
|
|
163
|
+
Usage:
|
|
164
|
+
|
|
165
|
+
- Finds the CAVE app directory and returns its path and name
|
|
166
|
+
|
|
167
|
+
Optional:
|
|
168
|
+
|
|
169
|
+
- ``start``:
|
|
170
|
+
- Type: str | None
|
|
171
|
+
- What: The starting directory to search from
|
|
172
|
+
- Default: None (uses current working directory)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
|
|
176
|
+
- ``app_dir``:
|
|
177
|
+
- Type: str
|
|
178
|
+
- What: The absolute path to the CAVE app directory
|
|
179
|
+
|
|
180
|
+
- ``app_name``:
|
|
181
|
+
- Type: str
|
|
182
|
+
- What: The base name of the app directory
|
|
183
|
+
"""
|
|
184
|
+
app_dir = find_app_dir(start)
|
|
185
|
+
app_name = Path(app_dir).resolve().name
|
|
186
|
+
return app_dir, app_name
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def confirm_action(message: str, auto_yes: bool = False) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Usage:
|
|
192
|
+
|
|
193
|
+
- Prompts the user for confirmation before proceeding
|
|
194
|
+
|
|
195
|
+
Requires:
|
|
196
|
+
|
|
197
|
+
- ``message``:
|
|
198
|
+
- Type: str
|
|
199
|
+
- What: The action description to display
|
|
200
|
+
|
|
201
|
+
Optional:
|
|
202
|
+
|
|
203
|
+
- ``auto_yes``:
|
|
204
|
+
- Type: bool
|
|
205
|
+
- What: If True, bypasses the prompt and continues
|
|
206
|
+
- Default: False
|
|
207
|
+
"""
|
|
208
|
+
if auto_yes:
|
|
209
|
+
return
|
|
210
|
+
try:
|
|
211
|
+
response = input(f"{message}. Would you like to continue? [y/N] ")
|
|
212
|
+
except (EOFError, KeyboardInterrupt):
|
|
213
|
+
print()
|
|
214
|
+
logger.error("Operation canceled.")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
if response.strip().lower() not in ("y", "yes"):
|
|
217
|
+
logger.error("Operation canceled.")
|
|
218
|
+
sys.exit(1)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cave_cli
|
|
3
|
+
Version: 3.5.0
|
|
4
|
+
Summary: CLI for creating and managing Docker-based CAVE web applications.
|
|
5
|
+
Author-email: MIT-CAVE <cave-contact@mit.edu>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/MIT-CAVE/cave_cli
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/MIT-CAVE/cave_cli/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
License-File: NOTICE.md
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# CAVE CLI
|
|
19
|
+
|
|
20
|
+
A cross-platform CLI for creating and managing Docker-based CAVE web applications.
|
|
21
|
+
|
|
22
|
+
Developed by [MIT-CAVE](https://cave.mit.edu/) (Center for Transportation & Logistics). Licensed under Apache 2.0.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- [Python](https://www.python.org/downloads/) 3.11+
|
|
27
|
+
- [Docker](https://docs.docker.com/get-docker/) 23.0.6+
|
|
28
|
+
- [Git](https://git-scm.com/)
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>Ubuntu</summary>
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
# Install Docker
|
|
35
|
+
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
36
|
+
sudo sh ./get-docker.sh
|
|
37
|
+
# Add the current user to the docker group
|
|
38
|
+
dockerd-rootless-setuptool.sh install
|
|
39
|
+
# Make sure it works outside of sudo
|
|
40
|
+
docker run hello-world
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</details>
|
|
44
|
+
<details>
|
|
45
|
+
<summary>macOS</summary>
|
|
46
|
+
|
|
47
|
+
- Install Docker Desktop: https://docs.docker.com/docker-for-mac/install/
|
|
48
|
+
|
|
49
|
+
</details>
|
|
50
|
+
<details>
|
|
51
|
+
<summary>Windows</summary>
|
|
52
|
+
|
|
53
|
+
- Install Docker Desktop for WSL: https://docs.docker.com/desktop/wsl/
|
|
54
|
+
- Install WSL2 with Ubuntu: https://learn.microsoft.com/en-us/windows/wsl/install
|
|
55
|
+
- Open your WSL Ubuntu terminal for all `cave` commands
|
|
56
|
+
|
|
57
|
+
</details>
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pip install git+https://github.com/MIT-CAVE/cave_cli.git
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or with [pipx](https://pipx.pypa.io/) (recommended for CLI tools):
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pipx install git+https://github.com/MIT-CAVE/cave_cli.git
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Verify the installation:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
cave --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Quick Start
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
cave create my_app
|
|
81
|
+
cd my_app
|
|
82
|
+
cave run
|
|
83
|
+
# Open http://localhost:8000/ in your browser
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## CLI Commands
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
cave --help
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Core Commands
|
|
93
|
+
|
|
94
|
+
| Command | Description |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `cave create <name>` | Create a new CAVE app from the template repository |
|
|
97
|
+
| `cave run` | Build Docker image and run the app |
|
|
98
|
+
|
|
99
|
+
### Peripheral Commands
|
|
100
|
+
|
|
101
|
+
| Command | Description |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `cave reset` | Remove containers/volumes and rebuild from scratch |
|
|
104
|
+
| `cave upgrade` | Upgrade app files from the upstream template |
|
|
105
|
+
| `cave sync --url <url>` | Merge files from another repository into the app |
|
|
106
|
+
| `cave test` | Run tests in `cave_api/tests/` |
|
|
107
|
+
| `cave prettify` | Format code with autoflake and black |
|
|
108
|
+
| `cave purge <path>` | Remove an app and all its Docker resources |
|
|
109
|
+
|
|
110
|
+
### Utility Commands
|
|
111
|
+
|
|
112
|
+
| Command | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `cave list` | List running CAVE apps |
|
|
115
|
+
| `cave kill` | Stop Docker containers for an app |
|
|
116
|
+
| `cave list-versions` | List available CAVE app versions |
|
|
117
|
+
| `cave update` | Update the CAVE CLI itself |
|
|
118
|
+
| `cave uninstall` | Remove the CAVE CLI |
|
|
119
|
+
| `cave version` | Print version information |
|
|
120
|
+
|
|
121
|
+
### Global Flags
|
|
122
|
+
|
|
123
|
+
| Flag | Description |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `-v`, `--verbose` | Enable verbose (DEBUG) logging output |
|
|
126
|
+
| `--loglevel LEVEL` | Set log level: DEBUG, INFO, WARN, ERROR, SILENT |
|
|
127
|
+
| `-y`, `--yes` | Automatically answer confirmation prompts with yes |
|
|
128
|
+
|
|
129
|
+
## Updating
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
pip install --upgrade git+https://github.com/MIT-CAVE/cave_cli.git
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
cave update
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License Notice
|
|
142
|
+
|
|
143
|
+
Copyright 2023 Massachusetts Institute of Technology (MIT), Center for Transportation & Logistics (CTL)
|
|
144
|
+
|
|
145
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
|
146
|
+
|
|
147
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
148
|
+
|
|
149
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
cave_cli/__init__.py,sha256=87INpPcp1F7_PqWBWVyWIJMFP4m5wqP3zwTouzbVx68,74
|
|
2
|
+
cave_cli/cli.py,sha256=4rBrxNCWfLIZsu-DzxKL-2XWU8UCEXz3x59W1mUKW2w,14106
|
|
3
|
+
cave_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
cave_cli/commands/create.py,sha256=5RrZd8GGQOJiajNlTYnUYl68B40Pu7XTm7p3u8Rvhqo,5192
|
|
5
|
+
cave_cli/commands/kill.py,sha256=WYHL5DojY0hhYiV2mSEpEXsW6z9k2tmcnqp41UuksKE,603
|
|
6
|
+
cave_cli/commands/list_cmd.py,sha256=fpeDAwhejC5ipwl7mxNQWbAr18YqF8ZjBXps9Ct8yjw,1236
|
|
7
|
+
cave_cli/commands/list_versions.py,sha256=NpoqTJ7LMKA9T77gtl5SP1SjtLSkwr4S3RbO1Dln3q0,2649
|
|
8
|
+
cave_cli/commands/prettify.py,sha256=eQb11E9jAfZT8E_anQ3bzGsoKAp-Gpn3llFTxJFFKOA,704
|
|
9
|
+
cave_cli/commands/purge.py,sha256=EkSg3sMJDawJGtQXzWIlOgArkcQk19UhVsHjY_7RsCA,1915
|
|
10
|
+
cave_cli/commands/reset.py,sha256=bVGBouEjLCsnc4wa2BN2HdCtTXDDd05sDNpekWKSwbM,1239
|
|
11
|
+
cave_cli/commands/run.py,sha256=gaXqPXw6VQrLY7sivtNAIBdCSmG4BgamnIVFcNEldBo,5669
|
|
12
|
+
cave_cli/commands/sync_cmd.py,sha256=dZAED2fOBFIgXxt6itw6gTZaf6F4ABHyQ5AjZ3aStt8,2163
|
|
13
|
+
cave_cli/commands/test.py,sha256=SLbpFiYpKl3_7wzL_NfLMSl7LxJW-nqjoAUOgvEWmrk,978
|
|
14
|
+
cave_cli/commands/uninstall.py,sha256=v34VoFo7tYphUYciloYGMgRLSNGJtkGKE1yYLE5RUno,971
|
|
15
|
+
cave_cli/commands/update.py,sha256=o_HB4d3YmUjRGahvKSokqmQvMBdGWehNksX6-IRCbhM,991
|
|
16
|
+
cave_cli/commands/upgrade.py,sha256=fLeEhWjuCeFFPXafb4vAG_9NN6IdVLGwRXJf25DELLQ,2199
|
|
17
|
+
cave_cli/commands/version.py,sha256=eIvBERe1_MaN7DwDyeOIhnUD0rOfq4Z8W2Z5onqHQVE,1924
|
|
18
|
+
cave_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
cave_cli/utils/cache.py,sha256=lutofWOa9cLSdDkVnSRm_PSzarxN_GosZZN2yunSt_g,6068
|
|
20
|
+
cave_cli/utils/constants.py,sha256=IPgGvBj2BVyiXrFAWfIKPqI6Q_jhAhElqjAMmfiA4C8,1123
|
|
21
|
+
cave_cli/utils/docker.py,sha256=Fj3fRgHOVqkXZP2gmsShmXJ1f4bGX-ypl1POww6EIs4,12553
|
|
22
|
+
cave_cli/utils/env.py,sha256=nlKKLOKtetviMLfpqkdhd7I_CiMU601lh2mxW6DC5mM,7154
|
|
23
|
+
cave_cli/utils/git.py,sha256=Dx8IwnLQ5yA_n_En13nP8IBycV1LcguB1xjJQyUpOdo,4672
|
|
24
|
+
cave_cli/utils/logger.py,sha256=qELmwrAMCJ8xxd6dV4hfgGczg6XLnHN_1mAcBb09zZA,1849
|
|
25
|
+
cave_cli/utils/net.py,sha256=qpKKACgrgJLW_BdciFd7EVtBvcBRk8oCimLwk4JqGIg,1866
|
|
26
|
+
cave_cli/utils/subprocess.py,sha256=s6a1VvAmfNo3uQnuckW9CbVlCwYkf5070AXdzygZa28,2877
|
|
27
|
+
cave_cli/utils/sync.py,sha256=-1Fma2Y923zKjyI6g5FmuUHkEdRSc5Rk_K7mlbSHdtk,2348
|
|
28
|
+
cave_cli/utils/validate.py,sha256=5B8vPls699IJhDckSmEJ68Ew6F4AeJ8rayRP2gL97MQ,5656
|
|
29
|
+
cave_cli-3.5.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
30
|
+
cave_cli-3.5.0.dist-info/licenses/NOTICE.md,sha256=2qN436eW01DWcPaXriThoEXhGNqG8OHNrHWYsGd2t3Q,631
|
|
31
|
+
cave_cli-3.5.0.dist-info/METADATA,sha256=KHtpHl-BxKhNnECgIicrugyfxH7MT_UrQyKeZ124JXc,4092
|
|
32
|
+
cave_cli-3.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
33
|
+
cave_cli-3.5.0.dist-info/entry_points.txt,sha256=rxKeJ_S2t0gTfCyPYSAEYlWF1H7D-kjUer8DQP3fldY,43
|
|
34
|
+
cave_cli-3.5.0.dist-info/top_level.txt,sha256=CDnV_I6P0bNNl97Ao7S-2Dqe0K0TFs_1TcPZiQkEXzw,9
|
|
35
|
+
cave_cli-3.5.0.dist-info/RECORD,,
|