quickscale-cli 0.85.0__tar.gz → 0.86.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.85.0 → quickscale_cli-0.86.0}/PKG-INFO +2 -2
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/pyproject.toml +2 -2
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/_version.py +1 -1
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/apply_command.py +156 -6
- quickscale_cli-0.86.0/src/quickscale_cli/commands/implied_module_defaults.py +23 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_config.py +10 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_wiring_specs.py +54 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/plan_command.py +48 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/module_catalog.py +5 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/README.md +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/__init__.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/analytics_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/auth_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/backups_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/beta_migration.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/billing_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/__init__.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/deployment_commands.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/development_commands.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/dr_commands.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_commands.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/remove_command.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/status_command.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/crm_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/main.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/notifications_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/schema/__init__.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/schema/config_schema.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/schema/delta.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/schema/state_schema.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/social_contract.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/__init__.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/dependency_utils.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/docker_utils.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/module_dependency_sync.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/module_wiring_manager.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/project_identity.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/project_manager.py +0 -0
- {quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/railway_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickscale-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.86.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
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
15
15
|
Classifier: Framework :: Django
|
|
16
16
|
Classifier: Framework :: Django :: 6.0
|
|
17
17
|
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
18
|
-
Requires-Dist: quickscale-core (>=0.
|
|
18
|
+
Requires-Dist: quickscale-core (>=0.86.0,<0.87.0)
|
|
19
19
|
Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
|
|
20
20
|
Project-URL: Homepage, https://github.com/Experto-AI/quickscale
|
|
21
21
|
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.86.0"
|
|
4
4
|
description = "Command-line interface for QuickScale Django project generator"
|
|
5
5
|
requires-python = ">=3.13,<3.15"
|
|
6
6
|
readme = "README.md"
|
|
@@ -32,7 +32,7 @@ packages = [{include = "quickscale_cli", from = "src"}]
|
|
|
32
32
|
[tool.poetry.dependencies]
|
|
33
33
|
python = ">=3.13,<3.15"
|
|
34
34
|
click = "^8.3.1"
|
|
35
|
-
quickscale-core = "^0.
|
|
35
|
+
quickscale-core = "^0.86.0"
|
|
36
36
|
|
|
37
37
|
[tool.poetry.group.dev.dependencies]
|
|
38
38
|
# Dev dependencies centralized in root pyproject.toml
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# Auto-generated by scripts/version_tool.sh
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.86.0"
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/apply_command.py
RENAMED
|
@@ -21,7 +21,14 @@ from quickscale_cli.analytics_contract import (
|
|
|
21
21
|
resolve_analytics_module_options,
|
|
22
22
|
validate_analytics_module_options,
|
|
23
23
|
)
|
|
24
|
-
from quickscale_cli.billing_contract import
|
|
24
|
+
from quickscale_cli.billing_contract import (
|
|
25
|
+
DEFAULT_BILLING_CURRENCY,
|
|
26
|
+
DEFAULT_BILLING_PUBLISHABLE_KEY_ENV_VAR,
|
|
27
|
+
DEFAULT_BILLING_SECRET_KEY_ENV_VAR,
|
|
28
|
+
DEFAULT_BILLING_WEBHOOK_SECRET_ENV_VAR,
|
|
29
|
+
resolve_billing_module_options,
|
|
30
|
+
validate_billing_module_options,
|
|
31
|
+
)
|
|
25
32
|
from quickscale_cli.backups_contract import (
|
|
26
33
|
BACKUPS_REMOTE_ACCESS_KEY_ID_ENV_VAR_OPTION,
|
|
27
34
|
BACKUPS_REMOTE_SECRET_ACCESS_KEY_ENV_VAR_OPTION,
|
|
@@ -40,6 +47,9 @@ from quickscale_cli.notifications_contract import (
|
|
|
40
47
|
resolve_notifications_module_options,
|
|
41
48
|
validate_notifications_module_options,
|
|
42
49
|
)
|
|
50
|
+
from quickscale_cli.commands.implied_module_defaults import (
|
|
51
|
+
get_implied_module_default_configs,
|
|
52
|
+
)
|
|
43
53
|
from quickscale_cli.module_catalog import (
|
|
44
54
|
find_not_ready_modules,
|
|
45
55
|
get_module_readiness_reason,
|
|
@@ -64,6 +74,7 @@ from quickscale_cli.utils.module_dependency_sync import (
|
|
|
64
74
|
from quickscale_cli.utils.module_wiring_manager import regenerate_managed_wiring
|
|
65
75
|
from quickscale_cli.schema.config_schema import (
|
|
66
76
|
ConfigValidationError,
|
|
77
|
+
ModuleConfig,
|
|
67
78
|
QuickScaleConfig,
|
|
68
79
|
generate_yaml,
|
|
69
80
|
validate_config,
|
|
@@ -681,6 +692,21 @@ def _sanitize_loaded_module_configs(qs_config: QuickScaleConfig) -> list[str]:
|
|
|
681
692
|
return sanitized_modules
|
|
682
693
|
|
|
683
694
|
|
|
695
|
+
def _materialize_implied_module_configs(qs_config: QuickScaleConfig) -> list[str]:
|
|
696
|
+
"""Add explicit module config blocks required by selected modules."""
|
|
697
|
+
implied_configs = get_implied_module_default_configs(qs_config.modules.keys())
|
|
698
|
+
if not implied_configs:
|
|
699
|
+
return []
|
|
700
|
+
|
|
701
|
+
for module_name, options in implied_configs.items():
|
|
702
|
+
qs_config.modules[module_name] = ModuleConfig(
|
|
703
|
+
name=module_name,
|
|
704
|
+
options=sanitize_module_options(module_name, options),
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
return list(implied_configs.keys())
|
|
708
|
+
|
|
709
|
+
|
|
684
710
|
def _load_and_validate_config(config_path: Path) -> QuickScaleConfig:
|
|
685
711
|
"""Load and validate configuration from file."""
|
|
686
712
|
if not config_path.exists():
|
|
@@ -699,16 +725,24 @@ def _load_and_validate_config(config_path: Path) -> QuickScaleConfig:
|
|
|
699
725
|
qs_config = validate_config(yaml_content)
|
|
700
726
|
original_data = yaml.safe_load(yaml_content) or {}
|
|
701
727
|
original_modules = original_data.get("modules") or {}
|
|
702
|
-
_sanitize_loaded_module_configs(qs_config)
|
|
728
|
+
sanitized_modules = _sanitize_loaded_module_configs(qs_config)
|
|
729
|
+
implied_modules = _materialize_implied_module_configs(qs_config)
|
|
703
730
|
normalized_yaml = generate_yaml(qs_config)
|
|
704
731
|
normalized_data = yaml.safe_load(normalized_yaml) or {}
|
|
705
732
|
normalized_modules = normalized_data.get("modules") or {}
|
|
706
733
|
if normalized_modules != original_modules:
|
|
707
734
|
config_path.write_text(normalized_yaml)
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
735
|
+
if sanitized_modules:
|
|
736
|
+
click.secho(
|
|
737
|
+
"✅ Sanitized legacy module config keys in quickscale.yml",
|
|
738
|
+
fg="green",
|
|
739
|
+
)
|
|
740
|
+
if implied_modules:
|
|
741
|
+
click.secho(
|
|
742
|
+
"✅ Added implied module config to quickscale.yml: "
|
|
743
|
+
+ ", ".join(implied_modules),
|
|
744
|
+
fg="green",
|
|
745
|
+
)
|
|
712
746
|
_validate_module_prerequisites(qs_config)
|
|
713
747
|
return qs_config
|
|
714
748
|
except ConfigValidationError as e:
|
|
@@ -721,6 +755,25 @@ def _load_and_validate_config(config_path: Path) -> QuickScaleConfig:
|
|
|
721
755
|
|
|
722
756
|
def _validate_module_prerequisites(qs_config: QuickScaleConfig) -> None:
|
|
723
757
|
"""Validate actionable module-specific prerequisites before apply proceeds."""
|
|
758
|
+
if "orgs" in qs_config.modules and "auth" not in qs_config.modules:
|
|
759
|
+
click.secho(
|
|
760
|
+
"\n❌ Organizations requires the auth module before apply can continue:",
|
|
761
|
+
fg="red",
|
|
762
|
+
err=True,
|
|
763
|
+
)
|
|
764
|
+
click.echo(
|
|
765
|
+
" • Add 'auth' under modules in quickscale.yml before applying orgs.",
|
|
766
|
+
err=True,
|
|
767
|
+
)
|
|
768
|
+
click.echo(
|
|
769
|
+
"\n💡 Organizations relies on the QuickScale auth login flow and "
|
|
770
|
+
"django-allauth account adapter hooks. Re-run 'quickscale plan "
|
|
771
|
+
"--reconfigure --configure-modules' or edit quickscale.yml to add "
|
|
772
|
+
"auth, then re-run 'quickscale apply'.",
|
|
773
|
+
err=True,
|
|
774
|
+
)
|
|
775
|
+
raise click.Abort()
|
|
776
|
+
|
|
724
777
|
billing_config = qs_config.modules.get("billing")
|
|
725
778
|
if billing_config is not None:
|
|
726
779
|
billing_issues = validate_billing_module_options(billing_config.options or {})
|
|
@@ -1044,6 +1097,95 @@ def _sync_analytics_env_example(
|
|
|
1044
1097
|
return True
|
|
1045
1098
|
|
|
1046
1099
|
|
|
1100
|
+
def _render_billing_env_example_block(
|
|
1101
|
+
options: Mapping[str, Any] | None,
|
|
1102
|
+
) -> str:
|
|
1103
|
+
"""Render the managed billing section for `.env.example`."""
|
|
1104
|
+
resolved = resolve_billing_module_options(options)
|
|
1105
|
+
publishable_key_env_var = str(
|
|
1106
|
+
resolved.get(
|
|
1107
|
+
"publishable_key_env_var",
|
|
1108
|
+
DEFAULT_BILLING_PUBLISHABLE_KEY_ENV_VAR,
|
|
1109
|
+
)
|
|
1110
|
+
or DEFAULT_BILLING_PUBLISHABLE_KEY_ENV_VAR
|
|
1111
|
+
).strip()
|
|
1112
|
+
secret_key_env_var = str(
|
|
1113
|
+
resolved.get(
|
|
1114
|
+
"secret_key_env_var",
|
|
1115
|
+
DEFAULT_BILLING_SECRET_KEY_ENV_VAR,
|
|
1116
|
+
)
|
|
1117
|
+
or DEFAULT_BILLING_SECRET_KEY_ENV_VAR
|
|
1118
|
+
).strip()
|
|
1119
|
+
webhook_secret_env_var = str(
|
|
1120
|
+
resolved.get(
|
|
1121
|
+
"webhook_secret_env_var",
|
|
1122
|
+
DEFAULT_BILLING_WEBHOOK_SECRET_ENV_VAR,
|
|
1123
|
+
)
|
|
1124
|
+
or DEFAULT_BILLING_WEBHOOK_SECRET_ENV_VAR
|
|
1125
|
+
).strip()
|
|
1126
|
+
billing_currency = str(
|
|
1127
|
+
resolved.get("billing_currency", DEFAULT_BILLING_CURRENCY)
|
|
1128
|
+
or DEFAULT_BILLING_CURRENCY
|
|
1129
|
+
).strip()
|
|
1130
|
+
|
|
1131
|
+
return "\n".join(
|
|
1132
|
+
[
|
|
1133
|
+
"# QuickScale Billing (managed)",
|
|
1134
|
+
"# Runtime Stripe variables for the backend billing module.",
|
|
1135
|
+
f"# Billing currency from quickscale.yml: {billing_currency}",
|
|
1136
|
+
f"{publishable_key_env_var}=",
|
|
1137
|
+
f"{secret_key_env_var}=",
|
|
1138
|
+
f"{webhook_secret_env_var}=",
|
|
1139
|
+
"# End QuickScale Billing",
|
|
1140
|
+
]
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _sync_billing_env_example(
|
|
1145
|
+
output_path: Path,
|
|
1146
|
+
qs_config: QuickScaleConfig,
|
|
1147
|
+
) -> bool:
|
|
1148
|
+
"""Keep `.env.example` aligned with the billing env-var names."""
|
|
1149
|
+
billing_config = qs_config.modules.get("billing")
|
|
1150
|
+
env_example_path = output_path / ".env.example"
|
|
1151
|
+
if billing_config is None or not env_example_path.exists():
|
|
1152
|
+
return True
|
|
1153
|
+
|
|
1154
|
+
start_marker = "# QuickScale Billing (managed)"
|
|
1155
|
+
end_marker = "# End QuickScale Billing"
|
|
1156
|
+
rendered_block = _render_billing_env_example_block(billing_config.options or {})
|
|
1157
|
+
|
|
1158
|
+
try:
|
|
1159
|
+
content = env_example_path.read_text()
|
|
1160
|
+
except OSError as e:
|
|
1161
|
+
click.secho(
|
|
1162
|
+
f"⚠️ Failed to read .env.example for billing wiring: {e}",
|
|
1163
|
+
fg="yellow",
|
|
1164
|
+
)
|
|
1165
|
+
return False
|
|
1166
|
+
|
|
1167
|
+
if start_marker in content and end_marker in content:
|
|
1168
|
+
before, remainder = content.split(start_marker, maxsplit=1)
|
|
1169
|
+
_, after = remainder.split(end_marker, maxsplit=1)
|
|
1170
|
+
replacement = rendered_block + after
|
|
1171
|
+
updated_content = before + replacement
|
|
1172
|
+
else:
|
|
1173
|
+
suffix = "" if content.endswith("\n") else "\n"
|
|
1174
|
+
updated_content = content + suffix + "\n" + rendered_block + "\n"
|
|
1175
|
+
|
|
1176
|
+
try:
|
|
1177
|
+
env_example_path.write_text(updated_content)
|
|
1178
|
+
except OSError as e:
|
|
1179
|
+
click.secho(
|
|
1180
|
+
f"⚠️ Failed to update .env.example for billing wiring: {e}",
|
|
1181
|
+
fg="yellow",
|
|
1182
|
+
)
|
|
1183
|
+
return False
|
|
1184
|
+
|
|
1185
|
+
click.secho("✅ Updated .env.example with billing env vars", fg="green")
|
|
1186
|
+
return True
|
|
1187
|
+
|
|
1188
|
+
|
|
1047
1189
|
def _normalize_backups_gitignore_entry(local_directory: str) -> str | None:
|
|
1048
1190
|
"""Return a safe repo-relative ignore entry for backup artifacts."""
|
|
1049
1191
|
if any(
|
|
@@ -2219,6 +2361,14 @@ def _execute_apply_steps(
|
|
|
2219
2361
|
reason="Unable to update .env.example with the configured analytics env-var names.",
|
|
2220
2362
|
)
|
|
2221
2363
|
|
|
2364
|
+
if not _sync_billing_env_example(ctx.output_path, ctx.qs_config):
|
|
2365
|
+
_abort_after_post_embed_failure(
|
|
2366
|
+
ctx,
|
|
2367
|
+
embedded_modules,
|
|
2368
|
+
failed_step="billing env example sync",
|
|
2369
|
+
reason="Unable to update .env.example with the configured billing env-var names.",
|
|
2370
|
+
)
|
|
2371
|
+
|
|
2222
2372
|
if not _sync_project_module_dependencies_for_apply(
|
|
2223
2373
|
ctx.output_path,
|
|
2224
2374
|
ctx.qs_config,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Helpers for materializing implicit module configs in CLI flows."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from quickscale_cli.commands.module_config import get_default_notifications_config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_implied_module_default_configs(
|
|
10
|
+
module_names: Collection[str],
|
|
11
|
+
) -> dict[str, dict[str, Any]]:
|
|
12
|
+
"""Return module config blocks that should be made explicit automatically."""
|
|
13
|
+
names = set(module_names)
|
|
14
|
+
implied_configs: dict[str, dict[str, Any]] = {}
|
|
15
|
+
|
|
16
|
+
if "billing" in names and "orgs" not in names:
|
|
17
|
+
implied_configs["orgs"] = {}
|
|
18
|
+
names.add("orgs")
|
|
19
|
+
|
|
20
|
+
if "orgs" in names and "notifications" not in names:
|
|
21
|
+
implied_configs["notifications"] = get_default_notifications_config()
|
|
22
|
+
|
|
23
|
+
return implied_configs
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_config.py
RENAMED
|
@@ -1973,6 +1973,16 @@ def apply_social_configuration(
|
|
|
1973
1973
|
click.echo(" • Providers: " + ", ".join(list(resolved["provider_allowlist"])))
|
|
1974
1974
|
|
|
1975
1975
|
|
|
1976
|
+
# ============================================================================
|
|
1977
|
+
# ORGS MODULE
|
|
1978
|
+
# ============================================================================
|
|
1979
|
+
|
|
1980
|
+
|
|
1981
|
+
def get_default_orgs_config() -> dict[str, Any]:
|
|
1982
|
+
"""Return default configuration for the orgs module."""
|
|
1983
|
+
return {"mode": "solo"}
|
|
1984
|
+
|
|
1985
|
+
|
|
1976
1986
|
# ============================================================================
|
|
1977
1987
|
# MODULE CONFIGURATORS REGISTRY
|
|
1978
1988
|
# ============================================================================
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_wiring_specs.py
RENAMED
|
@@ -16,6 +16,13 @@ from quickscale_cli.backups_contract import (
|
|
|
16
16
|
DEFAULT_BACKUPS_REMOTE_SECRET_ACCESS_KEY_ENV_VAR,
|
|
17
17
|
normalize_backups_module_options,
|
|
18
18
|
)
|
|
19
|
+
from quickscale_cli.billing_contract import (
|
|
20
|
+
DEFAULT_BILLING_CURRENCY,
|
|
21
|
+
DEFAULT_BILLING_PUBLISHABLE_KEY_ENV_VAR,
|
|
22
|
+
DEFAULT_BILLING_SECRET_KEY_ENV_VAR,
|
|
23
|
+
DEFAULT_BILLING_WEBHOOK_SECRET_ENV_VAR,
|
|
24
|
+
normalize_billing_module_options,
|
|
25
|
+
)
|
|
19
26
|
from quickscale_cli.notifications_contract import (
|
|
20
27
|
NOTIFICATIONS_LIVE_EMAIL_BACKEND,
|
|
21
28
|
notifications_runtime_email_backend,
|
|
@@ -106,6 +113,25 @@ def _auth_wiring(options: Mapping[str, Any]) -> ModuleWiringSpec:
|
|
|
106
113
|
)
|
|
107
114
|
|
|
108
115
|
|
|
116
|
+
def _orgs_wiring(options: Mapping[str, Any]) -> ModuleWiringSpec:
|
|
117
|
+
mode = str(options.get("mode", "solo")).strip().lower() or "solo"
|
|
118
|
+
if mode not in {"solo", "saas"}:
|
|
119
|
+
mode = "solo"
|
|
120
|
+
|
|
121
|
+
root_include = (("", "quickscale_modules_orgs.urls"),)
|
|
122
|
+
|
|
123
|
+
return ModuleWiringSpec(
|
|
124
|
+
apps=("quickscale_modules_orgs",),
|
|
125
|
+
middleware=("quickscale_modules_orgs.middleware.TenantMiddleware",),
|
|
126
|
+
settings={
|
|
127
|
+
"ACCOUNT_ADAPTER": "quickscale_modules_orgs.adapters.OrgsAccountAdapter",
|
|
128
|
+
"QUICKSCALE_MODE": mode,
|
|
129
|
+
},
|
|
130
|
+
pre_home_url_includes=root_include if mode == "solo" else (),
|
|
131
|
+
url_includes=root_include if mode == "saas" else (),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
109
135
|
def _blog_wiring(options: Mapping[str, Any]) -> ModuleWiringSpec:
|
|
110
136
|
posts_per_page = int(options.get("posts_per_page", 10))
|
|
111
137
|
enable_rss = bool(options.get("enable_rss", True))
|
|
@@ -607,8 +633,36 @@ def _social_wiring(
|
|
|
607
633
|
)
|
|
608
634
|
|
|
609
635
|
|
|
636
|
+
def _billing_wiring(options: Mapping[str, Any]) -> ModuleWiringSpec:
|
|
637
|
+
opts = normalize_billing_module_options(options)
|
|
638
|
+
|
|
639
|
+
settings: dict[str, Any] = {
|
|
640
|
+
"QUICKSCALE_BILLING_ENABLED": bool(opts.get("enabled", True)),
|
|
641
|
+
"QUICKSCALE_BILLING_PUBLISHABLE_KEY_ENV_VAR": str(
|
|
642
|
+
opts.get("publishable_key_env_var", DEFAULT_BILLING_PUBLISHABLE_KEY_ENV_VAR)
|
|
643
|
+
),
|
|
644
|
+
"QUICKSCALE_BILLING_SECRET_KEY_ENV_VAR": str(
|
|
645
|
+
opts.get("secret_key_env_var", DEFAULT_BILLING_SECRET_KEY_ENV_VAR)
|
|
646
|
+
),
|
|
647
|
+
"QUICKSCALE_BILLING_WEBHOOK_SECRET_ENV_VAR": str(
|
|
648
|
+
opts.get("webhook_secret_env_var", DEFAULT_BILLING_WEBHOOK_SECRET_ENV_VAR)
|
|
649
|
+
),
|
|
650
|
+
"QUICKSCALE_BILLING_CURRENCY": str(
|
|
651
|
+
opts.get("billing_currency", DEFAULT_BILLING_CURRENCY)
|
|
652
|
+
),
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return ModuleWiringSpec(
|
|
656
|
+
apps=("rest_framework", "quickscale_modules_billing"),
|
|
657
|
+
settings=settings,
|
|
658
|
+
url_includes=(("", "quickscale_modules_billing.urls"),),
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
|
|
610
662
|
MODULE_WIRING_BUILDERS = {
|
|
611
663
|
"auth": _auth_wiring,
|
|
664
|
+
"orgs": _orgs_wiring,
|
|
665
|
+
"billing": _billing_wiring,
|
|
612
666
|
"blog": _blog_wiring,
|
|
613
667
|
"listings": _listings_wiring,
|
|
614
668
|
"crm": _crm_wiring,
|
|
@@ -13,6 +13,9 @@ import click
|
|
|
13
13
|
|
|
14
14
|
from quickscale_cli.auth_contract import format_auth_desired_config_contract
|
|
15
15
|
from quickscale_cli.backups_contract import sanitize_module_options
|
|
16
|
+
from quickscale_cli.commands.implied_module_defaults import (
|
|
17
|
+
get_implied_module_default_configs,
|
|
18
|
+
)
|
|
16
19
|
from quickscale_cli.commands.module_config import MODULE_CONFIGURATORS
|
|
17
20
|
from quickscale_cli.module_catalog import (
|
|
18
21
|
ModuleCatalogEntry,
|
|
@@ -236,6 +239,32 @@ def _merge_module_names(*module_groups: list[str]) -> list[str]:
|
|
|
236
239
|
return merged
|
|
237
240
|
|
|
238
241
|
|
|
242
|
+
def _materialize_implied_module_configs(
|
|
243
|
+
module_names: list[str],
|
|
244
|
+
module_options: dict[str, dict[str, Any]],
|
|
245
|
+
) -> tuple[list[str], dict[str, dict[str, Any]], list[str]]:
|
|
246
|
+
"""Add explicit config blocks for modules that are implied by selection."""
|
|
247
|
+
present_module_names = _merge_module_names(
|
|
248
|
+
module_names, list(module_options.keys())
|
|
249
|
+
)
|
|
250
|
+
implied_configs = get_implied_module_default_configs(present_module_names)
|
|
251
|
+
if not implied_configs:
|
|
252
|
+
return present_module_names, module_options, []
|
|
253
|
+
|
|
254
|
+
merged_module_names = _merge_module_names(
|
|
255
|
+
present_module_names,
|
|
256
|
+
list(implied_configs.keys()),
|
|
257
|
+
)
|
|
258
|
+
merged_module_options = dict(module_options)
|
|
259
|
+
for module_name, options in implied_configs.items():
|
|
260
|
+
merged_module_options[module_name] = _copy_module_options(
|
|
261
|
+
options,
|
|
262
|
+
module_name=module_name,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return merged_module_names, merged_module_options, list(implied_configs.keys())
|
|
266
|
+
|
|
267
|
+
|
|
239
268
|
def _get_existing_module_options(
|
|
240
269
|
project_path: Path,
|
|
241
270
|
existing_config: QuickScaleConfig | None,
|
|
@@ -655,6 +684,15 @@ def _handle_add_modules(
|
|
|
655
684
|
for module_name in new_modules:
|
|
656
685
|
existing_options.setdefault(module_name, {})
|
|
657
686
|
|
|
687
|
+
all_modules, existing_options, implied_modules = (
|
|
688
|
+
_materialize_implied_module_configs(
|
|
689
|
+
all_modules,
|
|
690
|
+
existing_options,
|
|
691
|
+
)
|
|
692
|
+
)
|
|
693
|
+
if implied_modules:
|
|
694
|
+
new_modules = _merge_module_names(new_modules, implied_modules)
|
|
695
|
+
|
|
658
696
|
if existing_config is not None:
|
|
659
697
|
docker_config = existing_config.docker
|
|
660
698
|
project_config = existing_config.project
|
|
@@ -816,6 +854,11 @@ def _handle_reconfigure(
|
|
|
816
854
|
)
|
|
817
855
|
)
|
|
818
856
|
|
|
857
|
+
all_modules, module_options, _ = _materialize_implied_module_configs(
|
|
858
|
+
all_modules,
|
|
859
|
+
module_options,
|
|
860
|
+
)
|
|
861
|
+
|
|
819
862
|
# Configure Docker
|
|
820
863
|
docker_start, docker_build, docker_create_superuser = _configure_docker()
|
|
821
864
|
|
|
@@ -1134,6 +1177,11 @@ def plan(
|
|
|
1134
1177
|
)
|
|
1135
1178
|
)
|
|
1136
1179
|
|
|
1180
|
+
selected_modules, module_options, _ = _materialize_implied_module_configs(
|
|
1181
|
+
selected_modules,
|
|
1182
|
+
module_options,
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1137
1185
|
# Build configuration
|
|
1138
1186
|
modules = _build_module_configs(selected_modules, module_options)
|
|
1139
1187
|
|
|
@@ -70,6 +70,11 @@ MODULE_CATALOG: tuple[ModuleCatalogEntry, ...] = (
|
|
|
70
70
|
description="Stripe integration with credits-first pricing and dashboard routes",
|
|
71
71
|
ready=True,
|
|
72
72
|
),
|
|
73
|
+
ModuleCatalogEntry(
|
|
74
|
+
name="orgs",
|
|
75
|
+
description="Organizations and multi-tenant foundations with memberships and invitations",
|
|
76
|
+
ready=True,
|
|
77
|
+
),
|
|
73
78
|
ModuleCatalogEntry(
|
|
74
79
|
name="teams",
|
|
75
80
|
description="Multi-tenancy and team management",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/deployment_commands.py
RENAMED
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/development_commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/module_commands.py
RENAMED
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/remove_command.py
RENAMED
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/commands/status_command.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/notifications_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/dependency_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/module_dependency_sync.py
RENAMED
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/module_wiring_manager.py
RENAMED
|
File without changes
|
{quickscale_cli-0.85.0 → quickscale_cli-0.86.0}/src/quickscale_cli/utils/project_identity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|