quickscale-cli 0.83.0__tar.gz → 0.84.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/PKG-INFO +4 -3
  2. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/pyproject.toml +5 -4
  3. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/_version.py +1 -1
  4. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/development_commands.py +33 -8
  5. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/dependency_utils.py +8 -25
  6. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/docker_utils.py +12 -10
  7. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/README.md +0 -0
  8. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/__init__.py +0 -0
  9. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/analytics_contract.py +0 -0
  10. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/auth_contract.py +0 -0
  11. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/backups_contract.py +0 -0
  12. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/beta_migration.py +0 -0
  13. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/__init__.py +0 -0
  14. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/apply_command.py +0 -0
  15. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/deployment_commands.py +0 -0
  16. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/dr_commands.py +0 -0
  17. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/module_commands.py +0 -0
  18. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/module_config.py +0 -0
  19. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/module_wiring_specs.py +0 -0
  20. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/plan_command.py +0 -0
  21. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/remove_command.py +0 -0
  22. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/commands/status_command.py +0 -0
  23. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/crm_contract.py +0 -0
  24. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/main.py +0 -0
  25. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/module_catalog.py +0 -0
  26. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/notifications_contract.py +0 -0
  27. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/schema/__init__.py +0 -0
  28. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/schema/config_schema.py +0 -0
  29. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/schema/delta.py +0 -0
  30. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/schema/state_schema.py +0 -0
  31. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/social_contract.py +0 -0
  32. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/__init__.py +0 -0
  33. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/module_dependency_sync.py +0 -0
  34. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/module_wiring_manager.py +0 -0
  35. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/project_identity.py +0 -0
  36. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/project_manager.py +0 -0
  37. {quickscale_cli-0.83.0 → quickscale_cli-0.84.0}/src/quickscale_cli/utils/railway_utils.py +0 -0
@@ -1,20 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickscale-cli
3
- Version: 0.83.0
3
+ Version: 0.84.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.12,<3.14
9
+ Requires-Python: >=3.13,<3.15
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Classifier: Programming Language :: Python :: 3.14
14
15
  Classifier: Framework :: Django
15
16
  Classifier: Framework :: Django :: 6.0
16
17
  Requires-Dist: click (>=8.3.1,<9.0.0)
17
- Requires-Dist: quickscale-core (>=0.83.0,<0.84.0)
18
+ Requires-Dist: quickscale-core (>=0.84.0,<0.85.0)
18
19
  Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
19
20
  Project-URL: Homepage, https://github.com/Experto-AI/quickscale
20
21
  Project-URL: Repository, https://github.com/Experto-AI/quickscale
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "quickscale-cli"
3
- version = "0.83.0"
3
+ version = "0.84.0"
4
4
  description = "Command-line interface for QuickScale Django project generator"
5
- requires-python = ">=3.12,<3.14"
5
+ requires-python = ">=3.13,<3.15"
6
6
  readme = "README.md"
7
7
  license = "Apache-2.0"
8
8
  authors = [{name = "Experto AI", email = "victor@experto.ai"}]
@@ -11,6 +11,7 @@ classifiers = [
11
11
  "Development Status :: 3 - Alpha",
12
12
  "Intended Audience :: Developers",
13
13
  "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.13",
14
15
  "Programming Language :: Python :: 3.14",
15
16
  "Framework :: Django",
16
17
  "Framework :: Django :: 6.0",
@@ -29,9 +30,9 @@ quickscale = "quickscale_cli.main:cli"
29
30
  packages = [{include = "quickscale_cli", from = "src"}]
30
31
 
31
32
  [tool.poetry.dependencies]
32
- python = ">=3.12,<3.14"
33
+ python = ">=3.13,<3.15"
33
34
  click = "^8.3.1"
34
- quickscale-core = "^0.83.0"
35
+ quickscale-core = "^0.84.0"
35
36
 
36
37
  [tool.poetry.group.dev.dependencies]
37
38
  # Dev dependencies centralized in root pyproject.toml
@@ -1,2 +1,2 @@
1
1
  # Auto-generated by scripts/version_tool.sh
2
- __version__ = "0.83.0"
2
+ __version__ = "0.84.0"
@@ -10,6 +10,7 @@ import click
10
10
 
11
11
  from quickscale_cli.schema.config_schema import QuickScaleConfig
12
12
  from quickscale_cli.utils.docker_utils import (
13
+ DockerComposePluginRequiredError,
13
14
  get_docker_compose_command,
14
15
  get_port_from_env,
15
16
  is_docker_running,
@@ -74,8 +75,32 @@ def _show_port_conflict_error(port: int | str) -> None:
74
75
  )
75
76
 
76
77
 
78
+ def _show_compose_v2_required_error() -> None:
79
+ """Display a user-friendly error when Docker Compose v2 is unavailable."""
80
+ click.secho(
81
+ "❌ Error: Docker Compose v2 plugin is required",
82
+ fg="red",
83
+ err=True,
84
+ )
85
+ click.echo(
86
+ "💡 Tip: Install or update Docker so the 'docker compose' command is "
87
+ "available. QuickScale keeps the generated 'docker-compose.yml' file "
88
+ "name, but the CLI uses the Docker Compose v2 plugin.",
89
+ err=True,
90
+ )
91
+
92
+
93
+ def _require_docker_compose_command() -> list[str]:
94
+ """Return the Docker Compose v2 command or exit with remediation text."""
95
+ try:
96
+ return get_docker_compose_command()
97
+ except DockerComposePluginRequiredError:
98
+ _show_compose_v2_required_error()
99
+ sys.exit(1)
100
+
101
+
77
102
  def _run_docker_compose_up(compose_cmd: list, build: bool, no_cache: bool) -> None:
78
- """Execute docker-compose up with appropriate flags."""
103
+ """Execute docker compose up with appropriate flags."""
79
104
  if build or no_cache:
80
105
  cmd = compose_cmd + ["--progress", "plain", "up", "-d"]
81
106
  else:
@@ -101,7 +126,7 @@ def _run_docker_compose_up(compose_cmd: list, build: bool, no_cache: bool) -> No
101
126
 
102
127
 
103
128
  def _handle_up_error(error: subprocess.CalledProcessError) -> None:
104
- """Handle docker-compose up errors with user-friendly messages."""
129
+ """Handle docker compose up errors with user-friendly messages."""
105
130
  error_output = error.stderr if error.stderr else ""
106
131
  stdout_output = error.stdout if error.stdout else ""
107
132
  full_output = error_output + stdout_output
@@ -122,7 +147,7 @@ def _handle_up_error(error: subprocess.CalledProcessError) -> None:
122
147
  click.echo(
123
148
  f"\n💡 To resolve this issue, try one of the following:\n"
124
149
  f" 1. Stop existing containers: quickscale down\n"
125
- f" 2. Remove orphaned containers: docker-compose down --remove-orphans\n"
150
+ f" 2. Remove orphaned containers: docker compose down --remove-orphans\n"
126
151
  f" 3. Find and kill the process: lsof -ti:{conflict_port} | xargs kill -9\n"
127
152
  f" 4. Find process details: sudo lsof -i:{conflict_port}\n"
128
153
  f" 5. Or use: sudo fuser -k {conflict_port}/tcp",
@@ -272,14 +297,14 @@ def up(build: bool, no_cache: bool) -> None:
272
297
  )
273
298
  click.secho(" quickscale down && quickscale up --build\n", fg="cyan")
274
299
 
275
- # Check if required port is available BEFORE calling docker-compose
300
+ # Check if required port is available BEFORE calling docker compose.
276
301
  port = get_port_from_env()
277
302
  if not is_port_available(port):
278
303
  _show_port_conflict_error(port)
279
304
  sys.exit(1)
280
305
 
281
306
  try:
282
- compose_cmd = get_docker_compose_command()
307
+ compose_cmd = _require_docker_compose_command()
283
308
  _run_docker_compose_up(compose_cmd, should_build, no_cache)
284
309
 
285
310
  # Update build timestamp if build was performed
@@ -326,7 +351,7 @@ def down(volumes: bool) -> None:
326
351
  _validate_project_and_docker()
327
352
 
328
353
  try:
329
- compose_cmd = get_docker_compose_command()
354
+ compose_cmd = _require_docker_compose_command()
330
355
  cmd = compose_cmd + ["down", "--remove-orphans"]
331
356
 
332
357
  if volumes:
@@ -468,7 +493,7 @@ def logs(service: str | None, follow: bool, tail: str | None, timestamps: bool)
468
493
  _validate_project_and_docker()
469
494
 
470
495
  try:
471
- compose_cmd = get_docker_compose_command()
496
+ compose_cmd = _require_docker_compose_command()
472
497
  cmd = compose_cmd + ["logs"]
473
498
 
474
499
  if follow:
@@ -503,7 +528,7 @@ def ps() -> None:
503
528
  _validate_project_and_docker()
504
529
 
505
530
  try:
506
- compose_cmd = get_docker_compose_command()
531
+ compose_cmd = _require_docker_compose_command()
507
532
  cmd = compose_cmd + ["ps"]
508
533
  subprocess.run(cmd, check=True)
509
534
 
@@ -42,13 +42,13 @@ class DependencyStatus(NamedTuple):
42
42
 
43
43
 
44
44
  def check_python_version() -> DependencyStatus:
45
- """Check Python version (3.14+ required)."""
45
+ """Check Python version (3.13+ required)."""
46
46
  import sys
47
47
 
48
48
  version = (
49
49
  f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
50
50
  )
51
- meets_requirement = sys.version_info >= (3, 14)
51
+ meets_requirement = sys.version_info >= (3, 13)
52
52
 
53
53
  return DependencyStatus(
54
54
  name="Python",
@@ -151,7 +151,7 @@ def check_git_installed() -> DependencyStatus:
151
151
 
152
152
 
153
153
  def check_docker_installed() -> DependencyStatus:
154
- """Check if Docker and Docker Compose are installed."""
154
+ """Check if Docker and the Docker Compose v2 plugin are installed."""
155
155
 
156
156
  if _should_skip_dependency_checks():
157
157
  return _skipped_dependency(
@@ -176,45 +176,28 @@ def check_docker_installed() -> DependencyStatus:
176
176
  parts = version_str.split()
177
177
  version = parts[2].rstrip(",") if len(parts) >= 3 else None
178
178
 
179
- # Also check for Docker Compose (either v2 plugin or v1 standalone)
180
- compose_available = False
179
+ # Docker-backed workflows require the Compose v2 plugin.
181
180
  try:
182
- # Try docker compose v2 (plugin)
183
181
  subprocess.run(
184
182
  ["docker", "compose", "version"],
185
183
  capture_output=True,
186
184
  check=True,
187
185
  timeout=2,
188
186
  )
189
- compose_available = True
190
187
  except (
191
188
  subprocess.SubprocessError,
192
189
  FileNotFoundError,
193
190
  subprocess.TimeoutExpired,
194
191
  ):
195
- # Try docker-compose v1 (standalone)
196
- try:
197
- subprocess.run(
198
- ["docker-compose", "--version"],
199
- capture_output=True,
200
- check=True,
201
- timeout=2,
202
- )
203
- compose_available = True
204
- except (
205
- subprocess.SubprocessError,
206
- FileNotFoundError,
207
- subprocess.TimeoutExpired,
208
- ):
209
- pass
210
-
211
- if not compose_available:
212
192
  return DependencyStatus(
213
193
  name="Docker",
214
194
  installed=False,
215
195
  version=version,
216
196
  required=False,
217
- purpose="Containerized development (quickscale up/down) - Docker Compose not found",
197
+ purpose=(
198
+ "Containerized development (quickscale up/down) - "
199
+ "Docker Compose v2 plugin not found"
200
+ ),
218
201
  )
219
202
 
220
203
  return DependencyStatus(
@@ -7,6 +7,10 @@ import time
7
7
  from pathlib import Path
8
8
 
9
9
 
10
+ class DockerComposePluginRequiredError(RuntimeError):
11
+ """Raised when the Docker Compose v2 plugin is unavailable."""
12
+
13
+
10
14
  def is_interactive() -> bool:
11
15
  """Check if running in an interactive terminal (has TTY)."""
12
16
  return sys.stdout.isatty() and sys.stdin.isatty()
@@ -32,24 +36,22 @@ def find_docker_compose() -> Path | None:
32
36
 
33
37
 
34
38
  def get_docker_compose_command() -> list[str]:
35
- """Get the appropriate docker compose command.
36
-
37
- Tries 'docker compose' (v2 plugin) first, falls back to 'docker-compose' (v1 standalone).
38
- Both are fully supported.
39
- """
40
- # Try docker compose first (v2 plugin)
39
+ """Get the Docker Compose v2 command."""
41
40
  try:
42
41
  subprocess.run(
43
42
  ["docker", "compose", "version"], capture_output=True, check=True, timeout=2
44
43
  )
45
- return ["docker", "compose"]
46
44
  except (
47
45
  subprocess.SubprocessError,
48
46
  FileNotFoundError,
49
47
  subprocess.TimeoutExpired,
50
- ):
51
- # Fall back to docker-compose (v1 standalone)
52
- return ["docker-compose"]
48
+ ) as error:
49
+ raise DockerComposePluginRequiredError(
50
+ "Docker Compose v2 is required. Install or update Docker so the "
51
+ "'docker compose' command is available."
52
+ ) from error
53
+
54
+ return ["docker", "compose"]
53
55
 
54
56
 
55
57
  def get_container_status(container_name: str) -> str | None: