quickscale-cli 0.79.0__tar.gz → 0.83.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.79.0 → quickscale_cli-0.83.0}/PKG-INFO +5 -4
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/README.md +2 -1
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/pyproject.toml +5 -4
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/__init__.py +8 -1
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/_version.py +1 -1
- quickscale_cli-0.83.0/src/quickscale_cli/analytics_contract.py +177 -0
- quickscale_cli-0.83.0/src/quickscale_cli/auth_contract.py +97 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/backups_contract.py +11 -2
- quickscale_cli-0.83.0/src/quickscale_cli/beta_migration.py +2451 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/apply_command.py +1019 -160
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/development_commands.py +14 -3
- quickscale_cli-0.83.0/src/quickscale_cli/commands/dr_commands.py +1319 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/module_commands.py +296 -67
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/module_config.py +351 -411
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/module_wiring_specs.py +51 -39
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/plan_command.py +180 -43
- quickscale_cli-0.83.0/src/quickscale_cli/commands/remove_command.py +704 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/status_command.py +127 -27
- quickscale_cli-0.83.0/src/quickscale_cli/crm_contract.py +43 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/main.py +2 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/module_catalog.py +29 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/notifications_contract.py +13 -3
- quickscale_cli-0.83.0/src/quickscale_cli/schema/__init__.py +56 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/schema/config_schema.py +141 -5
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/schema/state_schema.py +7 -2
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/social_contract.py +1 -1
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/docker_utils.py +2 -2
- quickscale_cli-0.83.0/src/quickscale_cli/utils/module_dependency_sync.py +364 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/module_wiring_manager.py +31 -10
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/project_identity.py +54 -12
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/project_manager.py +8 -2
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/railway_utils.py +30 -6
- quickscale_cli-0.79.0/src/quickscale_cli/commands/remove_command.py +0 -264
- quickscale_cli-0.79.0/src/quickscale_cli/schema/__init__.py +0 -40
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/__init__.py +0 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/commands/deployment_commands.py +0 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/schema/delta.py +0 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/__init__.py +0 -0
- {quickscale_cli-0.79.0 → quickscale_cli-0.83.0}/src/quickscale_cli/utils/dependency_utils.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickscale-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.83.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
|
|
7
7
|
Author: Experto AI
|
|
8
8
|
Author-email: victor@experto.ai
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.12,<3.14
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -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.3.1,<9.0.0)
|
|
17
|
-
Requires-Dist: quickscale-core (>=0.
|
|
17
|
+
Requires-Dist: quickscale-core (>=0.83.0,<0.84.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
|
|
@@ -24,11 +24,12 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
|
|
25
25
|
Command-line interface package for QuickScale.
|
|
26
26
|
|
|
27
|
-
This package exposes the `quickscale` command used to plan, apply, inspect, and operate generated projects.
|
|
27
|
+
This package exposes the `quickscale` command used to plan, apply, inspect, recover, and operate generated projects.
|
|
28
28
|
|
|
29
29
|
## Command groups
|
|
30
30
|
|
|
31
31
|
- Project lifecycle: `plan`, `apply`, `status`, `remove`
|
|
32
|
+
- Disaster recovery & promotion: `dr capture`, `dr plan`, `dr execute`, `dr report`
|
|
32
33
|
- Local development: `up`, `down`, `ps`, `logs`, `shell`, `manage`
|
|
33
34
|
- Deployment: `deploy`
|
|
34
35
|
- Module workflows: `update`, `push`
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line interface package for QuickScale.
|
|
4
4
|
|
|
5
|
-
This package exposes the `quickscale` command used to plan, apply, inspect, and operate generated projects.
|
|
5
|
+
This package exposes the `quickscale` command used to plan, apply, inspect, recover, and operate generated projects.
|
|
6
6
|
|
|
7
7
|
## Command groups
|
|
8
8
|
|
|
9
9
|
- Project lifecycle: `plan`, `apply`, `status`, `remove`
|
|
10
|
+
- Disaster recovery & promotion: `dr capture`, `dr plan`, `dr execute`, `dr report`
|
|
10
11
|
- Local development: `up`, `down`, `ps`, `logs`, `shell`, `manage`
|
|
11
12
|
- Deployment: `deploy`
|
|
12
13
|
- Module workflows: `update`, `push`
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quickscale-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.83.0"
|
|
4
4
|
description = "Command-line interface for QuickScale Django project generator"
|
|
5
|
-
requires-python = ">=3.
|
|
5
|
+
requires-python = ">=3.12,<3.14"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "Apache-2.0"
|
|
8
8
|
authors = [{name = "Experto AI", email = "victor@experto.ai"}]
|
|
@@ -29,9 +29,9 @@ quickscale = "quickscale_cli.main:cli"
|
|
|
29
29
|
packages = [{include = "quickscale_cli", from = "src"}]
|
|
30
30
|
|
|
31
31
|
[tool.poetry.dependencies]
|
|
32
|
-
python = "
|
|
32
|
+
python = ">=3.12,<3.14"
|
|
33
33
|
click = "^8.3.1"
|
|
34
|
-
quickscale-core = "^0.
|
|
34
|
+
quickscale-core = "^0.83.0"
|
|
35
35
|
|
|
36
36
|
[tool.poetry.group.dev.dependencies]
|
|
37
37
|
# Dev dependencies centralized in root pyproject.toml
|
|
@@ -43,6 +43,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
43
43
|
|
|
44
44
|
[tool.pytest.ini_options]
|
|
45
45
|
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src"]
|
|
46
47
|
python_files = ["test_*.py"]
|
|
47
48
|
python_classes = ["Test*"]
|
|
48
49
|
python_functions = ["test_*"]
|
|
@@ -7,15 +7,22 @@ stays in sync with other packages when the release is bumped in one place.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import logging
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
__author__ = "Experto AI"
|
|
13
14
|
__email__ = "victor@experto.ai"
|
|
14
15
|
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
15
18
|
# Prefer an embedded package-level `_version.py` if it exists (build step).
|
|
16
19
|
try:
|
|
17
20
|
from ._version import __version__
|
|
18
|
-
except Exception:
|
|
21
|
+
except Exception as exc:
|
|
22
|
+
logger.warning(
|
|
23
|
+
"Failed to import embedded CLI version; falling back to VERSION file or 0.0.0: %s",
|
|
24
|
+
exc,
|
|
25
|
+
)
|
|
19
26
|
_root = Path(__file__).resolve().parents[3]
|
|
20
27
|
_version_file = _root / "VERSION"
|
|
21
28
|
if _version_file.exists():
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# Auto-generated by scripts/version_tool.sh
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.83.0"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Shared analytics-module configuration helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
ANALYTICS_PROVIDER_POSTHOG = "posthog"
|
|
11
|
+
ANALYTICS_PROVIDERS = (ANALYTICS_PROVIDER_POSTHOG,)
|
|
12
|
+
|
|
13
|
+
DEFAULT_ANALYTICS_POSTHOG_API_KEY_ENV_VAR = "POSTHOG_API_KEY"
|
|
14
|
+
DEFAULT_ANALYTICS_POSTHOG_HOST_ENV_VAR = "POSTHOG_HOST"
|
|
15
|
+
|
|
16
|
+
ANALYTICS_POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com"
|
|
17
|
+
ANALYTICS_POSTHOG_EU_HOST = "https://eu.i.posthog.com"
|
|
18
|
+
|
|
19
|
+
ANALYTICS_EVENT_PAGEVIEW = "$pageview"
|
|
20
|
+
ANALYTICS_EVENT_FORM_SUBMIT = "form_submit"
|
|
21
|
+
ANALYTICS_EVENT_SOCIAL_LINK_CLICK = "social_link_click"
|
|
22
|
+
|
|
23
|
+
_ANALYTICS_ENV_VAR_NAME_PATTERN = re.compile(r"^[A-Z][A-Z0-9_]*$")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def default_analytics_module_options() -> dict[str, Any]:
|
|
27
|
+
"""Return the default planner/apply contract for analytics."""
|
|
28
|
+
return {
|
|
29
|
+
"enabled": True,
|
|
30
|
+
"provider": ANALYTICS_PROVIDER_POSTHOG,
|
|
31
|
+
"posthog_api_key_env_var": DEFAULT_ANALYTICS_POSTHOG_API_KEY_ENV_VAR,
|
|
32
|
+
"posthog_host_env_var": DEFAULT_ANALYTICS_POSTHOG_HOST_ENV_VAR,
|
|
33
|
+
"posthog_host": ANALYTICS_POSTHOG_DEFAULT_HOST,
|
|
34
|
+
"exclude_debug": True,
|
|
35
|
+
"exclude_staff": False,
|
|
36
|
+
"anonymous_by_default": True,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalize_provider(value: Any) -> str:
|
|
41
|
+
return str(value).strip().lower()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _normalize_posthog_host(value: Any) -> str:
|
|
45
|
+
candidate = str(value).strip()
|
|
46
|
+
if not candidate:
|
|
47
|
+
return ""
|
|
48
|
+
if not candidate.startswith(("http://", "https://")):
|
|
49
|
+
candidate = "https://" + candidate.lstrip("/")
|
|
50
|
+
return candidate.rstrip("/")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def normalize_analytics_module_options(
|
|
54
|
+
options: Mapping[str, Any] | None,
|
|
55
|
+
) -> dict[str, Any]:
|
|
56
|
+
"""Return analytics options with normalized provider and host fields."""
|
|
57
|
+
normalized = dict(options or {})
|
|
58
|
+
|
|
59
|
+
if "provider" in normalized:
|
|
60
|
+
normalized["provider"] = _normalize_provider(normalized["provider"])
|
|
61
|
+
|
|
62
|
+
for option_name in ("posthog_api_key_env_var", "posthog_host_env_var"):
|
|
63
|
+
if option_name in normalized:
|
|
64
|
+
normalized[option_name] = str(normalized[option_name]).strip()
|
|
65
|
+
|
|
66
|
+
if "posthog_host" in normalized:
|
|
67
|
+
normalized["posthog_host"] = _normalize_posthog_host(normalized["posthog_host"])
|
|
68
|
+
|
|
69
|
+
return normalized
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve_analytics_module_options(
|
|
73
|
+
options: Mapping[str, Any] | None,
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
"""Merge analytics options with defaults and normalized overrides."""
|
|
76
|
+
resolved = default_analytics_module_options()
|
|
77
|
+
resolved.update(normalize_analytics_module_options(options))
|
|
78
|
+
resolved["provider"] = _normalize_provider(resolved["provider"])
|
|
79
|
+
resolved["posthog_api_key_env_var"] = str(
|
|
80
|
+
resolved["posthog_api_key_env_var"]
|
|
81
|
+
).strip()
|
|
82
|
+
resolved["posthog_host_env_var"] = str(resolved["posthog_host_env_var"]).strip()
|
|
83
|
+
resolved["posthog_host"] = _normalize_posthog_host(resolved["posthog_host"])
|
|
84
|
+
return resolved
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def validate_analytics_env_var_reference(option_name: str, value: Any) -> str | None:
|
|
88
|
+
"""Validate an analytics env-var reference field."""
|
|
89
|
+
candidate = str(value).strip()
|
|
90
|
+
if not candidate:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
qualified_option = f"modules.analytics.{option_name}"
|
|
94
|
+
if not _ANALYTICS_ENV_VAR_NAME_PATTERN.fullmatch(candidate):
|
|
95
|
+
return (
|
|
96
|
+
f"{qualified_option} must be an environment variable name matching "
|
|
97
|
+
"^[A-Z][A-Z0-9_]*$"
|
|
98
|
+
)
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _is_valid_posthog_host(value: str) -> bool:
|
|
103
|
+
candidate = value.strip()
|
|
104
|
+
if not candidate:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
parsed = urlsplit(candidate)
|
|
108
|
+
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_analytics_module_options(options: Mapping[str, Any] | None) -> list[str]:
|
|
112
|
+
"""Return validation issues for analytics module options."""
|
|
113
|
+
resolved = resolve_analytics_module_options(options)
|
|
114
|
+
issues: list[str] = []
|
|
115
|
+
|
|
116
|
+
provider = str(resolved.get("provider", "")).strip().lower()
|
|
117
|
+
if provider not in ANALYTICS_PROVIDERS:
|
|
118
|
+
issues.append(
|
|
119
|
+
"modules.analytics.provider must be one of: "
|
|
120
|
+
+ ", ".join(ANALYTICS_PROVIDERS)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
for option_name in ("posthog_api_key_env_var", "posthog_host_env_var"):
|
|
124
|
+
issue = validate_analytics_env_var_reference(
|
|
125
|
+
option_name,
|
|
126
|
+
resolved.get(option_name, ""),
|
|
127
|
+
)
|
|
128
|
+
if issue:
|
|
129
|
+
issues.append(issue)
|
|
130
|
+
|
|
131
|
+
if not _is_valid_posthog_host(str(resolved.get("posthog_host", ""))):
|
|
132
|
+
issues.append("modules.analytics.posthog_host must be an absolute http(s) URL")
|
|
133
|
+
|
|
134
|
+
for option_name in (
|
|
135
|
+
"enabled",
|
|
136
|
+
"exclude_debug",
|
|
137
|
+
"exclude_staff",
|
|
138
|
+
"anonymous_by_default",
|
|
139
|
+
):
|
|
140
|
+
if not isinstance(resolved.get(option_name), bool):
|
|
141
|
+
issues.append(f"modules.analytics.{option_name} must be a boolean")
|
|
142
|
+
|
|
143
|
+
return issues
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def analytics_production_targeted(options: Mapping[str, Any] | None) -> bool:
|
|
147
|
+
"""Return whether analytics is configured for live runtime capture."""
|
|
148
|
+
resolved = resolve_analytics_module_options(options)
|
|
149
|
+
if not bool(resolved.get("enabled", True)):
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
api_key_env_var = str(resolved.get("posthog_api_key_env_var", "")).strip()
|
|
153
|
+
return not bool(
|
|
154
|
+
validate_analytics_env_var_reference(
|
|
155
|
+
"posthog_api_key_env_var",
|
|
156
|
+
api_key_env_var,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
__all__ = [
|
|
162
|
+
"ANALYTICS_EVENT_FORM_SUBMIT",
|
|
163
|
+
"ANALYTICS_EVENT_PAGEVIEW",
|
|
164
|
+
"ANALYTICS_EVENT_SOCIAL_LINK_CLICK",
|
|
165
|
+
"ANALYTICS_POSTHOG_DEFAULT_HOST",
|
|
166
|
+
"ANALYTICS_POSTHOG_EU_HOST",
|
|
167
|
+
"ANALYTICS_PROVIDER_POSTHOG",
|
|
168
|
+
"ANALYTICS_PROVIDERS",
|
|
169
|
+
"DEFAULT_ANALYTICS_POSTHOG_API_KEY_ENV_VAR",
|
|
170
|
+
"DEFAULT_ANALYTICS_POSTHOG_HOST_ENV_VAR",
|
|
171
|
+
"analytics_production_targeted",
|
|
172
|
+
"default_analytics_module_options",
|
|
173
|
+
"normalize_analytics_module_options",
|
|
174
|
+
"resolve_analytics_module_options",
|
|
175
|
+
"validate_analytics_env_var_reference",
|
|
176
|
+
"validate_analytics_module_options",
|
|
177
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Shared auth-module configuration helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
AUTH_REGISTRATION_ENABLED_OPTION = "registration_enabled"
|
|
9
|
+
AUTH_EMAIL_VERIFICATION_OPTION = "email_verification"
|
|
10
|
+
AUTH_AUTHENTICATION_METHOD_OPTION = "authentication_method"
|
|
11
|
+
AUTH_SESSION_COOKIE_AGE_OPTION = "session_cookie_age"
|
|
12
|
+
|
|
13
|
+
AUTH_EMAIL_VERIFICATION_VALUES = ("none", "optional", "mandatory")
|
|
14
|
+
AUTH_AUTHENTICATION_METHOD_VALUES = ("email", "username", "both")
|
|
15
|
+
CANONICAL_AUTH_MODULE_OPTION_KEYS = frozenset(
|
|
16
|
+
{
|
|
17
|
+
AUTH_REGISTRATION_ENABLED_OPTION,
|
|
18
|
+
AUTH_EMAIL_VERIFICATION_OPTION,
|
|
19
|
+
AUTH_AUTHENTICATION_METHOD_OPTION,
|
|
20
|
+
AUTH_SESSION_COOKIE_AGE_OPTION,
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
LEGACY_AUTH_ALLOW_REGISTRATION_OPTION = "allow_registration"
|
|
25
|
+
LEGACY_AUTH_SOCIAL_PROVIDERS_OPTION = "social_providers"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def default_auth_module_options() -> dict[str, Any]:
|
|
29
|
+
"""Return the default planner/apply contract for auth."""
|
|
30
|
+
return {
|
|
31
|
+
AUTH_REGISTRATION_ENABLED_OPTION: True,
|
|
32
|
+
AUTH_EMAIL_VERIFICATION_OPTION: "none",
|
|
33
|
+
AUTH_AUTHENTICATION_METHOD_OPTION: "email",
|
|
34
|
+
AUTH_SESSION_COOKIE_AGE_OPTION: 1209600,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_auth_desired_config_contract() -> str:
|
|
39
|
+
"""Return concise remediation text for canonical auth desired config."""
|
|
40
|
+
return "\n".join(
|
|
41
|
+
[
|
|
42
|
+
"Canonical auth keys/value shapes:",
|
|
43
|
+
"modules.auth.registration_enabled: true|false",
|
|
44
|
+
"modules.auth.email_verification: none|optional|mandatory",
|
|
45
|
+
"modules.auth.authentication_method: email|username|both",
|
|
46
|
+
"modules.auth.session_cookie_age: <positive integer seconds> # optional",
|
|
47
|
+
(
|
|
48
|
+
"Remove legacy keys like modules.auth.allow_registration and "
|
|
49
|
+
"modules.auth.social_providers."
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def normalize_auth_module_options(
|
|
56
|
+
options: Mapping[str, Any] | None,
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
"""Return auth options with legacy keys normalized or removed."""
|
|
59
|
+
normalized = dict(options or {})
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
AUTH_REGISTRATION_ENABLED_OPTION not in normalized
|
|
63
|
+
and LEGACY_AUTH_ALLOW_REGISTRATION_OPTION in normalized
|
|
64
|
+
):
|
|
65
|
+
normalized[AUTH_REGISTRATION_ENABLED_OPTION] = normalized[
|
|
66
|
+
LEGACY_AUTH_ALLOW_REGISTRATION_OPTION
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
normalized.pop(LEGACY_AUTH_ALLOW_REGISTRATION_OPTION, None)
|
|
70
|
+
normalized.pop(LEGACY_AUTH_SOCIAL_PROVIDERS_OPTION, None)
|
|
71
|
+
return normalized
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def resolve_auth_module_options(
|
|
75
|
+
options: Mapping[str, Any] | None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
"""Merge auth options with defaults and normalized overrides."""
|
|
78
|
+
resolved = default_auth_module_options()
|
|
79
|
+
resolved.update(normalize_auth_module_options(options))
|
|
80
|
+
return resolved
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
"AUTH_AUTHENTICATION_METHOD_OPTION",
|
|
85
|
+
"AUTH_AUTHENTICATION_METHOD_VALUES",
|
|
86
|
+
"AUTH_EMAIL_VERIFICATION_OPTION",
|
|
87
|
+
"AUTH_EMAIL_VERIFICATION_VALUES",
|
|
88
|
+
"AUTH_REGISTRATION_ENABLED_OPTION",
|
|
89
|
+
"AUTH_SESSION_COOKIE_AGE_OPTION",
|
|
90
|
+
"CANONICAL_AUTH_MODULE_OPTION_KEYS",
|
|
91
|
+
"LEGACY_AUTH_ALLOW_REGISTRATION_OPTION",
|
|
92
|
+
"LEGACY_AUTH_SOCIAL_PROVIDERS_OPTION",
|
|
93
|
+
"default_auth_module_options",
|
|
94
|
+
"format_auth_desired_config_contract",
|
|
95
|
+
"normalize_auth_module_options",
|
|
96
|
+
"resolve_auth_module_options",
|
|
97
|
+
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Shared
|
|
1
|
+
"""Shared module configuration helpers.
|
|
2
2
|
|
|
3
3
|
This module centralizes the non-secret configuration contract used by the
|
|
4
|
-
planner, apply flow, and state persistence for
|
|
4
|
+
planner, apply flow, and state persistence for module compatibility helpers.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -10,6 +10,9 @@ from collections.abc import Mapping
|
|
|
10
10
|
import re
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
+
from quickscale_cli.analytics_contract import normalize_analytics_module_options
|
|
14
|
+
from quickscale_cli.auth_contract import normalize_auth_module_options
|
|
15
|
+
from quickscale_cli.crm_contract import normalize_crm_module_options
|
|
13
16
|
from quickscale_cli.notifications_contract import normalize_notifications_module_options
|
|
14
17
|
from quickscale_cli.social_contract import normalize_social_module_options
|
|
15
18
|
|
|
@@ -107,8 +110,14 @@ def sanitize_module_options(
|
|
|
107
110
|
options: Mapping[str, Any] | None,
|
|
108
111
|
) -> dict[str, Any]:
|
|
109
112
|
"""Return module options safe for config/state persistence."""
|
|
113
|
+
if module_name == "analytics":
|
|
114
|
+
return normalize_analytics_module_options(options)
|
|
115
|
+
if module_name == "auth":
|
|
116
|
+
return normalize_auth_module_options(options)
|
|
110
117
|
if module_name == "backups":
|
|
111
118
|
return normalize_backups_module_options(options)
|
|
119
|
+
if module_name == "crm":
|
|
120
|
+
return normalize_crm_module_options(options)
|
|
112
121
|
if module_name == "notifications":
|
|
113
122
|
return normalize_notifications_module_options(options)
|
|
114
123
|
if module_name == "social":
|