quickscale-cli 0.73.0__tar.gz → 0.74.2__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 (24) hide show
  1. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/PKG-INFO +162 -10
  2. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/README.md +160 -8
  3. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/pyproject.toml +7 -2
  4. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/_version.py +1 -1
  5. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/apply_command.py +82 -16
  6. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/development_commands.py +10 -3
  7. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/module_config.py +111 -101
  8. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/plan_command.py +10 -9
  9. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/schema/config_schema.py +3 -2
  10. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/utils/project_manager.py +12 -0
  11. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/__init__.py +0 -0
  12. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/__init__.py +0 -0
  13. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/deployment_commands.py +0 -0
  14. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/module_commands.py +0 -0
  15. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/remove_command.py +0 -0
  16. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/commands/status_command.py +0 -0
  17. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/main.py +0 -0
  18. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/schema/__init__.py +0 -0
  19. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/schema/delta.py +0 -0
  20. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/schema/state_schema.py +0 -0
  21. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/utils/__init__.py +0 -0
  22. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/utils/dependency_utils.py +0 -0
  23. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/src/quickscale_cli/utils/docker_utils.py +0 -0
  24. {quickscale_cli-0.73.0 → quickscale_cli-0.74.2}/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.73.0
3
+ Version: 0.74.2
4
4
  Summary: Command-line interface for QuickScale Django project generator
5
5
  License: Apache-2.0
6
6
  Keywords: django,cli,project-generator,saas
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
19
  Requires-Dist: click (>=8.3.1,<9.0.0)
20
- Requires-Dist: quickscale-core (>=0.73.0,<0.74.0)
20
+ Requires-Dist: quickscale-core (>=0.74.2,<0.75.0)
21
21
  Project-URL: Documentation, https://github.com/Experto-AI/quickscale/tree/main/docs
22
22
  Project-URL: Homepage, https://github.com/Experto-AI/quickscale
23
23
  Project-URL: Repository, https://github.com/Experto-AI/quickscale
@@ -25,6 +25,14 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  # 🚀 QuickScale
27
27
 
28
+ [![PyPI version](https://img.shields.io/pypi/v/quickscale.svg)](https://pypi.org/project/quickscale/)
29
+ [![CI](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml/badge.svg)](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml)
30
+ [![Coverage](https://img.shields.io/badge/coverage-90%25-green.svg)](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml)
31
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
32
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
33
+ [![Version](https://img.shields.io/badge/version-0.74.0-green.svg)](https://github.com/Experto-AI/quickscale/releases)
34
+ [![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
35
+
28
36
  > **You are here**: **QuickScale README** (Project Overview)
29
37
  > **Related docs**: [Start Here](START_HERE.md) | [Glossary](GLOSSARY.md) | [Decisions](docs/technical/decisions.md) | [User Manual](docs/technical/user_manual.md)
30
38
 
@@ -93,10 +101,11 @@ See [decisions.md - Glossary section](./docs/technical/decisions.md#document-res
93
101
 
94
102
  ### Development Flow
95
103
  1. `quickscale plan myapp` → Interactive configuration wizard
96
- 2. `quickscale apply` → Generates production-ready Django project
97
- 3. Add your custom Django apps and features
98
- 4. Build your unique client application
99
- 5. Deploy to Railway with `quickscale deploy railway` (or use standard Django deployment)
104
+ 2. `cd myapp` → Navigate to your project directory
105
+ 3. `quickscale apply` → Generates production-ready Django project
106
+ 4. Add your custom Django apps and features
107
+ 5. Build your unique client application
108
+ 6. Deploy to Railway with `quickscale deploy railway` (or use standard Django deployment)
100
109
 
101
110
  ℹ️ The [MVP Feature Matrix](./docs/technical/decisions.md#mvp-feature-matrix-authoritative) is the single source of truth for what's in or out.
102
111
 
@@ -104,6 +113,7 @@ See [decisions.md - Glossary section](./docs/technical/decisions.md#document-res
104
113
 
105
114
  Running `quickscale plan myapp && quickscale apply` generates a **production-ready Django project** with:
106
115
 
116
+ - ✅ **React + shadcn/ui** frontend (TypeScript, Vite, Tailwind CSS) — **NEW in v0.74.0**
107
117
  - ✅ **Docker** setup (development + production)
108
118
  - ✅ **PostgreSQL** configuration
109
119
  - ✅ **Environment-based** settings (dev/prod split)
@@ -115,6 +125,8 @@ Running `quickscale plan myapp && quickscale apply` generates a **production-rea
115
125
  - ✅ **Poetry** for dependency management
116
126
  - ✅ **One-Command Deployment**: Deploy to Railway with `quickscale deploy railway` - fully automated setup
117
127
 
128
+ **Alternative**: Use `quickscale plan myapp --theme showcase_html` for pure HTML/CSS (simpler projects).
129
+
118
130
  **See the complete project structure:** [scaffolding.md - Generated Project Output](./docs/technical/scaffolding.md#5-generated-project-output)
119
131
 
120
132
  The generated project is **yours to own and modify** - no vendor lock-in, just Django best practices.
@@ -132,8 +144,148 @@ See [competitive_analysis.md](./docs/overview/competitive_analysis.md) for detai
132
144
 
133
145
  ---
134
146
 
147
+ ## Installation
148
+
149
+ QuickScale can be installed in two ways:
150
+
151
+ ### Method 1: Install from PyPI (Recommended)
152
+
153
+ **Quick install:**
154
+ ```bash
155
+ pip install quickscale
156
+ ```
157
+
158
+ **Or with Poetry:**
159
+ ```bash
160
+ poetry add quickscale
161
+ ```
162
+
163
+ **Then use directly:**
164
+ ```bash
165
+ quickscale plan myapp
166
+ cd myapp
167
+ quickscale apply
168
+ ```
169
+
170
+ ### Method 2: Install from Source
171
+
172
+ **For those who prefer building from source:**
173
+ ```bash
174
+ git clone https://github.com/Experto-AI/quickscale.git
175
+ cd quickscale
176
+ ./scripts/install_global.sh
177
+ ```
178
+
179
+ **Then use directly:**
180
+ ```bash
181
+ quickscale plan myapp
182
+ cd myapp
183
+ quickscale apply
184
+ ```
185
+
186
+ **Both methods use the same command syntax:** `quickscale plan`, `quickscale apply`, etc.
187
+
188
+ ---
189
+
190
+ ### For Contributors
191
+
192
+ If you want to contribute to QuickScale development, see the [Development Guide](./docs/technical/development.md) for setup instructions.
193
+
194
+ ---
195
+
196
+ ## 🚀 5-Minute Quickstart
197
+
198
+ **Want to see QuickScale in action right now?** Here's the fastest path to a working Django SaaS:
199
+
200
+ ```bash
201
+ # 1. Install QuickScale
202
+ ./scripts/install_global.sh
203
+
204
+ # 2. Create your project configuration
205
+ quickscale plan myapp
206
+ # → Accept defaults: theme (showcase_html), no modules, Docker enabled
207
+
208
+ # 3. Generate and enter the project
209
+ cd myapp
210
+ quickscale apply
211
+ # → Docker services auto-start! (default: docker.start=true)
212
+
213
+ # 4. Setup and verify
214
+ quickscale manage migrate
215
+ quickscale manage createsuperuser
216
+ quickscale ps # Check services are running
217
+
218
+ # 5. Open http://localhost:8000
219
+ ```
220
+
221
+ **That's it!** 🎉 You now have a production-ready Django SaaS running with Docker + PostgreSQL.
222
+
223
+ **Note**: Services started automatically during `quickscale apply`. No need to run `quickscale up` manually!
224
+
225
+ **Prefer native Python?** After step 3, run:
226
+ ```bash
227
+ poetry install
228
+ poetry run python manage.py migrate
229
+ poetry run python manage.py runserver
230
+ # Open http://localhost:8000
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Understanding Docker Auto-Start
236
+
237
+ **When does `quickscale apply` start Docker automatically?**
238
+
239
+ ✅ **Services auto-start** (no `quickscale up` needed):
240
+ - First-time project generation
241
+ - When `docker.start: true` in quickscale.yml (**default**)
242
+ - When `--no-docker` flag is NOT used
243
+
244
+ ❌ **Manual start required** (`quickscale up` needed):
245
+ - You set `docker.start: false` during `quickscale plan` wizard
246
+ - After stopping services with `quickscale down`
247
+ - When running `quickscale apply --no-docker`
248
+ - Adding modules to existing project (incremental apply)
135
249
 
136
- ## Quick Start
250
+ **Example - No manual start needed:**
251
+ ```bash
252
+ quickscale plan myapp # Accept defaults (docker.start: true)
253
+ cd myapp
254
+ quickscale apply # ← Docker services auto-start!
255
+
256
+ # Already running - just check status:
257
+ quickscale ps
258
+ curl http://localhost:8000 # ✅ Works immediately
259
+ ```
260
+
261
+ **Example - Manual start needed:**
262
+ ```bash
263
+ quickscale plan myapp
264
+ # During wizard, set: "Docker start? [Y/n]: n"
265
+ cd myapp
266
+ quickscale apply # Services do NOT start
267
+
268
+ # Must start manually:
269
+ quickscale up
270
+ ```
271
+
272
+ **Docker configuration options** in quickscale.yml:
273
+
274
+ ```yaml
275
+ docker:
276
+ start: true # Auto-start services during apply?
277
+ build: true # Rebuild images? (slower but ensures latest)
278
+ ```
279
+
280
+ - `start: true` + `build: true` → Full rebuild + start (slowest, most reliable)
281
+ - `start: true` + `build: false` → Start with cached images (faster)
282
+ - `start: false` → Manual control with `quickscale up`
283
+
284
+ **Need more details?** See [Docker Workflows Guide](./docs/technical/docker_workflows.md) for comprehensive scenarios and troubleshooting.
285
+
286
+ ---
287
+
288
+ ## Full Documentation & Setup
137
289
 
138
290
  ```bash
139
291
  # Install QuickScale globally
@@ -142,11 +294,11 @@ See [competitive_analysis.md](./docs/overview/competitive_analysis.md) for detai
142
294
  # Create a configuration interactively
143
295
  quickscale plan myapp
144
296
  # → Select theme, modules, Docker options
145
- # → Generates quickscale.yml
297
+ # → Generates quickscale.yml in myapp/ directory
146
298
 
147
- # Execute the configuration
148
- quickscale apply
299
+ # Navigate to project and execute the configuration
149
300
  cd myapp
301
+ quickscale apply
150
302
  ```
151
303
 
152
304
  **Choose your development workflow:**
@@ -1,5 +1,13 @@
1
1
  # 🚀 QuickScale
2
2
 
3
+ [![PyPI version](https://img.shields.io/pypi/v/quickscale.svg)](https://pypi.org/project/quickscale/)
4
+ [![CI](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml/badge.svg)](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml)
5
+ [![Coverage](https://img.shields.io/badge/coverage-90%25-green.svg)](https://github.com/Experto-AI/quickscale/actions/workflows/ci.yml)
6
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
7
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
+ [![Version](https://img.shields.io/badge/version-0.74.0-green.svg)](https://github.com/Experto-AI/quickscale/releases)
9
+ [![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
+
3
11
  > **You are here**: **QuickScale README** (Project Overview)
4
12
  > **Related docs**: [Start Here](START_HERE.md) | [Glossary](GLOSSARY.md) | [Decisions](docs/technical/decisions.md) | [User Manual](docs/technical/user_manual.md)
5
13
 
@@ -68,10 +76,11 @@ See [decisions.md - Glossary section](./docs/technical/decisions.md#document-res
68
76
 
69
77
  ### Development Flow
70
78
  1. `quickscale plan myapp` → Interactive configuration wizard
71
- 2. `quickscale apply` → Generates production-ready Django project
72
- 3. Add your custom Django apps and features
73
- 4. Build your unique client application
74
- 5. Deploy to Railway with `quickscale deploy railway` (or use standard Django deployment)
79
+ 2. `cd myapp` → Navigate to your project directory
80
+ 3. `quickscale apply` → Generates production-ready Django project
81
+ 4. Add your custom Django apps and features
82
+ 5. Build your unique client application
83
+ 6. Deploy to Railway with `quickscale deploy railway` (or use standard Django deployment)
75
84
 
76
85
  ℹ️ The [MVP Feature Matrix](./docs/technical/decisions.md#mvp-feature-matrix-authoritative) is the single source of truth for what's in or out.
77
86
 
@@ -79,6 +88,7 @@ See [decisions.md - Glossary section](./docs/technical/decisions.md#document-res
79
88
 
80
89
  Running `quickscale plan myapp && quickscale apply` generates a **production-ready Django project** with:
81
90
 
91
+ - ✅ **React + shadcn/ui** frontend (TypeScript, Vite, Tailwind CSS) — **NEW in v0.74.0**
82
92
  - ✅ **Docker** setup (development + production)
83
93
  - ✅ **PostgreSQL** configuration
84
94
  - ✅ **Environment-based** settings (dev/prod split)
@@ -90,6 +100,8 @@ Running `quickscale plan myapp && quickscale apply` generates a **production-rea
90
100
  - ✅ **Poetry** for dependency management
91
101
  - ✅ **One-Command Deployment**: Deploy to Railway with `quickscale deploy railway` - fully automated setup
92
102
 
103
+ **Alternative**: Use `quickscale plan myapp --theme showcase_html` for pure HTML/CSS (simpler projects).
104
+
93
105
  **See the complete project structure:** [scaffolding.md - Generated Project Output](./docs/technical/scaffolding.md#5-generated-project-output)
94
106
 
95
107
  The generated project is **yours to own and modify** - no vendor lock-in, just Django best practices.
@@ -107,8 +119,148 @@ See [competitive_analysis.md](./docs/overview/competitive_analysis.md) for detai
107
119
 
108
120
  ---
109
121
 
122
+ ## Installation
123
+
124
+ QuickScale can be installed in two ways:
125
+
126
+ ### Method 1: Install from PyPI (Recommended)
127
+
128
+ **Quick install:**
129
+ ```bash
130
+ pip install quickscale
131
+ ```
132
+
133
+ **Or with Poetry:**
134
+ ```bash
135
+ poetry add quickscale
136
+ ```
137
+
138
+ **Then use directly:**
139
+ ```bash
140
+ quickscale plan myapp
141
+ cd myapp
142
+ quickscale apply
143
+ ```
144
+
145
+ ### Method 2: Install from Source
146
+
147
+ **For those who prefer building from source:**
148
+ ```bash
149
+ git clone https://github.com/Experto-AI/quickscale.git
150
+ cd quickscale
151
+ ./scripts/install_global.sh
152
+ ```
153
+
154
+ **Then use directly:**
155
+ ```bash
156
+ quickscale plan myapp
157
+ cd myapp
158
+ quickscale apply
159
+ ```
160
+
161
+ **Both methods use the same command syntax:** `quickscale plan`, `quickscale apply`, etc.
162
+
163
+ ---
164
+
165
+ ### For Contributors
166
+
167
+ If you want to contribute to QuickScale development, see the [Development Guide](./docs/technical/development.md) for setup instructions.
168
+
169
+ ---
170
+
171
+ ## 🚀 5-Minute Quickstart
172
+
173
+ **Want to see QuickScale in action right now?** Here's the fastest path to a working Django SaaS:
174
+
175
+ ```bash
176
+ # 1. Install QuickScale
177
+ ./scripts/install_global.sh
178
+
179
+ # 2. Create your project configuration
180
+ quickscale plan myapp
181
+ # → Accept defaults: theme (showcase_html), no modules, Docker enabled
182
+
183
+ # 3. Generate and enter the project
184
+ cd myapp
185
+ quickscale apply
186
+ # → Docker services auto-start! (default: docker.start=true)
187
+
188
+ # 4. Setup and verify
189
+ quickscale manage migrate
190
+ quickscale manage createsuperuser
191
+ quickscale ps # Check services are running
192
+
193
+ # 5. Open http://localhost:8000
194
+ ```
195
+
196
+ **That's it!** 🎉 You now have a production-ready Django SaaS running with Docker + PostgreSQL.
197
+
198
+ **Note**: Services started automatically during `quickscale apply`. No need to run `quickscale up` manually!
199
+
200
+ **Prefer native Python?** After step 3, run:
201
+ ```bash
202
+ poetry install
203
+ poetry run python manage.py migrate
204
+ poetry run python manage.py runserver
205
+ # Open http://localhost:8000
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Understanding Docker Auto-Start
211
+
212
+ **When does `quickscale apply` start Docker automatically?**
213
+
214
+ ✅ **Services auto-start** (no `quickscale up` needed):
215
+ - First-time project generation
216
+ - When `docker.start: true` in quickscale.yml (**default**)
217
+ - When `--no-docker` flag is NOT used
218
+
219
+ ❌ **Manual start required** (`quickscale up` needed):
220
+ - You set `docker.start: false` during `quickscale plan` wizard
221
+ - After stopping services with `quickscale down`
222
+ - When running `quickscale apply --no-docker`
223
+ - Adding modules to existing project (incremental apply)
110
224
 
111
- ## Quick Start
225
+ **Example - No manual start needed:**
226
+ ```bash
227
+ quickscale plan myapp # Accept defaults (docker.start: true)
228
+ cd myapp
229
+ quickscale apply # ← Docker services auto-start!
230
+
231
+ # Already running - just check status:
232
+ quickscale ps
233
+ curl http://localhost:8000 # ✅ Works immediately
234
+ ```
235
+
236
+ **Example - Manual start needed:**
237
+ ```bash
238
+ quickscale plan myapp
239
+ # During wizard, set: "Docker start? [Y/n]: n"
240
+ cd myapp
241
+ quickscale apply # Services do NOT start
242
+
243
+ # Must start manually:
244
+ quickscale up
245
+ ```
246
+
247
+ **Docker configuration options** in quickscale.yml:
248
+
249
+ ```yaml
250
+ docker:
251
+ start: true # Auto-start services during apply?
252
+ build: true # Rebuild images? (slower but ensures latest)
253
+ ```
254
+
255
+ - `start: true` + `build: true` → Full rebuild + start (slowest, most reliable)
256
+ - `start: true` + `build: false` → Start with cached images (faster)
257
+ - `start: false` → Manual control with `quickscale up`
258
+
259
+ **Need more details?** See [Docker Workflows Guide](./docs/technical/docker_workflows.md) for comprehensive scenarios and troubleshooting.
260
+
261
+ ---
262
+
263
+ ## Full Documentation & Setup
112
264
 
113
265
  ```bash
114
266
  # Install QuickScale globally
@@ -117,11 +269,11 @@ See [competitive_analysis.md](./docs/overview/competitive_analysis.md) for detai
117
269
  # Create a configuration interactively
118
270
  quickscale plan myapp
119
271
  # → Select theme, modules, Docker options
120
- # → Generates quickscale.yml
272
+ # → Generates quickscale.yml in myapp/ directory
121
273
 
122
- # Execute the configuration
123
- quickscale apply
274
+ # Navigate to project and execute the configuration
124
275
  cd myapp
276
+ quickscale apply
125
277
  ```
126
278
 
127
279
  **Choose your development workflow:**
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "quickscale-cli"
3
- version = "0.73.0"
3
+ version = "0.74.2"
4
4
  description = "Command-line interface for QuickScale Django project generator"
5
5
  authors = ["Experto AI <victor@experto.ai>"]
6
6
  license = "Apache-2.0"
@@ -25,7 +25,7 @@ packages = [{include = "quickscale_cli", from = "src"}]
25
25
  [tool.poetry.dependencies]
26
26
  python = "^3.12"
27
27
  click = "^8.3.1"
28
- quickscale-core = "^0.73.0"
28
+ quickscale-core = "^0.74.2"
29
29
 
30
30
  [tool.poetry.group.dev.dependencies]
31
31
  # Dev dependencies centralized in root pyproject.toml
@@ -49,6 +49,7 @@ addopts = [
49
49
  "--cov=quickscale_cli",
50
50
  "--cov-report=term-missing",
51
51
  "--cov-report=html",
52
+ "--cov-fail-under=90",
52
53
  "-m", "not e2e", # Skip E2E tests by default (run with: pytest -m e2e)
53
54
  ]
54
55
  markers = [
@@ -57,6 +58,10 @@ markers = [
57
58
  "e2e: end-to-end tests",
58
59
  ]
59
60
 
61
+ [tool.coverage.report]
62
+ fail_under = 90
63
+ # Per-file minimum target: 80% (enforced via code review)
64
+
60
65
  # Ruff and mypy configurations centralized in root ruff.toml and mypy.ini
61
66
  # Package-specific isort override
62
67
  [tool.ruff.lint.isort]
@@ -1,2 +1,2 @@
1
1
  # Auto-generated by scripts/version_tool.sh
2
- __version__ = "0.73.0"
2
+ __version__ = "0.74.2"
@@ -92,18 +92,21 @@ def _generate_project(config: QuickScaleConfig, output_path: Path) -> bool:
92
92
  click.echo(f"⏳ Generating project: {config.project.name}...")
93
93
 
94
94
  # Validate theme availability
95
- if config.project.theme in ["showcase_htmx", "showcase_react"]:
95
+ if config.project.theme == "showcase_htmx":
96
96
  click.secho(
97
97
  f"❌ Error: Theme '{config.project.theme}' is not yet implemented",
98
98
  fg="red",
99
99
  err=True,
100
100
  )
101
101
  click.echo(
102
- f"\n💡 The '{config.project.theme}' theme is planned for a future release:",
102
+ "\n💡 The 'showcase_htmx' theme is planned for a future release:",
103
+ err=True,
104
+ )
105
+ click.echo(" - showcase_htmx: Coming in v0.78.0", err=True)
106
+ click.echo(
107
+ " - Use 'showcase_react' (default) or 'showcase_html' instead",
103
108
  err=True,
104
109
  )
105
- click.echo(" - showcase_htmx: Coming in v0.70.0", err=True)
106
- click.echo(" - showcase_react: Coming in v0.71.0", err=True)
107
110
  return False
108
111
 
109
112
  generator = ProjectGenerator(theme=config.project.theme)
@@ -197,18 +200,54 @@ def _run_migrations(project_path: Path) -> bool:
197
200
  )[0]
198
201
 
199
202
 
200
- def _start_docker(project_path: Path, build: bool = True) -> bool:
201
- """Start Docker services using quickscale up"""
203
+ def _start_docker(
204
+ project_path: Path, build: bool = True, verbose: bool = False
205
+ ) -> bool:
206
+ """Start Docker services using quickscale up
207
+
208
+ Args:
209
+ project_path: Path to the project directory
210
+ build: Whether to build images before starting
211
+ verbose: Whether to show Docker build output (useful for debugging)
212
+
213
+ Returns:
214
+ True if Docker started successfully, False otherwise
215
+ """
202
216
  cmd = ["quickscale", "up"]
203
217
  if build:
204
218
  cmd.append("--build")
205
219
 
206
- success, _ = _run_command(
207
- cmd,
208
- project_path,
209
- "Starting Docker services",
210
- )
211
- return success
220
+ if verbose:
221
+ # Show Docker build output for debugging
222
+ click.echo("⏳ Starting Docker services (showing build output)...")
223
+ click.echo("=" * 50)
224
+ try:
225
+ result = subprocess.run(
226
+ cmd,
227
+ cwd=project_path,
228
+ text=True,
229
+ check=False,
230
+ )
231
+ click.echo("=" * 50)
232
+ if result.returncode == 0:
233
+ click.secho("✅ Starting Docker services", fg="green")
234
+ return True
235
+ else:
236
+ click.secho("❌ Starting Docker services failed", fg="red")
237
+ return False
238
+ except FileNotFoundError:
239
+ click.secho(f"❌ Command not found: {cmd[0]}", fg="red", err=True)
240
+ return False
241
+ except Exception as e:
242
+ click.secho(f"❌ Unexpected error: {e}", fg="red", err=True)
243
+ return False
244
+ else:
245
+ success, _ = _run_command(
246
+ cmd,
247
+ project_path,
248
+ "Starting Docker services",
249
+ )
250
+ return success
212
251
 
213
252
 
214
253
  def _load_module_manifests(
@@ -646,7 +685,11 @@ def _prepare_apply_context(config_path: Path) -> ApplyContext:
646
685
 
647
686
 
648
687
  def _execute_apply_steps(
649
- ctx: ApplyContext, force: bool, no_docker: bool, no_modules: bool
688
+ ctx: ApplyContext,
689
+ force: bool,
690
+ no_docker: bool,
691
+ no_modules: bool,
692
+ verbose_docker: bool = False,
650
693
  ) -> None:
651
694
  """Execute the apply steps after confirmation."""
652
695
  click.echo("\n" + "=" * 50)
@@ -685,7 +728,9 @@ def _execute_apply_steps(
685
728
 
686
729
  # Start Docker
687
730
  if not no_docker and ctx.qs_config.docker.start:
688
- if not _start_docker(ctx.output_path, ctx.qs_config.docker.build):
731
+ if not _start_docker(
732
+ ctx.output_path, ctx.qs_config.docker.build, verbose_docker
733
+ ):
689
734
  click.secho("⚠️ Docker start failed, continuing...", fg="yellow")
690
735
 
691
736
  # Save state
@@ -724,7 +769,14 @@ def _execute_apply_steps(
724
769
  is_flag=True,
725
770
  help="Skip module embedding",
726
771
  )
727
- def apply(config: str, force: bool, no_docker: bool, no_modules: bool) -> None:
772
+ @click.option(
773
+ "--verbose-docker",
774
+ is_flag=True,
775
+ help="Show Docker build output (useful for debugging build issues)",
776
+ )
777
+ def apply(
778
+ config: str, force: bool, no_docker: bool, no_modules: bool, verbose_docker: bool
779
+ ) -> None:
728
780
  """
729
781
  Execute project configuration from quickscale.yml.
730
782
 
@@ -737,6 +789,7 @@ def apply(config: str, force: bool, no_docker: bool, no_modules: bool) -> None:
737
789
  quickscale apply myapp/quickscale.yml # Use specific config file
738
790
  quickscale apply --force # Overwrite existing project
739
791
  quickscale apply --no-docker # Skip Docker operations
792
+ quickscale apply --verbose-docker # Show Docker build output
740
793
 
741
794
  \b
742
795
  Execution Order:
@@ -760,10 +813,23 @@ def apply(config: str, force: bool, no_docker: bool, no_modules: bool) -> None:
760
813
  # Check output directory
761
814
  _check_output_directory(ctx.output_path, ctx.existing_state, force)
762
815
 
816
+ # Ask about Docker build output visibility if Docker will be started
817
+ show_docker_output = verbose_docker
818
+ if (
819
+ not no_docker
820
+ and ctx.qs_config.docker.start
821
+ and ctx.qs_config.docker.build
822
+ and not verbose_docker
823
+ ):
824
+ show_docker_output = click.confirm(
825
+ "\n🐳 Show Docker build output? (useful for debugging build issues)",
826
+ default=False,
827
+ )
828
+
763
829
  # Confirm before proceeding
764
830
  if not click.confirm("\n❓ Proceed with apply?", default=True):
765
831
  click.echo("❌ Cancelled")
766
832
  raise click.Abort()
767
833
 
768
834
  # Execute apply steps
769
- _execute_apply_steps(ctx, force, no_docker, no_modules)
835
+ _execute_apply_steps(ctx, force, no_docker, no_modules, show_docker_output)
@@ -17,6 +17,7 @@ from quickscale_cli.utils.docker_utils import (
17
17
  wait_for_port_release,
18
18
  )
19
19
  from quickscale_cli.utils.project_manager import (
20
+ get_project_config,
20
21
  get_web_container_name,
21
22
  is_in_quickscale_project,
22
23
  )
@@ -135,8 +136,14 @@ def up(build: bool, no_cache: bool) -> None:
135
136
  """Start Docker services for development."""
136
137
  _validate_project_and_docker()
137
138
 
139
+ # Load config to check for default build behavior
140
+ config = get_project_config()
141
+ should_build = build
142
+ if not build and config and config.docker:
143
+ should_build = config.docker.build
144
+
138
145
  # Check if dependencies changed and suggest rebuild
139
- if not build and _dependencies_changed_since_last_build():
146
+ if not should_build and _dependencies_changed_since_last_build():
140
147
  click.secho(
141
148
  "⚠️ Warning: Dependencies may have changed since last Docker build",
142
149
  fg="yellow",
@@ -156,10 +163,10 @@ def up(build: bool, no_cache: bool) -> None:
156
163
 
157
164
  try:
158
165
  compose_cmd = get_docker_compose_command()
159
- _run_docker_compose_up(compose_cmd, build, no_cache)
166
+ _run_docker_compose_up(compose_cmd, should_build, no_cache)
160
167
 
161
168
  # Update build timestamp if build was performed
162
- if build:
169
+ if should_build:
163
170
  _update_last_build_timestamp()
164
171
 
165
172
  except subprocess.CalledProcessError as e:
@@ -7,7 +7,7 @@ including interactive configuration prompts and settings application.
7
7
  import re
8
8
  import subprocess
9
9
  from pathlib import Path
10
- from typing import Any
10
+ from typing import Any, Optional
11
11
 
12
12
  import click
13
13
 
@@ -688,6 +688,50 @@ def configure_crm_module(non_interactive: bool = False) -> dict[str, Any]:
688
688
  return config
689
689
 
690
690
 
691
+ def _get_dependency_version(content: str, package: str) -> Optional[str]:
692
+ """Extract dependency version from pyproject.toml content."""
693
+ match = re.search(
694
+ rf'{package}\s*=\s*["\']([^"\']+)["\']',
695
+ content,
696
+ )
697
+ return match.group(1) if match else None
698
+
699
+
700
+ def _update_pyproject_toml(
701
+ pyproject_path: Path,
702
+ content: str,
703
+ drf_ver: Optional[str],
704
+ filter_ver: Optional[str],
705
+ ) -> None:
706
+ """Update pyproject.toml with new dependencies."""
707
+ dependencies_pattern = r"(\[tool\.poetry\.dependencies\][^\[]*)"
708
+ match = re.search(dependencies_pattern, content, re.DOTALL)
709
+ if not match:
710
+ return
711
+
712
+ dependencies_section = match.group(1)
713
+ additions = ""
714
+ if drf_ver:
715
+ additions += f'\ndjangorestframework = "{drf_ver}"'
716
+ if filter_ver:
717
+ additions += f'\ndjango-filter = "{filter_ver}"'
718
+
719
+ updated_dependencies = re.sub(
720
+ r'(python = "[^"]*")',
721
+ rf"\1{additions}",
722
+ dependencies_section,
723
+ )
724
+ content = content.replace(dependencies_section, updated_dependencies)
725
+
726
+ with open(pyproject_path, "w") as f:
727
+ f.write(content)
728
+
729
+ if drf_ver:
730
+ click.secho(" ✅ Added djangorestframework to pyproject.toml", fg="green")
731
+ if filter_ver:
732
+ click.secho(" ✅ Added django-filter to pyproject.toml", fg="green")
733
+
734
+
691
735
  def _add_drf_and_filter_dependencies(project_path: Path, pyproject_path: Path) -> None:
692
736
  """Add djangorestframework and django-filter dependencies to project's pyproject.toml."""
693
737
  with open(pyproject_path) as f:
@@ -704,78 +748,91 @@ def _add_drf_and_filter_dependencies(project_path: Path, pyproject_path: Path) -
704
748
  err=True,
705
749
  )
706
750
  click.echo(f"Expected file: {crm_pyproject_path}", err=True)
707
- click.echo(
708
- "This indicates the CRM module was not embedded correctly.",
709
- err=True,
710
- )
711
751
  raise click.Abort()
712
752
 
713
753
  try:
714
754
  with open(crm_pyproject_path) as f:
715
755
  crm_pyproject_content = f.read()
716
756
 
717
- # Extract djangorestframework version
718
757
  drf_version = None
719
758
  if "djangorestframework" not in pyproject_content:
720
- drf_match = re.search(
721
- r'djangorestframework\s*=\s*["\']([^"\']+)["\']',
722
- crm_pyproject_content,
759
+ drf_version = _get_dependency_version(
760
+ crm_pyproject_content, "djangorestframework"
723
761
  )
724
- if drf_match:
725
- drf_version = drf_match.group(1)
726
762
 
727
- # Extract django-filter version
728
763
  filter_version = None
729
764
  if "django-filter" not in pyproject_content:
730
- filter_match = re.search(
731
- r'django-filter\s*=\s*["\']([^"\']+)["\']',
732
- crm_pyproject_content,
765
+ filter_version = _get_dependency_version(
766
+ crm_pyproject_content, "django-filter"
733
767
  )
734
- if filter_match:
735
- filter_version = filter_match.group(1)
736
768
 
737
- # Add dependencies if needed
738
769
  if drf_version or filter_version:
739
- dependencies_pattern = r"(\[tool\.poetry\.dependencies\][^\[]*)"
740
- match = re.search(dependencies_pattern, pyproject_content, re.DOTALL)
741
- if match:
742
- dependencies_section = match.group(1)
743
- additions = ""
744
- if drf_version:
745
- additions += f'\ndjangorestframework = "{drf_version}"'
746
- if filter_version:
747
- additions += f'\ndjango-filter = "{filter_version}"'
748
-
749
- updated_dependencies = re.sub(
750
- r'(python = "[^"]*")',
751
- rf"\1{additions}",
752
- dependencies_section,
753
- )
754
- pyproject_content = pyproject_content.replace(
755
- dependencies_section, updated_dependencies
756
- )
770
+ _update_pyproject_toml(
771
+ pyproject_path, pyproject_content, drf_version, filter_version
772
+ )
757
773
 
758
- with open(pyproject_path, "w") as f:
759
- f.write(pyproject_content)
760
-
761
- if drf_version:
762
- click.secho(
763
- " ✅ Added djangorestframework to pyproject.toml", fg="green"
764
- )
765
- if filter_version:
766
- click.secho(
767
- " ✅ Added django-filter to pyproject.toml", fg="green"
768
- )
769
774
  except (FileNotFoundError, AttributeError) as e:
770
775
  click.secho(
771
776
  f"❌ Error: Failed to parse dependencies from CRM module: {e}",
772
777
  fg="red",
773
778
  err=True,
774
779
  )
775
- click.echo(f"File: {crm_pyproject_path}", err=True)
776
780
  raise click.Abort()
777
781
 
778
782
 
783
+ def _get_crm_settings_addition(config: dict[str, Any]) -> str:
784
+ """Generate settings addition for CRM module."""
785
+ addition = f"""
786
+ # CRM Module Settings
787
+ CRM_DEALS_PER_PAGE = {config["deals_per_page"]}
788
+ CRM_CONTACTS_PER_PAGE = {config["contacts_per_page"]}
789
+ CRM_ENABLE_API = {config["enable_api"]}
790
+ """
791
+ if config["enable_api"]:
792
+ addition += """
793
+ # REST Framework Settings
794
+ REST_FRAMEWORK = {
795
+ "DEFAULT_AUTHENTICATION_CLASSES": [
796
+ "rest_framework.authentication.SessionAuthentication",
797
+ ],
798
+ "DEFAULT_PERMISSION_CLASSES": [
799
+ "rest_framework.permissions.IsAuthenticated",
800
+ ],
801
+ "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
802
+ "PAGE_SIZE": 25,
803
+ "DEFAULT_FILTER_BACKENDS": [
804
+ "django_filters.rest_framework.DjangoFilterBackend",
805
+ "rest_framework.filters.SearchFilter",
806
+ "rest_framework.filters.OrderingFilter",
807
+ ],
808
+ }
809
+ """
810
+ return addition
811
+
812
+
813
+ def _update_crm_urls(urls_path: Path) -> None:
814
+ """Add CRM URLs to project's urls.py."""
815
+ if not urls_path.exists():
816
+ return
817
+
818
+ with open(urls_path) as f:
819
+ urls_content = f.read()
820
+
821
+ if "quickscale_modules_crm" in urls_content:
822
+ return
823
+
824
+ if "urlpatterns = [" in urls_content:
825
+ urls_addition = ' path("crm/", include("quickscale_modules_crm.urls")),\n'
826
+ urls_content = urls_content.replace(
827
+ "urlpatterns = [", "urlpatterns = [\n" + urls_addition
828
+ )
829
+
830
+ with open(urls_path, "w") as f:
831
+ f.write(urls_content)
832
+
833
+ click.secho(" ✅ Updated urls.py with CRM URLs", fg="green")
834
+
835
+
779
836
  def apply_crm_configuration(project_path: Path, config: dict[str, Any]) -> None:
780
837
  """Apply CRM module configuration to project settings"""
781
838
  # QuickScale uses settings/base.py and project_name/urls.py structure
@@ -807,10 +864,7 @@ def apply_crm_configuration(project_path: Path, config: dict[str, Any]) -> None:
807
864
  required_apps = ["rest_framework", "django_filters", "quickscale_modules_crm"]
808
865
  new_apps = _filter_new_apps(settings_content, required_apps)
809
866
 
810
- if not new_apps:
811
- click.echo("ℹ️ All required apps already in INSTALLED_APPS")
812
- else:
813
- # Build the INSTALLED_APPS addition with only new apps
867
+ if new_apps:
814
868
  apps_list = ",\n ".join([f'"{app}"' for app in new_apps])
815
869
  installed_apps_addition = f"""
816
870
  # QuickScale CRM Module - Added by quickscale embed
@@ -818,64 +872,20 @@ INSTALLED_APPS += [
818
872
  {apps_list},
819
873
  ]
820
874
  """
821
- # Append INSTALLED_APPS to settings.py
822
875
  with open(settings_path, "a") as f:
823
876
  f.write("\n" + installed_apps_addition)
877
+ else:
878
+ click.echo("ℹ️ All required apps already in INSTALLED_APPS")
824
879
 
825
- # Add CRM settings (always add these)
826
- settings_addition = f"""
827
- # CRM Module Settings
828
- CRM_DEALS_PER_PAGE = {config["deals_per_page"]}
829
- CRM_CONTACTS_PER_PAGE = {config["contacts_per_page"]}
830
- CRM_ENABLE_API = {config["enable_api"]}
831
- """
832
-
833
- # Add REST Framework settings if enabling API
834
- if config["enable_api"]:
835
- settings_addition += """
836
- # REST Framework Settings
837
- REST_FRAMEWORK = {
838
- "DEFAULT_AUTHENTICATION_CLASSES": [
839
- "rest_framework.authentication.SessionAuthentication",
840
- ],
841
- "DEFAULT_PERMISSION_CLASSES": [
842
- "rest_framework.permissions.IsAuthenticated",
843
- ],
844
- "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
845
- "PAGE_SIZE": 25,
846
- "DEFAULT_FILTER_BACKENDS": [
847
- "django_filters.rest_framework.DjangoFilterBackend",
848
- "rest_framework.filters.SearchFilter",
849
- "rest_framework.filters.OrderingFilter",
850
- ],
851
- }
852
- """
853
-
854
- # Append settings to settings.py
880
+ # Add CRM settings and REST framework settings
881
+ settings_addition = _get_crm_settings_addition(config)
855
882
  with open(settings_path, "a") as f:
856
883
  f.write(settings_addition)
857
884
 
858
885
  click.secho(" ✅ Updated settings.py with CRM configuration", fg="green")
859
886
 
860
887
  # Update urls.py
861
- if urls_path.exists():
862
- with open(urls_path) as f:
863
- urls_content = f.read()
864
-
865
- if "quickscale_modules_crm" not in urls_content:
866
- # Find urlpatterns and add CRM URLs
867
- if "urlpatterns = [" in urls_content:
868
- urls_addition = (
869
- ' path("crm/", include("quickscale_modules_crm.urls")),\n'
870
- )
871
- urls_content = urls_content.replace(
872
- "urlpatterns = [", "urlpatterns = [\n" + urls_addition
873
- )
874
-
875
- with open(urls_path, "w") as f:
876
- f.write(urls_content)
877
-
878
- click.secho(" ✅ Updated urls.py with CRM URLs", fg="green")
888
+ _update_crm_urls(urls_path)
879
889
 
880
890
  # Show configuration summary
881
891
  click.echo("\n📋 Configuration applied:")
@@ -20,9 +20,9 @@ from quickscale_cli.schema.state_schema import QuickScaleState, StateManager
20
20
 
21
21
  # Available themes for selection
22
22
  AVAILABLE_THEMES = [
23
- ("showcase_html", "Pure HTML + CSS (default, production-ready)"),
24
- ("showcase_htmx", "HTMX + Alpine.js (coming in v0.70.0)"),
25
- ("showcase_react", "React + TypeScript SPA (coming in v0.71.0)"),
23
+ ("showcase_react", "React + TypeScript + shadcn/ui (default, production-ready)"),
24
+ ("showcase_html", "Pure HTML + CSS (simpler alternative)"),
25
+ ("showcase_htmx", "HTMX + Alpine.js (coming in v0.78.0)"),
26
26
  ]
27
27
 
28
28
  # Available modules for selection
@@ -53,12 +53,12 @@ def _get_theme_by_name(name: str) -> str | None:
53
53
 
54
54
  def _confirm_unimplemented_theme(theme_id: str) -> bool:
55
55
  """Confirm use of unimplemented theme"""
56
- if theme_id == "showcase_html":
56
+ if theme_id in ("showcase_html", "showcase_react"):
57
57
  return True
58
58
 
59
59
  click.secho(
60
60
  f"\n⚠️ Theme '{theme_id}' is not yet implemented. "
61
- "It will be available in a future release.",
61
+ "It will be available in a future release (v0.78.0).",
62
62
  fg="yellow",
63
63
  )
64
64
  return click.confirm("Use this theme anyway?", default=False)
@@ -68,7 +68,7 @@ def _select_theme() -> str:
68
68
  """Interactive theme selection"""
69
69
  click.echo("\n🎨 Select a theme for your project:")
70
70
  for i, (tid, description) in enumerate(AVAILABLE_THEMES, start=1):
71
- status = " ⚠️ not yet implemented" if tid != "showcase_html" else ""
71
+ status = " ⚠️ not yet implemented" if tid == "showcase_htmx" else ""
72
72
  click.echo(f" {i}. {tid} - {description}{status}")
73
73
 
74
74
  while True:
@@ -611,15 +611,16 @@ def _validate_new_project_name(name: str | None) -> str:
611
611
  )
612
612
  raise click.Abort()
613
613
 
614
- if not name.isidentifier():
614
+ package_name = name.replace("-", "_")
615
+ if not package_name.isidentifier():
615
616
  click.secho(
616
617
  f"\n❌ Error: '{name}' is not a valid project name",
617
618
  fg="red",
618
619
  err=True,
619
620
  )
620
621
  click.echo(
621
- " Project name must be a valid Python identifier "
622
- "(letters, numbers, underscores, not starting with a number)",
622
+ " Project name must check out as a valid Python identifier when hyphens are replaced with underscores "
623
+ "(letters, numbers, underscores, hyphens, not starting with a number)",
623
624
  err=True,
624
625
  )
625
626
  raise click.Abort()
@@ -159,12 +159,13 @@ def _validate_project_section(data: dict, yaml_content: str) -> tuple[str, str]:
159
159
  "'project.name' must be a non-empty string", line=line
160
160
  )
161
161
 
162
- if not project_name.isidentifier():
162
+ package_name = project_name.replace("-", "_")
163
+ if not package_name.isidentifier():
163
164
  line = _find_line_number(yaml_content, "name")
164
165
  raise ConfigValidationError(
165
166
  f"Invalid project name '{project_name}'",
166
167
  line=line,
167
- suggestion="Project name must be a valid Python identifier (letters, numbers, underscores, not starting with a number)",
168
+ suggestion="Project name must be a valid Python identifier (letters, numbers, underscores, hyphens, not starting with a number)",
168
169
  )
169
170
 
170
171
  theme = project_data.get("theme", "showcase_html")
@@ -3,9 +3,21 @@
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
+ from quickscale_cli.schema.config_schema import QuickScaleConfig, validate_config
6
7
  from .docker_utils import find_docker_compose, get_running_containers
7
8
 
8
9
 
10
+ def get_project_config() -> QuickScaleConfig | None:
11
+ """Load and validate quickscale.yml from current directory if it exists."""
12
+ config_path = Path.cwd() / "quickscale.yml"
13
+ if not config_path.exists():
14
+ return None
15
+ try:
16
+ return validate_config(config_path.read_text())
17
+ except Exception:
18
+ return None
19
+
20
+
9
21
  def get_project_state() -> dict[str, Any]:
10
22
  """Get comprehensive project state including directory and containers."""
11
23
  try: