sscli 2.1.0__tar.gz → 2.2.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.
- {sscli-2.1.0 → sscli-2.2.0}/PKG-INFO +37 -27
- {sscli-2.1.0 → sscli-2.2.0}/README.md +36 -26
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/development.py +101 -5
- sscli-2.2.0/foundry/actions/explore.py +227 -0
- sscli-2.2.0/foundry/actions/features.py +302 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/generation_utils.py +1 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/generator.py +25 -1
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/interactive_config.py +21 -1
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/metadata.py +31 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/cli.py +46 -1
- {sscli-2.1.0 → sscli-2.2.0}/foundry/constants.py +27 -12
- sscli-2.2.0/foundry/ops/cli.py +120 -0
- sscli-2.2.0/foundry/ops/version_manager.py +187 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/utils.py +2 -2
- {sscli-2.1.0 → sscli-2.2.0}/pyproject.toml +1 -1
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/PKG-INFO +37 -27
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/SOURCES.txt +1 -0
- sscli-2.1.0/foundry/actions/explore.py +0 -139
- sscli-2.1.0/foundry/actions/features.py +0 -111
- sscli-2.1.0/foundry/ops/cli.py +0 -42
- {sscli-2.1.0 → sscli-2.2.0}/foundry/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/account_info.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/documentation.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/health.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/repo_utils.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/setup.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/validation.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/models.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/reports.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verification/verifier.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/actions/verify.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/admin_tools.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/access_manager.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/manager.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/models.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha/view.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/alpha_expiration.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animated_experience.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/animation.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/assets.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/base.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/canvas.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/components.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/composites.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/core.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/animation_engine.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/clock.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/frame_buffer.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/engine/state.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/rendering.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/entrance.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/forge.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/info_slide.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_disperse.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_dissolve.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_reveal.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/logo_slide.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/rain.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/scenes/tranquility.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/sections/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/ui/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/animations/utils.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/auth.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/client_access_manager.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/auth.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/commands/interactive.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/development_experience.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/gate.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/interactive.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/interactive_utils.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/internal_experience.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/manage.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/licenses.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/manager.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/menus.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/reporter.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/ops/runner.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/patch.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/telemetry.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/client/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/development/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/development/build_tools.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/foundry/tools/internal/__init__.py +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/setup.cfg +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/dependency_links.txt +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/entry_points.txt +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/requires.txt +0 -0
- {sscli-2.1.0 → sscli-2.2.0}/sscli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sscli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Seed & Source CLI - Multi-tenant SaaS scaffolding with Seed & Source Core
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,50 +13,49 @@ Requires-Dist: python-dotenv
|
|
|
13
13
|
Requires-Dist: pydantic>=2.0.0
|
|
14
14
|
Requires-Dist: httpx>=0.27.0
|
|
15
15
|
|
|
16
|
-
# Seed & Source CLI
|
|
16
|
+
# Seed & Source CLI v2.2.0
|
|
17
17
|
|
|
18
|
-
Unified interface for Seed & Source templates. Now with **Seed & Source Core**
|
|
18
|
+
Unified interface for Seed & Source templates. Now with **Seed & Source Core** and **The Central Vault**.
|
|
19
19
|
|
|
20
20
|
## 🎯 What is sscli?
|
|
21
21
|
|
|
22
|
-
`sscli` scaffolds complete, enterprise-grade SaaS stacks with
|
|
23
|
-
|
|
24
|
-
- ✅
|
|
25
|
-
- ✅
|
|
26
|
-
- ✅
|
|
27
|
-
- ✅
|
|
22
|
+
`sscli` scaffolds complete, enterprise-grade SaaS stacks. As part of the **Great Decoupling**, our templates are now "Lean Cores", with high-value proprietary features (Commerce, Admin, Tunnels) managed via a central vault and injected at setup time.
|
|
23
|
+
|
|
24
|
+
- ✅ **Lean Cores**: Free, high-performance base templates for Rails, Python, and Astro.
|
|
25
|
+
- ✅ **The Vault**: Proprietary high-value features (Commerce, Admin, Tunnels) injected on-demand.
|
|
26
|
+
- ✅ **Multi-tenant isolation**: Pre-configured database-level security.
|
|
27
|
+
- ✅ **Hexagonal Architecture**: Clean separation between domain logic and infrastructure.
|
|
28
28
|
|
|
29
29
|
## 📦 Quick Install
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
# Via pip
|
|
33
|
-
pip install sscli==
|
|
33
|
+
pip install sscli==2.1.0
|
|
34
34
|
|
|
35
35
|
# Via pipx (recommended)
|
|
36
|
-
pipx install sscli==
|
|
36
|
+
pipx install sscli==2.1.0
|
|
37
37
|
|
|
38
38
|
# Verify
|
|
39
39
|
sscli --version
|
|
40
|
-
# Output: sscli
|
|
40
|
+
# Output: sscli 2.1.0
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## 🚀 Quick Start
|
|
44
44
|
|
|
45
|
-
### Create a Multi-Tenant Rails API
|
|
45
|
+
### Create a Multi-Tenant Rails API (Lean Core + Commerce Vault)
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
48
|
sscli new \
|
|
49
49
|
--template rails-api \
|
|
50
50
|
--name my-saas-api \
|
|
51
|
-
--with-commerce
|
|
52
|
-
--with-shopify
|
|
51
|
+
--with-commerce
|
|
53
52
|
```
|
|
54
53
|
|
|
55
54
|
This scaffolds:
|
|
56
|
-
- Rails 8.0 API with PostgreSQL
|
|
57
|
-
-
|
|
55
|
+
- Rails 8.0 API with PostgreSQL (Lean Core)
|
|
56
|
+
- **Vault Injection**: Pluggable Commerce adapters and and webhooks.
|
|
57
|
+
- Multi-tenant core (Tenants, API Keys, etc.)
|
|
58
58
|
- HMAC webhook validation
|
|
59
|
-
- Shopify adapter (ready for orders/create webhooks)
|
|
60
59
|
- Row-Level Security (RLS) enforcement
|
|
61
60
|
|
|
62
61
|
### Create a Python SaaS Backend
|
|
@@ -88,16 +87,27 @@ This scaffolds:
|
|
|
88
87
|
- Tailwind CSS
|
|
89
88
|
- TypeScript
|
|
90
89
|
|
|
91
|
-
## 📋 Available Templates
|
|
90
|
+
## 📋 Available Templates (Lean Cores)
|
|
91
|
+
|
|
92
|
+
| Template | Tier | Description |
|
|
93
|
+
|----------|------|-------------|
|
|
94
|
+
| `rails-api` | **FREE** | Multi-tenant Rails API |
|
|
95
|
+
| `python-saas` | **FREE** | Python SaaS Boilerplate |
|
|
96
|
+
| `static-landing` | **FREE** | Astro Static Landing Page |
|
|
97
|
+
| `react-client` | ALPHA | React + Vite frontend |
|
|
98
|
+
| `data-pipeline` | ALPHA | dbt + Airflow pipeline |
|
|
99
|
+
|
|
100
|
+
## 💎 High-Value Feature Vault (Premium)
|
|
101
|
+
|
|
102
|
+
These modules are managed in the central private vault and injected on-demand into the Lean Cores:
|
|
92
103
|
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `mobile-ios` | iOS Swift app | Mobile apps |
|
|
104
|
+
| Feature | Tier | Description |
|
|
105
|
+
|---------|------|-------------|
|
|
106
|
+
| `commerce` | **PRO** | Shopify/Stripe adapters, webhooks, and commerce models |
|
|
107
|
+
| `admin` | **PRO** | Standalone NiceGUI-based admin dashboard |
|
|
108
|
+
| `tunnel` | **PRO** | Automated ngrok config for local webhook testing |
|
|
109
|
+
| `sqlite` | ALPHA | Local persistence adapter (SQLAlchemy/Alembic) |
|
|
110
|
+
| `ingestor` | **PRO** | Data Ingestor adapter for raw normalization |
|
|
101
111
|
|
|
102
112
|
## 🔧 CLI Commands
|
|
103
113
|
|
|
@@ -1,47 +1,46 @@
|
|
|
1
|
-
# Seed & Source CLI
|
|
1
|
+
# Seed & Source CLI v2.2.0
|
|
2
2
|
|
|
3
|
-
Unified interface for Seed & Source templates. Now with **Seed & Source Core**
|
|
3
|
+
Unified interface for Seed & Source templates. Now with **Seed & Source Core** and **The Central Vault**.
|
|
4
4
|
|
|
5
5
|
## 🎯 What is sscli?
|
|
6
6
|
|
|
7
|
-
`sscli` scaffolds complete, enterprise-grade SaaS stacks with
|
|
8
|
-
|
|
9
|
-
- ✅
|
|
10
|
-
- ✅
|
|
11
|
-
- ✅
|
|
12
|
-
- ✅
|
|
7
|
+
`sscli` scaffolds complete, enterprise-grade SaaS stacks. As part of the **Great Decoupling**, our templates are now "Lean Cores", with high-value proprietary features (Commerce, Admin, Tunnels) managed via a central vault and injected at setup time.
|
|
8
|
+
|
|
9
|
+
- ✅ **Lean Cores**: Free, high-performance base templates for Rails, Python, and Astro.
|
|
10
|
+
- ✅ **The Vault**: Proprietary high-value features (Commerce, Admin, Tunnels) injected on-demand.
|
|
11
|
+
- ✅ **Multi-tenant isolation**: Pre-configured database-level security.
|
|
12
|
+
- ✅ **Hexagonal Architecture**: Clean separation between domain logic and infrastructure.
|
|
13
13
|
|
|
14
14
|
## 📦 Quick Install
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
# Via pip
|
|
18
|
-
pip install sscli==
|
|
18
|
+
pip install sscli==2.1.0
|
|
19
19
|
|
|
20
20
|
# Via pipx (recommended)
|
|
21
|
-
pipx install sscli==
|
|
21
|
+
pipx install sscli==2.1.0
|
|
22
22
|
|
|
23
23
|
# Verify
|
|
24
24
|
sscli --version
|
|
25
|
-
# Output: sscli
|
|
25
|
+
# Output: sscli 2.1.0
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## 🚀 Quick Start
|
|
29
29
|
|
|
30
|
-
### Create a Multi-Tenant Rails API
|
|
30
|
+
### Create a Multi-Tenant Rails API (Lean Core + Commerce Vault)
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
sscli new \
|
|
34
34
|
--template rails-api \
|
|
35
35
|
--name my-saas-api \
|
|
36
|
-
--with-commerce
|
|
37
|
-
--with-shopify
|
|
36
|
+
--with-commerce
|
|
38
37
|
```
|
|
39
38
|
|
|
40
39
|
This scaffolds:
|
|
41
|
-
- Rails 8.0 API with PostgreSQL
|
|
42
|
-
-
|
|
40
|
+
- Rails 8.0 API with PostgreSQL (Lean Core)
|
|
41
|
+
- **Vault Injection**: Pluggable Commerce adapters and and webhooks.
|
|
42
|
+
- Multi-tenant core (Tenants, API Keys, etc.)
|
|
43
43
|
- HMAC webhook validation
|
|
44
|
-
- Shopify adapter (ready for orders/create webhooks)
|
|
45
44
|
- Row-Level Security (RLS) enforcement
|
|
46
45
|
|
|
47
46
|
### Create a Python SaaS Backend
|
|
@@ -73,16 +72,27 @@ This scaffolds:
|
|
|
73
72
|
- Tailwind CSS
|
|
74
73
|
- TypeScript
|
|
75
74
|
|
|
76
|
-
## 📋 Available Templates
|
|
75
|
+
## 📋 Available Templates (Lean Cores)
|
|
76
|
+
|
|
77
|
+
| Template | Tier | Description |
|
|
78
|
+
|----------|------|-------------|
|
|
79
|
+
| `rails-api` | **FREE** | Multi-tenant Rails API |
|
|
80
|
+
| `python-saas` | **FREE** | Python SaaS Boilerplate |
|
|
81
|
+
| `static-landing` | **FREE** | Astro Static Landing Page |
|
|
82
|
+
| `react-client` | ALPHA | React + Vite frontend |
|
|
83
|
+
| `data-pipeline` | ALPHA | dbt + Airflow pipeline |
|
|
84
|
+
|
|
85
|
+
## 💎 High-Value Feature Vault (Premium)
|
|
86
|
+
|
|
87
|
+
These modules are managed in the central private vault and injected on-demand into the Lean Cores:
|
|
77
88
|
|
|
78
|
-
|
|
|
79
|
-
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `mobile-ios` | iOS Swift app | Mobile apps |
|
|
89
|
+
| Feature | Tier | Description |
|
|
90
|
+
|---------|------|-------------|
|
|
91
|
+
| `commerce` | **PRO** | Shopify/Stripe adapters, webhooks, and commerce models |
|
|
92
|
+
| `admin` | **PRO** | Standalone NiceGUI-based admin dashboard |
|
|
93
|
+
| `tunnel` | **PRO** | Automated ngrok config for local webhook testing |
|
|
94
|
+
| `sqlite` | ALPHA | Local persistence adapter (SQLAlchemy/Alembic) |
|
|
95
|
+
| `ingestor` | **PRO** | Data Ingestor adapter for raw normalization |
|
|
86
96
|
|
|
87
97
|
## 🔧 CLI Commands
|
|
88
98
|
|
|
@@ -281,16 +281,18 @@ def run_swap(target=None):
|
|
|
281
281
|
wheel = wheels[0]
|
|
282
282
|
console.print(f" Installing from [cyan]{wheel.name}[/cyan]...")
|
|
283
283
|
|
|
284
|
+
import os
|
|
285
|
+
python_exe = venv_path / 'bin' / 'python'
|
|
286
|
+
|
|
284
287
|
result = subprocess.run(
|
|
285
|
-
[str(
|
|
288
|
+
[str(python_exe), '-m', 'pip', 'install', '--force-reinstall', str(wheel)],
|
|
286
289
|
capture_output=True,
|
|
287
290
|
text=True
|
|
288
291
|
)
|
|
289
292
|
|
|
290
293
|
if result.returncode != 0:
|
|
291
294
|
console.print("[red]✗ Installation failed[/red]")
|
|
292
|
-
|
|
293
|
-
console.print(result.stderr)
|
|
295
|
+
console.print(f"[dim]Error details: {result.stderr[:200]}[/dim]")
|
|
294
296
|
return False
|
|
295
297
|
|
|
296
298
|
console.print("[green]✓ Switched to local build[/green]")
|
|
@@ -303,14 +305,18 @@ def run_swap(target=None):
|
|
|
303
305
|
|
|
304
306
|
console.print(f" Installing sscli=={pip_version} from PyPI...")
|
|
305
307
|
|
|
308
|
+
import os
|
|
309
|
+
python_exe = venv_path / 'bin' / 'python'
|
|
310
|
+
|
|
306
311
|
result = subprocess.run(
|
|
307
|
-
[str(
|
|
312
|
+
[str(python_exe), '-m', 'pip', 'install', '--force-reinstall', f'sscli=={pip_version}'],
|
|
308
313
|
capture_output=True,
|
|
309
314
|
text=True
|
|
310
315
|
)
|
|
311
316
|
|
|
312
317
|
if result.returncode != 0:
|
|
313
318
|
console.print("[red]✗ Installation failed[/red]")
|
|
319
|
+
console.print(f"[dim]Error details: {result.stderr[:200]}[/dim]")
|
|
314
320
|
return False
|
|
315
321
|
|
|
316
322
|
console.print("[green]✓ Switched to pip package[/green]")
|
|
@@ -322,10 +328,93 @@ def run_swap(target=None):
|
|
|
322
328
|
|
|
323
329
|
return False
|
|
324
330
|
|
|
331
|
+
def run_bump_version(bump_type=None, auto_confirm=False):
|
|
332
|
+
"""Run the bump version script interactively or with specified type."""
|
|
333
|
+
stack_cli_root = get_stack_cli_root()
|
|
334
|
+
bin_dir = stack_cli_root / 'bin'
|
|
335
|
+
bump_script = bin_dir / 'sscli-bump-version'
|
|
336
|
+
|
|
337
|
+
if not bump_script.exists():
|
|
338
|
+
console.print("[red]✗ Bump script not found at[/red]", bump_script)
|
|
339
|
+
return False
|
|
340
|
+
|
|
341
|
+
# If bump_type is not provided, ask interactively
|
|
342
|
+
if bump_type is None:
|
|
343
|
+
console.print("")
|
|
344
|
+
console.print("[bold cyan]Version Bump Utility[/bold cyan]")
|
|
345
|
+
console.print("")
|
|
346
|
+
|
|
347
|
+
# Ask which version to bump
|
|
348
|
+
bump_type = questionary.select(
|
|
349
|
+
"Select version bump type:",
|
|
350
|
+
choices=[
|
|
351
|
+
questionary.Choice("Major (X.0.0)", value="major"),
|
|
352
|
+
questionary.Choice("Minor (x.Y.0)", value="minor"),
|
|
353
|
+
questionary.Choice("Patch (x.y.Z)", value="patch"),
|
|
354
|
+
questionary.Choice("Cancel", value="cancel"),
|
|
355
|
+
],
|
|
356
|
+
).ask()
|
|
357
|
+
|
|
358
|
+
if not bump_type or bump_type == "cancel":
|
|
359
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
360
|
+
return False
|
|
361
|
+
|
|
362
|
+
# In interactive mode through the dev menu, let the script ask for confirmation
|
|
363
|
+
auto_confirm = False
|
|
364
|
+
|
|
365
|
+
console.print("")
|
|
366
|
+
try:
|
|
367
|
+
# Prepare environment for the bash script
|
|
368
|
+
import os
|
|
369
|
+
env = os.environ.copy()
|
|
370
|
+
|
|
371
|
+
# Build command
|
|
372
|
+
cmd = [str(bump_script), bump_type]
|
|
373
|
+
stdin_input = None
|
|
374
|
+
|
|
375
|
+
if auto_confirm:
|
|
376
|
+
# For non-interactive CLI usage, auto-confirm by feeding 'y' to stdin
|
|
377
|
+
stdin_input = "y\n"
|
|
378
|
+
|
|
379
|
+
result = subprocess.run(
|
|
380
|
+
cmd,
|
|
381
|
+
input=stdin_input,
|
|
382
|
+
capture_output=True,
|
|
383
|
+
text=True,
|
|
384
|
+
env=env
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Print script output
|
|
388
|
+
if result.stdout:
|
|
389
|
+
console.print(result.stdout)
|
|
390
|
+
|
|
391
|
+
if result.returncode != 0:
|
|
392
|
+
if result.stderr:
|
|
393
|
+
console.print("[red]Error:[/red]")
|
|
394
|
+
console.print(result.stderr)
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
return True
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
console.print(f"[red]✗ Bump failed: {e}[/red]")
|
|
401
|
+
return False
|
|
402
|
+
|
|
325
403
|
def dev_tools_menu():
|
|
326
404
|
"""Interactive menu for development tools."""
|
|
327
405
|
# Check if running in development context
|
|
328
|
-
|
|
406
|
+
# Check environment variable first (takes precedence for CLI wrapper scripts)
|
|
407
|
+
import os
|
|
408
|
+
from pathlib import Path
|
|
409
|
+
foundry_root = os.getenv("FOUNDRY_ROOT")
|
|
410
|
+
if foundry_root:
|
|
411
|
+
root_path = Path(foundry_root)
|
|
412
|
+
markers = [".github", "docs", "tooling", "wiring"]
|
|
413
|
+
is_dev = all((root_path / marker).exists() for marker in markers)
|
|
414
|
+
else:
|
|
415
|
+
is_dev = is_internal_dev()
|
|
416
|
+
|
|
417
|
+
if not is_dev:
|
|
329
418
|
console.print("[yellow]⚠️ Development tools are only available in development mode.[/yellow]")
|
|
330
419
|
return
|
|
331
420
|
|
|
@@ -340,6 +429,7 @@ def dev_tools_menu():
|
|
|
340
429
|
questionary.Choice("📊 Show Development Status", value="status"),
|
|
341
430
|
questionary.Choice("🔨 Build Distribution Package", value="build"),
|
|
342
431
|
questionary.Choice("🔄 Switch Build Source (Local ↔ Pip)", value="swap"),
|
|
432
|
+
questionary.Choice("📌 Bump Version (major/minor/patch)", value="bump"),
|
|
343
433
|
questionary.Choice("← Back to Main Menu", value="back"),
|
|
344
434
|
]
|
|
345
435
|
|
|
@@ -392,6 +482,12 @@ def dev_tools_menu():
|
|
|
392
482
|
else:
|
|
393
483
|
console.print("[red]✗[/red] Swap failed")
|
|
394
484
|
|
|
485
|
+
elif selection == "bump":
|
|
486
|
+
if run_bump_version():
|
|
487
|
+
console.print("[green]✓[/green] Version bump complete")
|
|
488
|
+
else:
|
|
489
|
+
console.print("[red]✗[/red] Version bump failed")
|
|
490
|
+
|
|
395
491
|
console.print("")
|
|
396
492
|
input("[dim]Press Enter to continue...[/dim]")
|
|
397
493
|
console.clear()
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from foundry.constants import console, TIER_HIERARCHY, QUESTIONARY_STYLE, TEMPLATE_REGISTRY
|
|
2
|
+
from foundry.utils import get_templates, get_template_info
|
|
3
|
+
from foundry.auth import get_current_license_info
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
from rich.tree import Tree
|
|
6
|
+
import questionary
|
|
7
|
+
from questionary import Choice
|
|
8
|
+
from prompt_toolkit.key_binding import KeyBindings
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_template_tech(name):
|
|
13
|
+
"""Get technology stack for a template by name."""
|
|
14
|
+
# Map template names to tech descriptions
|
|
15
|
+
tech_map = {
|
|
16
|
+
"python-saas": "Python (FastAPI/SQLAlchemy)",
|
|
17
|
+
"rails-api": "Ruby on Rails",
|
|
18
|
+
"react-client": "React/Vite",
|
|
19
|
+
"rails-ui-kit": "Rails + ViewComponent",
|
|
20
|
+
"static-landing": "Astro (Static Site)",
|
|
21
|
+
"mobile-android": "Android (Kotlin/Compose)",
|
|
22
|
+
"mobile-ios": "iOS (Swift/SwiftUI)",
|
|
23
|
+
"data-pipeline": "Data Stack (dbt + Python)",
|
|
24
|
+
"terraform-infra": "Terraform (Multi-cloud)",
|
|
25
|
+
"wiring": "Docker Orchestration",
|
|
26
|
+
}
|
|
27
|
+
return tech_map.get(name, "Multi-tech Stack")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _strip_markup(text):
|
|
31
|
+
"""Remove Rich markup tags from text."""
|
|
32
|
+
return re.sub(r'\[/?[^\]]+\]', '', text)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _render_templates_tree(buckets):
|
|
36
|
+
"""Render a tree view with all categories expanded showing available templates."""
|
|
37
|
+
tree = Tree("[bold green]Seed & Source Inventory[/bold green]")
|
|
38
|
+
|
|
39
|
+
for category, items in buckets.items():
|
|
40
|
+
if not items:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
count = len(items)
|
|
44
|
+
cat_node = tree.add(f"[bold yellow]▼ {category} ({count})[/bold yellow]")
|
|
45
|
+
|
|
46
|
+
for item in items:
|
|
47
|
+
node = cat_node.add(f"[bold cyan]{item['name']}[/bold cyan]")
|
|
48
|
+
# Show tier badge in tree
|
|
49
|
+
if item['tier'] != "free":
|
|
50
|
+
tier_display = f"(🔐 {item['tier'].upper()})" if item['locked'] else f"(✅ {item['tier'].upper()})"
|
|
51
|
+
node.add(f"[yellow]{tier_display}[/yellow]")
|
|
52
|
+
# Show tech
|
|
53
|
+
tech = _get_template_tech(item['name'])
|
|
54
|
+
node.add(f"[dim]{tech}[/dim]")
|
|
55
|
+
|
|
56
|
+
console.print(Panel(tree, title="Available Templates", border_style="green"))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _show_template_info(name):
|
|
60
|
+
"""Show detailed information about a template."""
|
|
61
|
+
info = get_template_info(name)
|
|
62
|
+
tech = _get_template_tech(name)
|
|
63
|
+
|
|
64
|
+
content = f"[bold cyan]Name:[/bold cyan] {name}\n"
|
|
65
|
+
content += f"[bold cyan]Tech:[/bold cyan] {tech}\n"
|
|
66
|
+
content += f"[bold cyan]Tier:[/bold cyan] {info.get('tier', 'free').upper()}\n"
|
|
67
|
+
content += f"[bold cyan]Repo:[/bold cyan] {info.get('repo', 'N/A')}\n\n"
|
|
68
|
+
content += f"[italic]{info.get('description', 'No detailed description available.')}[/italic]"
|
|
69
|
+
|
|
70
|
+
console.print(Panel(content, title=f"Template Details: {name}", border_style="cyan", expand=False))
|
|
71
|
+
questionary.press_any_key_to_continue().ask()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def run_explore():
|
|
75
|
+
"""Interactive template explorer with collapsible tree and selectable templates."""
|
|
76
|
+
# Get user info for tier checking
|
|
77
|
+
user_info = get_current_license_info()
|
|
78
|
+
user_tier = user_info.get("tier", "free")
|
|
79
|
+
user_rank = TIER_HIERARCHY.get(user_tier, 0)
|
|
80
|
+
|
|
81
|
+
# Group templates by category
|
|
82
|
+
buckets = {
|
|
83
|
+
"Backend Services": [],
|
|
84
|
+
"Frontend & UI": [],
|
|
85
|
+
"Mobile App": [],
|
|
86
|
+
"Data Engineering": [],
|
|
87
|
+
"Infrastructure": [],
|
|
88
|
+
"Other": [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
templates = get_templates()
|
|
92
|
+
|
|
93
|
+
for tmpl in templates:
|
|
94
|
+
name = tmpl.name
|
|
95
|
+
# Fetch metadata to get the tier
|
|
96
|
+
info = get_template_info(name)
|
|
97
|
+
tmpl_tier = info.get("tier", "free").lower()
|
|
98
|
+
tmpl_rank = TIER_HIERARCHY.get(tmpl_tier, 0)
|
|
99
|
+
|
|
100
|
+
# Check if locked
|
|
101
|
+
locked = tmpl_rank > user_rank
|
|
102
|
+
|
|
103
|
+
# Store template data for processing
|
|
104
|
+
tmpl_data = {
|
|
105
|
+
"name": name,
|
|
106
|
+
"tier": tmpl_tier,
|
|
107
|
+
"locked": locked,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if name in ["python-saas", "rails-api"]:
|
|
111
|
+
buckets["Backend Services"].append(tmpl_data)
|
|
112
|
+
elif name in ["react-client", "rails-ui-kit", "static-landing"]:
|
|
113
|
+
buckets["Frontend & UI"].append(tmpl_data)
|
|
114
|
+
elif name.startswith("mobile-"):
|
|
115
|
+
buckets["Mobile App"].append(tmpl_data)
|
|
116
|
+
elif name == "data-pipeline":
|
|
117
|
+
buckets["Data Engineering"].append(tmpl_data)
|
|
118
|
+
elif name == "terraform-infra":
|
|
119
|
+
buckets["Infrastructure"].append(tmpl_data)
|
|
120
|
+
else:
|
|
121
|
+
buckets["Other"].append(tmpl_data)
|
|
122
|
+
|
|
123
|
+
# Remove empty categories
|
|
124
|
+
buckets = {k: v for k, v in buckets.items() if v}
|
|
125
|
+
|
|
126
|
+
# State for collapsible sections - start collapsed as requested
|
|
127
|
+
expanded_categories = set()
|
|
128
|
+
last_selected_value = f"toggle:{list(buckets.keys())[0]}" if buckets else None
|
|
129
|
+
|
|
130
|
+
# Custom Keybindings for Left/Right arrows
|
|
131
|
+
kb = KeyBindings()
|
|
132
|
+
@kb.add("left")
|
|
133
|
+
@kb.add("right")
|
|
134
|
+
def _(event):
|
|
135
|
+
# Map left/right arrows to Enter (submit) to trigger the toggle/select loop
|
|
136
|
+
event.app.key_processor.feed_text("\n")
|
|
137
|
+
|
|
138
|
+
# ANSI Colors for visual richness
|
|
139
|
+
YELLOW = "\033[33m"
|
|
140
|
+
BLUE = "\033[34m"
|
|
141
|
+
CYAN = "\033[36m"
|
|
142
|
+
GREEN = "\033[32m"
|
|
143
|
+
RED = "\033[31m"
|
|
144
|
+
MAGENTA = "\033[35m"
|
|
145
|
+
BOLD = "\033[1m"
|
|
146
|
+
RESET = "\033[0m"
|
|
147
|
+
|
|
148
|
+
while True:
|
|
149
|
+
choices = []
|
|
150
|
+
for category, items in buckets.items():
|
|
151
|
+
is_expanded = category in expanded_categories
|
|
152
|
+
symbol = "▼" if is_expanded else "▶"
|
|
153
|
+
count = len(items)
|
|
154
|
+
|
|
155
|
+
# Category choice - Base Yellow
|
|
156
|
+
choices.append(Choice(
|
|
157
|
+
title=f"{YELLOW}{symbol} {category} ({count}){RESET}",
|
|
158
|
+
value=f"toggle:{category}"
|
|
159
|
+
))
|
|
160
|
+
|
|
161
|
+
if is_expanded:
|
|
162
|
+
for i, item in enumerate(items):
|
|
163
|
+
is_last = (i == len(items) - 1)
|
|
164
|
+
connector = " └─ " if is_last else " ├─ "
|
|
165
|
+
|
|
166
|
+
# Blue template names
|
|
167
|
+
label = f"{connector}{BLUE}{item['name']}{RESET}"
|
|
168
|
+
|
|
169
|
+
# Colored badges
|
|
170
|
+
if item['locked']:
|
|
171
|
+
label += f" {RED}🔐 LOCKED{RESET}"
|
|
172
|
+
else:
|
|
173
|
+
tier = item['tier']
|
|
174
|
+
if tier == "alpha":
|
|
175
|
+
label += f" {YELLOW}✅ ALPHA{RESET}"
|
|
176
|
+
elif tier == "pro":
|
|
177
|
+
label += f" {MAGENTA}✅ PRO{RESET}"
|
|
178
|
+
elif tier == "free":
|
|
179
|
+
label += f" {GREEN}✅ FREE{RESET}"
|
|
180
|
+
|
|
181
|
+
# Tech description for the gray 3rd row (via patch)
|
|
182
|
+
tech = _get_template_tech(item['name'])
|
|
183
|
+
description = f" Description: {tech}"
|
|
184
|
+
|
|
185
|
+
choices.append(Choice(
|
|
186
|
+
title=label,
|
|
187
|
+
value=f"tmpl:{item['name']}",
|
|
188
|
+
description=description
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
choices.append(Choice("← Back to Main Menu", value="back"))
|
|
192
|
+
|
|
193
|
+
console.clear()
|
|
194
|
+
# Green frame that encompasses the header and instructions
|
|
195
|
+
console.print(Panel(
|
|
196
|
+
f"[bold green]Seed & Source Inventory[/bold green]\n"
|
|
197
|
+
f"[dim]Navigate with arrows. Use {BOLD}Enter/Right{RESET} [dim]to expand/select, {BOLD}Left{RESET} [dim]to collapse.[/dim]",
|
|
198
|
+
border_style="green",
|
|
199
|
+
padding=(1, 2)
|
|
200
|
+
))
|
|
201
|
+
|
|
202
|
+
selected = questionary.select(
|
|
203
|
+
"Select a template to view details:",
|
|
204
|
+
choices=choices,
|
|
205
|
+
style=questionary.Style(QUESTIONARY_STYLE),
|
|
206
|
+
use_indicator=True,
|
|
207
|
+
default=last_selected_value,
|
|
208
|
+
key_bindings=kb
|
|
209
|
+
).ask()
|
|
210
|
+
|
|
211
|
+
if selected is None or selected == "back":
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
last_selected_value = selected
|
|
215
|
+
|
|
216
|
+
if selected.startswith("toggle:"):
|
|
217
|
+
cat = selected.split(":", 1)[1]
|
|
218
|
+
if cat in expanded_categories:
|
|
219
|
+
expanded_categories.remove(cat)
|
|
220
|
+
else:
|
|
221
|
+
expanded_categories.add(cat)
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
if selected.startswith("tmpl:"):
|
|
225
|
+
tmpl_name = selected.split(":", 1)[1]
|
|
226
|
+
_show_template_info(tmpl_name)
|
|
227
|
+
|