quickscale-cli 0.88.0__tar.gz → 0.89.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.
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/PKG-INFO +2 -2
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/pyproject.toml +10 -4
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/_version.py +1 -1
- quickscale_cli-0.89.0/src/quickscale_cli/cli_help.py +292 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_config.py +419 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_context.py +637 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_deploy_service_config.py +320 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_deploy_steps.py +383 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_adapter.py +192 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_context.py +201 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_env_sync.py +157 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_dr_report.py +211 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_embedding.py +144 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_finalize.py +527 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_generation.py +258 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_git_ops.py +329 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_embed.py +542 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_push.py +140 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_shared.py +383 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update.py +1463 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_guard.py +250 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_identity.py +105 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_io.py +646 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_journal.py +238 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_module_update_seal.py +167 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_add.py +353 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_new_project.py +252 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_reconfigure.py +268 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_shared.py +182 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_plan_validation.py +122 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_runtime.py +521 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_split_refs.py +114 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_state.py +535 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_context.py +247 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_diagnostics.py +282 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_status_display.py +539 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_steps.py +751 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/_types.py +115 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/apply_command.py +365 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/apply_support.py +200 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/contract_vintage.py +363 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/deployment_commands.py +200 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/development_commands.py +51 -38
- quickscale_cli-0.89.0/src/quickscale_cli/commands/dr_commands.py +544 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/dr_output.py +18 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/module_commands.py +95 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/module_config.py +288 -0
- quickscale_cli-0.89.0/src/quickscale_cli/commands/plan_command.py +171 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/plan_selection.py +1 -6
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/remove_command.py +93 -170
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/remove_output.py +1 -2
- quickscale_cli-0.89.0/src/quickscale_cli/commands/status_command.py +161 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/main.py +2 -1
- quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_io.py +299 -0
- quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_sync_ops.py +403 -0
- quickscale_cli-0.89.0/src/quickscale_cli/utils/_dependency_write.py +368 -0
- quickscale_cli-0.89.0/src/quickscale_cli/utils/_railway_cli.py +484 -0
- quickscale_cli-0.88.0/src/quickscale_cli/utils/railway_utils.py → quickscale_cli-0.89.0/src/quickscale_cli/utils/_railway_project.py +63 -472
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/auth_migration.py +2 -2
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/dependency_utils.py +5 -5
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/docker_utils.py +14 -11
- quickscale_cli-0.89.0/src/quickscale_cli/utils/module_dependency_sync.py +25 -0
- quickscale_cli-0.89.0/src/quickscale_cli/utils/module_provenance.py +437 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/module_wiring_manager.py +175 -11
- quickscale_cli-0.89.0/src/quickscale_cli/utils/railway_utils.py +45 -0
- quickscale_cli-0.88.0/src/quickscale_cli/commands/apply_command.py +0 -4122
- quickscale_cli-0.88.0/src/quickscale_cli/commands/apply_support.py +0 -102
- quickscale_cli-0.88.0/src/quickscale_cli/commands/deployment_commands.py +0 -678
- quickscale_cli-0.88.0/src/quickscale_cli/commands/dr_commands.py +0 -1066
- quickscale_cli-0.88.0/src/quickscale_cli/commands/module_commands.py +0 -1672
- quickscale_cli-0.88.0/src/quickscale_cli/commands/module_config.py +0 -1310
- quickscale_cli-0.88.0/src/quickscale_cli/commands/plan_command.py +0 -1231
- quickscale_cli-0.88.0/src/quickscale_cli/commands/status_command.py +0 -1031
- quickscale_cli-0.88.0/src/quickscale_cli/utils/module_dependency_sync.py +0 -834
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/README.md +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/__init__.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/__init__.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/commands/module_output.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/py.typed +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/__init__.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/development_state.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/project_identity.py +0 -0
- {quickscale_cli-0.88.0 → quickscale_cli-0.89.0}/src/quickscale_cli/utils/project_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickscale-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.89.0
|
|
4
4
|
Summary: Command-line interface for QuickScale Django project generator
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Keywords: django,cli,project-generator,saas
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
14
14
|
Classifier: Framework :: Django
|
|
15
15
|
Classifier: Framework :: Django :: 6.0
|
|
16
16
|
Requires-Dist: click (>=8.4.2,<9.0.0)
|
|
17
|
-
Requires-Dist: quickscale-core (>=0.
|
|
17
|
+
Requires-Dist: quickscale-core (>=0.89.0,<0.90.0)
|
|
18
18
|
Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
|
|
19
19
|
Project-URL: Homepage, https://github.com/Experto-AI/quickscale
|
|
20
20
|
Project-URL: Repository, https://github.com/Experto-AI/quickscale
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quickscale-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.89.0"
|
|
4
4
|
description = "Command-line interface for QuickScale Django project generator"
|
|
5
5
|
requires-python = ">=3.14,<3.15"
|
|
6
6
|
readme = "README.md"
|
|
@@ -31,14 +31,14 @@ packages = [{include = "quickscale_cli", from = "src"}]
|
|
|
31
31
|
[tool.poetry.dependencies]
|
|
32
32
|
python = ">=3.14,<3.15"
|
|
33
33
|
click = "^8.4.2"
|
|
34
|
-
quickscale-core = "^0.
|
|
34
|
+
quickscale-core = "^0.89.0"
|
|
35
35
|
|
|
36
36
|
[tool.poetry.group.dev.dependencies]
|
|
37
37
|
# Dev dependencies centralized in root pyproject.toml
|
|
38
38
|
# Use: poetry install from repository root
|
|
39
39
|
|
|
40
40
|
[build-system]
|
|
41
|
-
requires = ["poetry-core"]
|
|
41
|
+
requires = ["poetry-core>=2.0,<3.0"]
|
|
42
42
|
build-backend = "poetry.core.masonry.api"
|
|
43
43
|
|
|
44
44
|
[tool.pytest.ini_options]
|
|
@@ -76,13 +76,19 @@ target-version = "py314"
|
|
|
76
76
|
# findings (0.16 widened them by ~486 across these two packages).
|
|
77
77
|
# Adopting the shared select here is worthwhile but is its own cleanup.
|
|
78
78
|
[tool.ruff.lint]
|
|
79
|
-
select = ["E4", "E7", "E9", "F"]
|
|
79
|
+
select = ["E4", "E7", "E9", "F", "S"]
|
|
80
80
|
|
|
81
81
|
# NOTE: `I` (isort) is not in the select above, so the [tool.ruff.lint.isort]
|
|
82
82
|
# override below is currently inert — it configures a rule that is not
|
|
83
83
|
# enabled. It is kept because it becomes correct the moment this package
|
|
84
84
|
# adopts the shared select (which does enable `I`); see implementation_contract.md.
|
|
85
85
|
|
|
86
|
+
# The S (flake8-bandit) rules govern shipped package source. The Bandit
|
|
87
|
+
# static-security gate already scopes its scan to maintained source roots,
|
|
88
|
+
# excluding test trees; this table keeps the Ruff selection in step.
|
|
89
|
+
[tool.ruff.lint.per-file-ignores]
|
|
90
|
+
"tests/**" = ["S"]
|
|
91
|
+
|
|
86
92
|
# Package-specific isort override
|
|
87
93
|
[tool.ruff.lint.isort]
|
|
88
94
|
known-first-party = ["quickscale_cli", "quickscale_core"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# Auto-generated by scripts/version_tool.sh
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.89.0"
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Help-tree machinery shared by the QuickScale CLI commands.
|
|
2
|
+
|
|
3
|
+
Click's default group help lists only each subcommand's name and one-line
|
|
4
|
+
summary. The CLI contract asks for the full tree, so :class:`QuickScaleGroup`
|
|
5
|
+
writes every reachable command's own help beneath its own. This module also
|
|
6
|
+
owns the help that Click cannot infer: per-command examples, positional
|
|
7
|
+
argument descriptions (Click 8.4 arguments carry none), and the ``--tail``
|
|
8
|
+
value type used by ``quickscale logs``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import subprocess
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from quickscale_core.contracts.module_catalog import MODULE_CATALOG
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_MODULE_NAMES = ", ".join(sorted(entry.name for entry in MODULE_CATALOG))
|
|
25
|
+
_DEFAULT_COMPOSE_SERVICES = ("backend", "frontend", "db")
|
|
26
|
+
|
|
27
|
+
EXAMPLES: dict[str, str] = {
|
|
28
|
+
"quickscale": (
|
|
29
|
+
"quickscale --help\nquickscale plan my-app\nquickscale apply\nquickscale up"
|
|
30
|
+
),
|
|
31
|
+
"quickscale apply": (
|
|
32
|
+
"quickscale apply\n"
|
|
33
|
+
"quickscale apply myapp/quickscale.yml\n"
|
|
34
|
+
"quickscale apply --force --no-docker"
|
|
35
|
+
),
|
|
36
|
+
"quickscale deploy": (
|
|
37
|
+
"quickscale deploy railway\nquickscale deploy railway --project-name my-app"
|
|
38
|
+
),
|
|
39
|
+
"quickscale deploy railway": (
|
|
40
|
+
"quickscale deploy railway\nquickscale deploy railway --project-name my-app"
|
|
41
|
+
),
|
|
42
|
+
"quickscale down": "quickscale down\nquickscale down --volumes",
|
|
43
|
+
"quickscale dr": (
|
|
44
|
+
"quickscale dr capture --route local-to-railway-develop\n"
|
|
45
|
+
"quickscale dr report --route local-to-railway-develop --snapshot-id <id>"
|
|
46
|
+
),
|
|
47
|
+
"quickscale dr capture": (
|
|
48
|
+
"quickscale dr capture --route local-to-railway-develop\n"
|
|
49
|
+
"quickscale dr capture --route railway-develop-to-railway-production "
|
|
50
|
+
"--source-service my-app --json"
|
|
51
|
+
),
|
|
52
|
+
"quickscale dr execute": (
|
|
53
|
+
"quickscale dr execute --route local-to-railway-develop --snapshot-id <id> "
|
|
54
|
+
"--target-service my-app --database\n"
|
|
55
|
+
"quickscale dr execute --route railway-develop-to-railway-production "
|
|
56
|
+
"--snapshot-id <id> --source-service my-app --target-service my-app --resume"
|
|
57
|
+
),
|
|
58
|
+
"quickscale dr plan": (
|
|
59
|
+
"quickscale dr plan --route local-to-railway-develop --snapshot-id <id> "
|
|
60
|
+
"--target-service my-app\n"
|
|
61
|
+
"quickscale dr plan --route railway-develop-to-railway-production "
|
|
62
|
+
"--snapshot-id <id> --source-service my-app --target-service my-app --json"
|
|
63
|
+
),
|
|
64
|
+
"quickscale dr report": (
|
|
65
|
+
"quickscale dr report --route local-to-railway-develop --snapshot-id <id>"
|
|
66
|
+
),
|
|
67
|
+
"quickscale logs": (
|
|
68
|
+
"quickscale logs\n"
|
|
69
|
+
"quickscale logs backend --follow\n"
|
|
70
|
+
"quickscale logs db --tail 100"
|
|
71
|
+
),
|
|
72
|
+
"quickscale manage": (
|
|
73
|
+
"quickscale manage\n"
|
|
74
|
+
"quickscale manage migrate\n"
|
|
75
|
+
"quickscale manage createsuperuser"
|
|
76
|
+
),
|
|
77
|
+
"quickscale plan": (
|
|
78
|
+
"quickscale plan my-app\n"
|
|
79
|
+
"quickscale plan my-app --package my_app\n"
|
|
80
|
+
"quickscale plan --add\n"
|
|
81
|
+
"quickscale plan --reconfigure"
|
|
82
|
+
),
|
|
83
|
+
"quickscale ps": "quickscale ps",
|
|
84
|
+
"quickscale push": (
|
|
85
|
+
"quickscale push --module auth\n"
|
|
86
|
+
"quickscale push --module auth --branch feature/fix-email-validation"
|
|
87
|
+
),
|
|
88
|
+
"quickscale remove": "quickscale remove auth\nquickscale remove billing --keep-data",
|
|
89
|
+
"quickscale shell": (
|
|
90
|
+
'quickscale shell\nquickscale shell -c "python manage.py check"'
|
|
91
|
+
),
|
|
92
|
+
"quickscale status": "quickscale status\nquickscale status --json",
|
|
93
|
+
"quickscale up": "quickscale up\nquickscale up --build\nquickscale up --no-cache",
|
|
94
|
+
"quickscale update": "quickscale update\nquickscale update --no-preview",
|
|
95
|
+
"quickscale version": "quickscale version",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ARGUMENT_HELP: dict[str, dict[str, str]] = {
|
|
99
|
+
"quickscale apply": {
|
|
100
|
+
"config": "Path to the configuration file (default: quickscale.yml in the current directory).",
|
|
101
|
+
},
|
|
102
|
+
"quickscale logs": {
|
|
103
|
+
"service": (
|
|
104
|
+
"Compose service to show logs for: backend, frontend, or db in "
|
|
105
|
+
"generated projects (read from the project's docker-compose.yml)."
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
"quickscale manage": {
|
|
109
|
+
"args": (
|
|
110
|
+
"Django management command and its arguments, for example: migrate, "
|
|
111
|
+
"createsuperuser. Omit it, or pass --help, to list the project's commands."
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
"quickscale plan": {
|
|
115
|
+
"project_slug": "Project slug to configure; omit it with --add or --reconfigure.",
|
|
116
|
+
},
|
|
117
|
+
"quickscale remove": {
|
|
118
|
+
"module_name": (
|
|
119
|
+
f"Embedded module to remove. Valid shipped modules: {_MODULE_NAMES}."
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TailLines(click.ParamType):
|
|
126
|
+
"""Accept a non-negative line count or the literal ``all``."""
|
|
127
|
+
|
|
128
|
+
name = "LINES|all"
|
|
129
|
+
|
|
130
|
+
def convert(
|
|
131
|
+
self,
|
|
132
|
+
value: Any,
|
|
133
|
+
param: click.Parameter | None,
|
|
134
|
+
ctx: click.Context | None,
|
|
135
|
+
) -> Any:
|
|
136
|
+
if value is None:
|
|
137
|
+
return None
|
|
138
|
+
text = str(value).strip()
|
|
139
|
+
if text.lower() == "all":
|
|
140
|
+
return "all"
|
|
141
|
+
try:
|
|
142
|
+
lines = int(text)
|
|
143
|
+
except ValueError:
|
|
144
|
+
self.fail("must be a non-negative integer or 'all'", param, ctx)
|
|
145
|
+
if lines < 0:
|
|
146
|
+
self.fail("must be a non-negative integer or 'all'", param, ctx)
|
|
147
|
+
return str(lines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
TAIL_LINES = TailLines()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _declared_compose_services() -> tuple[str, ...] | None:
|
|
154
|
+
"""Return the Compose services declared in the current project, if any."""
|
|
155
|
+
compose_file = Path("docker-compose.yml")
|
|
156
|
+
try:
|
|
157
|
+
data = yaml.safe_load(compose_file.read_text(encoding="utf-8")) or {}
|
|
158
|
+
except OSError, yaml.YAMLError:
|
|
159
|
+
return None
|
|
160
|
+
services = data.get("services") if isinstance(data, dict) else None
|
|
161
|
+
if not isinstance(services, dict) or not services:
|
|
162
|
+
return None
|
|
163
|
+
return tuple(sorted(str(name) for name in services))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _compose_service_help(default: str) -> str:
|
|
167
|
+
"""Name the project's declared services in the logs argument help."""
|
|
168
|
+
services = _declared_compose_services()
|
|
169
|
+
if services is None:
|
|
170
|
+
return default
|
|
171
|
+
return (
|
|
172
|
+
f"Compose service to show logs for: {', '.join(services)} "
|
|
173
|
+
"(from the project's docker-compose.yml)."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def validate_compose_service(service: str | None) -> None:
|
|
178
|
+
"""Reject a service name the project's Compose file does not declare."""
|
|
179
|
+
if service is None:
|
|
180
|
+
return
|
|
181
|
+
services = _declared_compose_services()
|
|
182
|
+
if services is None:
|
|
183
|
+
return
|
|
184
|
+
if service not in services:
|
|
185
|
+
choices = ", ".join(services)
|
|
186
|
+
raise click.BadParameter(
|
|
187
|
+
f"{service!r} is not declared in docker-compose.yml; choose one of: {choices}",
|
|
188
|
+
param_hint="SERVICE",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def list_django_commands(run_exec: Callable[..., None], container_name: str) -> None:
|
|
193
|
+
"""List the project's Django commands through manage.py help."""
|
|
194
|
+
click.echo("Django management commands, grouped by app:")
|
|
195
|
+
try:
|
|
196
|
+
run_exec(container_name, ["python", "manage.py", "help"], capture=True)
|
|
197
|
+
except subprocess.CalledProcessError as error:
|
|
198
|
+
click.secho(
|
|
199
|
+
"❌ Error: Backend container is not running or manage.py help failed",
|
|
200
|
+
fg="red",
|
|
201
|
+
err=True,
|
|
202
|
+
)
|
|
203
|
+
click.echo("💡 Start the stack with 'quickscale up' and try again.", err=True)
|
|
204
|
+
raise SystemExit(error.returncode) from error
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _help_key(ctx: click.Context) -> str:
|
|
208
|
+
"""Return the registry key for a context, independent of the program name."""
|
|
209
|
+
root = ctx.find_root()
|
|
210
|
+
if ctx is root:
|
|
211
|
+
return "quickscale"
|
|
212
|
+
suffix = ctx.command_path[len(root.command_path) :].strip()
|
|
213
|
+
return f"quickscale {suffix}"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _write_argument_help(
|
|
217
|
+
ctx: click.Context, formatter: click.HelpFormatter, command: click.Command
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Write the documented positional arguments, if this command has any."""
|
|
220
|
+
key = _help_key(ctx)
|
|
221
|
+
documented = ARGUMENT_HELP.get(key, {})
|
|
222
|
+
rows = []
|
|
223
|
+
for param in command.get_params(ctx):
|
|
224
|
+
if isinstance(param, click.Argument):
|
|
225
|
+
help_text = documented.get(param.name)
|
|
226
|
+
if key == "quickscale logs" and param.name == "service":
|
|
227
|
+
help_text = _compose_service_help(help_text or "")
|
|
228
|
+
if help_text:
|
|
229
|
+
rows.append((param.make_metavar(ctx), help_text))
|
|
230
|
+
if rows:
|
|
231
|
+
with formatter.section("Arguments"):
|
|
232
|
+
formatter.write_dl(rows)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _write_examples(ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
236
|
+
"""Write the registered examples for this command, if it has any."""
|
|
237
|
+
examples = EXAMPLES.get(_help_key(ctx))
|
|
238
|
+
if not examples:
|
|
239
|
+
return
|
|
240
|
+
formatter.write_paragraph()
|
|
241
|
+
formatter.write("Examples:\n")
|
|
242
|
+
for line in examples.splitlines():
|
|
243
|
+
formatter.write(f" {line}\n")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class QuickScaleCommand(click.Command):
|
|
247
|
+
"""A command whose help carries examples and argument descriptions."""
|
|
248
|
+
|
|
249
|
+
def format_help_text(
|
|
250
|
+
self, ctx: click.Context, formatter: click.HelpFormatter
|
|
251
|
+
) -> None:
|
|
252
|
+
super().format_help_text(ctx, formatter)
|
|
253
|
+
_write_argument_help(ctx, formatter, self)
|
|
254
|
+
_write_examples(ctx, formatter)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class QuickScaleGroup(click.Group):
|
|
258
|
+
"""A group that prints its full command tree beneath its own help."""
|
|
259
|
+
|
|
260
|
+
command_class = QuickScaleCommand
|
|
261
|
+
|
|
262
|
+
def format_help_text(
|
|
263
|
+
self, ctx: click.Context, formatter: click.HelpFormatter
|
|
264
|
+
) -> None:
|
|
265
|
+
super().format_help_text(ctx, formatter)
|
|
266
|
+
_write_argument_help(ctx, formatter, self)
|
|
267
|
+
_write_examples(ctx, formatter)
|
|
268
|
+
|
|
269
|
+
def format_commands(
|
|
270
|
+
self, ctx: click.Context, formatter: click.HelpFormatter
|
|
271
|
+
) -> None:
|
|
272
|
+
commands = []
|
|
273
|
+
for name in self.list_commands(ctx):
|
|
274
|
+
command = self.get_command(ctx, name)
|
|
275
|
+
if command is not None and not command.hidden:
|
|
276
|
+
commands.append((name, command))
|
|
277
|
+
if not commands:
|
|
278
|
+
return
|
|
279
|
+
formatter.write_paragraph()
|
|
280
|
+
formatter.write_heading("Commands")
|
|
281
|
+
for name, command in commands:
|
|
282
|
+
sub_ctx = click.Context(command, info_name=name, parent=ctx)
|
|
283
|
+
formatter.write_paragraph()
|
|
284
|
+
for line in command.get_help(sub_ctx).splitlines():
|
|
285
|
+
formatter.write(f"{line}\n")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class PassThroughCommand(QuickScaleCommand):
|
|
289
|
+
"""A command that forwards unknown options, including ``--help``."""
|
|
290
|
+
|
|
291
|
+
def get_help_option(self, ctx: click.Context) -> click.Option | None:
|
|
292
|
+
return None
|