stackforge-cli 0.1.1__tar.gz → 0.1.3__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.
- stackforge_cli-0.1.3/PKG-INFO +107 -0
- stackforge_cli-0.1.3/README.md +93 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/pyproject.toml +1 -1
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/__init__.py +1 -1
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/api_generator.py +2 -1
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/config_generator.py +4 -3
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/csproj_generator.py +2 -1
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/docker_generator.py +3 -2
- stackforge_cli-0.1.3/src/stackforge/engines/dotnet/git_generator.py +111 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/layers_generator.py +5 -4
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/program_generator.py +3 -2
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/scaffolder.py +19 -16
- stackforge_cli-0.1.3/src/stackforge/project_name.py +34 -0
- stackforge_cli-0.1.3/src/stackforge/scaffold.py +90 -0
- stackforge_cli-0.1.3/src/stackforge/ui.py +72 -0
- stackforge_cli-0.1.3/src/stackforge/version.py +48 -0
- stackforge_cli-0.1.3/src/stackforge_cli.egg-info/PKG-INFO +107 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/SOURCES.txt +3 -0
- stackforge_cli-0.1.1/PKG-INFO +0 -47
- stackforge_cli-0.1.1/README.md +0 -33
- stackforge_cli-0.1.1/src/stackforge/engines/dotnet/git_generator.py +0 -67
- stackforge_cli-0.1.1/src/stackforge/scaffold.py +0 -83
- stackforge_cli-0.1.1/src/stackforge_cli.egg-info/PKG-INFO +0 -47
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/setup.cfg +0 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/__init__.py +0 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/dependency_links.txt +0 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/entry_points.txt +0 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/requires.txt +0 -0
- {stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackforge-cli
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: A CLI scaffolder for .NET Clean Architecture projects
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: scaffolder,dotnet,clean-architecture,cli
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: colorama>=0.4.6
|
|
14
|
+
|
|
15
|
+
# StackForge
|
|
16
|
+
|
|
17
|
+
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install stackforge-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
stackforge
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then follow the prompts to scaffold a new project.
|
|
32
|
+
|
|
33
|
+
## Version and updates
|
|
34
|
+
|
|
35
|
+
Check the version you have installed:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
stackforge --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Update to the latest release:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install --upgrade stackforge-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
When you run `stackforge`, it checks PyPI for a newer version and prints an upgrade notice if one is available (network required; fails silently if offline).
|
|
48
|
+
|
|
49
|
+
## What it generates
|
|
50
|
+
|
|
51
|
+
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
52
|
+
- JWT authentication with ASP.NET Identity
|
|
53
|
+
- Swagger UI with Bearer token support
|
|
54
|
+
- PostgreSQL with EF Core
|
|
55
|
+
- Docker + docker-compose with healthchecks
|
|
56
|
+
- Role and user seeders (SuperAdmin out of the box)
|
|
57
|
+
- Git initialization on `main` with `staging` and `development` branches
|
|
58
|
+
- GitHub Actions workflow to create `staging` and `development` on first push to `main`
|
|
59
|
+
|
|
60
|
+
## Quickstart after scaffolding
|
|
61
|
+
|
|
62
|
+
1. Open the project in VS Code or Visual Studio
|
|
63
|
+
2. Run: `docker-compose up --build`
|
|
64
|
+
3. Visit: `http://localhost:5000/swagger`
|
|
65
|
+
|
|
66
|
+
## Git workflow
|
|
67
|
+
|
|
68
|
+
Each scaffolded project is initialized with three local branches:
|
|
69
|
+
|
|
70
|
+
| Branch | Purpose |
|
|
71
|
+
|---------------|----------------------------------|
|
|
72
|
+
| `main` | Production / default branch |
|
|
73
|
+
| `staging` | Pre-production environment |
|
|
74
|
+
| `development` | Active development environment |
|
|
75
|
+
|
|
76
|
+
A GitHub Actions workflow (`.github/workflows/sync-branches.yml`) is also included as a fallback: if you only push `main` first, it creates remote `staging` and `development` on GitHub automatically.
|
|
77
|
+
|
|
78
|
+
### Example: push to GitHub
|
|
79
|
+
|
|
80
|
+
From your scaffolded project directory (e.g. `~/stackforge/MyApi`):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 1. Create a new repo on GitHub (empty, no README), then add the remote
|
|
84
|
+
git remote add origin https://github.com/<your-user>/MyApi.git
|
|
85
|
+
|
|
86
|
+
# 2. Push all branches in one go
|
|
87
|
+
git push -u origin --all
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
That publishes `main`, `staging`, and `development` in a single command and sets upstream tracking for each.
|
|
91
|
+
|
|
92
|
+
### Day-to-day branching
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Work on a feature
|
|
96
|
+
git checkout development
|
|
97
|
+
git checkout -b feature/my-feature
|
|
98
|
+
# ... commit changes ...
|
|
99
|
+
git push -u origin feature/my-feature
|
|
100
|
+
|
|
101
|
+
# Merge into development when ready (via PR or locally)
|
|
102
|
+
git checkout development
|
|
103
|
+
git merge feature/my-feature
|
|
104
|
+
git push origin development
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Promote changes up the chain (`development` → `staging` → `main`) using pull requests or merges as your team prefers.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# StackForge
|
|
2
|
+
|
|
3
|
+
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install stackforge-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
stackforge
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then follow the prompts to scaffold a new project.
|
|
18
|
+
|
|
19
|
+
## Version and updates
|
|
20
|
+
|
|
21
|
+
Check the version you have installed:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
stackforge --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Update to the latest release:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install --upgrade stackforge-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
When you run `stackforge`, it checks PyPI for a newer version and prints an upgrade notice if one is available (network required; fails silently if offline).
|
|
34
|
+
|
|
35
|
+
## What it generates
|
|
36
|
+
|
|
37
|
+
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
38
|
+
- JWT authentication with ASP.NET Identity
|
|
39
|
+
- Swagger UI with Bearer token support
|
|
40
|
+
- PostgreSQL with EF Core
|
|
41
|
+
- Docker + docker-compose with healthchecks
|
|
42
|
+
- Role and user seeders (SuperAdmin out of the box)
|
|
43
|
+
- Git initialization on `main` with `staging` and `development` branches
|
|
44
|
+
- GitHub Actions workflow to create `staging` and `development` on first push to `main`
|
|
45
|
+
|
|
46
|
+
## Quickstart after scaffolding
|
|
47
|
+
|
|
48
|
+
1. Open the project in VS Code or Visual Studio
|
|
49
|
+
2. Run: `docker-compose up --build`
|
|
50
|
+
3. Visit: `http://localhost:5000/swagger`
|
|
51
|
+
|
|
52
|
+
## Git workflow
|
|
53
|
+
|
|
54
|
+
Each scaffolded project is initialized with three local branches:
|
|
55
|
+
|
|
56
|
+
| Branch | Purpose |
|
|
57
|
+
|---------------|----------------------------------|
|
|
58
|
+
| `main` | Production / default branch |
|
|
59
|
+
| `staging` | Pre-production environment |
|
|
60
|
+
| `development` | Active development environment |
|
|
61
|
+
|
|
62
|
+
A GitHub Actions workflow (`.github/workflows/sync-branches.yml`) is also included as a fallback: if you only push `main` first, it creates remote `staging` and `development` on GitHub automatically.
|
|
63
|
+
|
|
64
|
+
### Example: push to GitHub
|
|
65
|
+
|
|
66
|
+
From your scaffolded project directory (e.g. `~/stackforge/MyApi`):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. Create a new repo on GitHub (empty, no README), then add the remote
|
|
70
|
+
git remote add origin https://github.com/<your-user>/MyApi.git
|
|
71
|
+
|
|
72
|
+
# 2. Push all branches in one go
|
|
73
|
+
git push -u origin --all
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That publishes `main`, `staging`, and `development` in a single command and sets upstream tracking for each.
|
|
77
|
+
|
|
78
|
+
### Day-to-day branching
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Work on a feature
|
|
82
|
+
git checkout development
|
|
83
|
+
git checkout -b feature/my-feature
|
|
84
|
+
# ... commit changes ...
|
|
85
|
+
git push -u origin feature/my-feature
|
|
86
|
+
|
|
87
|
+
# Merge into development when ready (via PR or locally)
|
|
88
|
+
git checkout development
|
|
89
|
+
git merge feature/my-feature
|
|
90
|
+
git push origin development
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Promote changes up the chain (`development` → `staging` → `main`) using pull requests or merges as your team prefers.
|
|
@@ -3,6 +3,6 @@ from .csproj_generator import generate_csproj
|
|
|
3
3
|
from .program_generator import generate_configure_services, generate_program_cs
|
|
4
4
|
from .docker_generator import generate_dockerfile, generate_docker_compose
|
|
5
5
|
from .config_generator import generate_env, generate_appsettings, generate_appsettings_development
|
|
6
|
-
from .git_generator import generate_gitignore, git_init
|
|
6
|
+
from .git_generator import generate_gitignore, generate_github_workflow, git_init
|
|
7
7
|
from .api_generator import generate_api_structure
|
|
8
8
|
from .layers_generator import generate_domain_layer, generate_application_layer, generate_infrastructure_layer, generate_persistence_layer
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/api_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def generate_api_structure(base: Path, project: str):
|
|
@@ -28,7 +29,7 @@ def generate_api_structure(base: Path, project: str):
|
|
|
28
29
|
_generate_login_dto(base, project)
|
|
29
30
|
_generate_account_controller(base, project)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
ui.file_created("API/ structure and files")
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def _generate_base_controller(base: Path, project: str):
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/config_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def generate_env(base: Path, project: str):
|
|
@@ -18,7 +19,7 @@ JWT_EXPIRY_MINUTES=60
|
|
|
18
19
|
|
|
19
20
|
file_path = base / ".env"
|
|
20
21
|
file_path.write_text(content)
|
|
21
|
-
|
|
22
|
+
ui.file_created(".env")
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
def generate_appsettings(base: Path, project: str):
|
|
@@ -46,7 +47,7 @@ def generate_appsettings(base: Path, project: str):
|
|
|
46
47
|
|
|
47
48
|
file_path = base / "API" / "appsettings.json"
|
|
48
49
|
file_path.write_text(content)
|
|
49
|
-
|
|
50
|
+
ui.file_created("API/appsettings.json")
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def generate_appsettings_development(base: Path, project: str):
|
|
@@ -73,4 +74,4 @@ def generate_appsettings_development(base: Path, project: str):
|
|
|
73
74
|
|
|
74
75
|
file_path = base / "API" / "appsettings.Development.json"
|
|
75
76
|
file_path.write_text(content)
|
|
76
|
-
|
|
77
|
+
ui.file_created("API/appsettings.Development.json")
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/csproj_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
LAYER_CONFIGS = {
|
|
@@ -79,4 +80,4 @@ def generate_csproj(layer: str, base: Path):
|
|
|
79
80
|
|
|
80
81
|
csproj_path = base / layer / f"{layer}.csproj"
|
|
81
82
|
csproj_path.write_text(content.strip())
|
|
82
|
-
|
|
83
|
+
ui.file_created(f"{layer}/{layer}.csproj")
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/docker_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def generate_dockerfile(base: Path):
|
|
@@ -14,7 +15,7 @@ ENTRYPOINT ["dotnet", "out/API.dll"]
|
|
|
14
15
|
|
|
15
16
|
file_path = base / "Dockerfile"
|
|
16
17
|
file_path.write_text(content)
|
|
17
|
-
|
|
18
|
+
ui.file_created("Dockerfile")
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def generate_docker_compose(base: Path, project: str):
|
|
@@ -60,4 +61,4 @@ volumes:
|
|
|
60
61
|
|
|
61
62
|
file_path = base / "docker-compose.yml"
|
|
62
63
|
file_path.write_text(content)
|
|
63
|
-
|
|
64
|
+
ui.file_created("docker-compose.yml")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from stackforge import ui
|
|
4
|
+
|
|
5
|
+
def generate_sln(base: Path, project: str):
|
|
6
|
+
layers = ["API", "Application", "Domain", "Infrastructure", "Persistence"]
|
|
7
|
+
|
|
8
|
+
project_entries = ""
|
|
9
|
+
for layer in layers:
|
|
10
|
+
guid = str(__import__('uuid').uuid4()).upper()
|
|
11
|
+
project_entries += f"""Project("{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}") = "{layer}", "{layer}\\{layer}.csproj", "{{{guid}}}"
|
|
12
|
+
EndProject
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
content = f"""
|
|
16
|
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
17
|
+
# Visual Studio Version 17
|
|
18
|
+
{project_entries}"""
|
|
19
|
+
|
|
20
|
+
file_path = base / f"{project}.sln"
|
|
21
|
+
file_path.write_text(content)
|
|
22
|
+
ui.file_created(f"{project}.sln")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_gitignore(base: Path):
|
|
26
|
+
content = """# Build results
|
|
27
|
+
bin/
|
|
28
|
+
obj/
|
|
29
|
+
out/
|
|
30
|
+
|
|
31
|
+
# Environment
|
|
32
|
+
.env
|
|
33
|
+
|
|
34
|
+
# VS / Rider
|
|
35
|
+
.vs/
|
|
36
|
+
.idea/
|
|
37
|
+
*.user
|
|
38
|
+
|
|
39
|
+
# Logs
|
|
40
|
+
*.log
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
file_path = base / ".gitignore"
|
|
44
|
+
file_path.write_text(content)
|
|
45
|
+
ui.file_created(".gitignore")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def generate_github_workflow(base: Path):
|
|
49
|
+
workflow_dir = base / ".github" / "workflows"
|
|
50
|
+
workflow_dir.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
content = """name: Sync Environment Branches
|
|
53
|
+
|
|
54
|
+
on:
|
|
55
|
+
push:
|
|
56
|
+
branches:
|
|
57
|
+
- main
|
|
58
|
+
|
|
59
|
+
permissions:
|
|
60
|
+
contents: write
|
|
61
|
+
|
|
62
|
+
jobs:
|
|
63
|
+
sync-branches:
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
with:
|
|
68
|
+
fetch-depth: 0
|
|
69
|
+
|
|
70
|
+
- name: Create staging branch if missing
|
|
71
|
+
run: |
|
|
72
|
+
if ! git ls-remote --exit-code origin refs/heads/staging; then
|
|
73
|
+
git push origin HEAD:refs/heads/staging
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
- name: Create development branch if missing
|
|
77
|
+
run: |
|
|
78
|
+
if ! git ls-remote --exit-code origin refs/heads/development; then
|
|
79
|
+
git push origin HEAD:refs/heads/development
|
|
80
|
+
fi
|
|
81
|
+
"""
|
|
82
|
+
file_path = workflow_dir / "sync-branches.yml"
|
|
83
|
+
file_path.write_text(content.lstrip())
|
|
84
|
+
ui.file_created(".github/workflows/sync-branches.yml")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def git_init(base: Path, project: str):
|
|
88
|
+
try:
|
|
89
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=base, check=True, capture_output=True)
|
|
90
|
+
ui.file_created("git init (main)")
|
|
91
|
+
|
|
92
|
+
subprocess.run(["git", "add", "."], cwd=base, check=True, capture_output=True)
|
|
93
|
+
ui.file_created("git add .")
|
|
94
|
+
|
|
95
|
+
subprocess.run(
|
|
96
|
+
["git", "commit", "-m", f"initial: scaffold {project} with stackforge"],
|
|
97
|
+
cwd=base,
|
|
98
|
+
check=True,
|
|
99
|
+
capture_output=True
|
|
100
|
+
)
|
|
101
|
+
ui.file_created("initial commit")
|
|
102
|
+
|
|
103
|
+
subprocess.run(["git", "branch", "staging"], cwd=base, check=True, capture_output=True)
|
|
104
|
+
subprocess.run(["git", "branch", "development"], cwd=base, check=True, capture_output=True)
|
|
105
|
+
ui.file_created("branches: main, staging, development")
|
|
106
|
+
|
|
107
|
+
except subprocess.CalledProcessError as e:
|
|
108
|
+
ui.error(f"Git error: {e}")
|
|
109
|
+
except FileNotFoundError:
|
|
110
|
+
ui.error("Git not found. Make sure Git is installed and in your PATH.")
|
|
111
|
+
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/layers_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def generate_application_layer(base: Path, project: str):
|
|
@@ -27,7 +28,7 @@ def generate_application_layer(base: Path, project: str):
|
|
|
27
28
|
_gen_paged_response(base, project)
|
|
28
29
|
_gen_iso_datetime(base, project)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
ui.file_created("Application/ structure and files")
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def generate_domain_layer(base: Path, project: str):
|
|
@@ -47,7 +48,7 @@ def generate_domain_layer(base: Path, project: str):
|
|
|
47
48
|
_gen_user_role(base, project)
|
|
48
49
|
_gen_global_usings(base, project)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
ui.file_created("Domain/ structure and files")
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
def generate_infrastructure_layer(base: Path, project: str):
|
|
@@ -60,7 +61,7 @@ def generate_infrastructure_layer(base: Path, project: str):
|
|
|
60
61
|
for folder in folders:
|
|
61
62
|
(base / folder).mkdir(parents=True, exist_ok=True)
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
ui.file_created("Infrastructure/ structure and files")
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def generate_persistence_layer(base: Path, project: str):
|
|
@@ -78,7 +79,7 @@ def generate_persistence_layer(base: Path, project: str):
|
|
|
78
79
|
_gen_user_seeder(base, project)
|
|
79
80
|
_gen_user_role_configuration(base, project)
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
ui.file_created("Persistence/ structure and files")
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
# ── Application files ──────────────────────────────────────────────────────────
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge/engines/dotnet/program_generator.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def generate_configure_services(layer: str, base: Path, project: str):
|
|
@@ -67,7 +68,7 @@ namespace {project}.Persistence
|
|
|
67
68
|
|
|
68
69
|
file_path = base / layer / "ConfigureServices.cs"
|
|
69
70
|
file_path.write_text(templates[layer])
|
|
70
|
-
|
|
71
|
+
ui.file_created(f"{layer}/ConfigureServices.cs")
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def generate_program_cs(base: Path, project: str):
|
|
@@ -191,4 +192,4 @@ app.Run();
|
|
|
191
192
|
|
|
192
193
|
file_path = base / "API" / "Program.cs"
|
|
193
194
|
file_path.write_text(content)
|
|
194
|
-
|
|
195
|
+
ui.file_created("API/Program.cs")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from stackforge import ui
|
|
2
3
|
from .csproj_generator import generate_csproj
|
|
3
4
|
from .program_generator import generate_configure_services, generate_program_cs
|
|
4
5
|
from .docker_generator import generate_dockerfile, generate_docker_compose
|
|
5
6
|
from .config_generator import generate_env, generate_appsettings, generate_appsettings_development
|
|
6
|
-
from .git_generator import generate_gitignore, generate_sln, git_init
|
|
7
|
+
from .git_generator import generate_gitignore, generate_github_workflow, generate_sln, git_init
|
|
7
8
|
from .api_generator import generate_api_structure
|
|
8
9
|
from .layers_generator import (
|
|
9
10
|
generate_application_layer,
|
|
@@ -21,51 +22,53 @@ def scaffold_folders(base: Path, project: str):
|
|
|
21
22
|
"Persistence"
|
|
22
23
|
]
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
ui.section(f"Scaffolding '{project}'")
|
|
25
26
|
|
|
27
|
+
ui.step("Creating folders")
|
|
26
28
|
for layer in layers:
|
|
27
29
|
folder = base / layer
|
|
28
30
|
folder.mkdir(parents=True, exist_ok=True)
|
|
29
|
-
|
|
31
|
+
ui.file_created(layer)
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
print("\nGenerating .csproj files...")
|
|
33
|
+
ui.section("Generating .csproj files")
|
|
34
34
|
for layer in layers:
|
|
35
35
|
generate_csproj(layer, base)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
ui.section("Generating service registrations")
|
|
38
38
|
for layer in ["Application", "Infrastructure", "Persistence"]:
|
|
39
39
|
generate_configure_services(layer, base, project)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
ui.section("Generating Program.cs")
|
|
42
42
|
generate_program_cs(base, project)
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
ui.section("Generating Docker files")
|
|
45
45
|
generate_dockerfile(base)
|
|
46
46
|
generate_docker_compose(base, project)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
ui.section("Generating config files")
|
|
49
49
|
generate_env(base, project)
|
|
50
50
|
generate_appsettings(base, project)
|
|
51
51
|
generate_appsettings_development(base, project)
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
ui.section("Generating API layer")
|
|
54
54
|
generate_api_structure(base, project)
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
ui.section("Generating layer files")
|
|
57
57
|
generate_application_layer(base, project)
|
|
58
58
|
generate_domain_layer(base, project)
|
|
59
59
|
generate_infrastructure_layer(base, project)
|
|
60
60
|
generate_persistence_layer(base, project)
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
ui.section("Generating solution file")
|
|
63
63
|
generate_sln(base, project)
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
ui.section("Generating .gitignore")
|
|
66
66
|
generate_gitignore(base)
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
ui.section("Generating GitHub workflow")
|
|
69
|
+
generate_github_workflow(base)
|
|
70
|
+
|
|
71
|
+
ui.section("Initializing Git repository")
|
|
69
72
|
git_init(base, project)
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
ui.done()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_CAMEL_PARTS = re.compile(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\b)|\d+")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_valid_project_name(name: str) -> bool:
|
|
7
|
+
if not name or not name[0].isalpha():
|
|
8
|
+
return False
|
|
9
|
+
if not re.fullmatch(r"[A-Za-z0-9_-]+", name):
|
|
10
|
+
return False
|
|
11
|
+
if name.startswith(("-", "_")) or name.endswith(("-", "_")):
|
|
12
|
+
return False
|
|
13
|
+
if "__" in name or "--" in name:
|
|
14
|
+
return False
|
|
15
|
+
return True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _title_part(part: str) -> str:
|
|
19
|
+
if not part:
|
|
20
|
+
return ""
|
|
21
|
+
if part.isdigit():
|
|
22
|
+
return part
|
|
23
|
+
return part[0].upper() + part[1:]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def to_pascal_case(name: str) -> str:
|
|
27
|
+
if "_" in name:
|
|
28
|
+
parts = name.split("_")
|
|
29
|
+
elif "-" in name:
|
|
30
|
+
parts = name.split("-")
|
|
31
|
+
else:
|
|
32
|
+
parts = _CAMEL_PARTS.findall(name) or [name]
|
|
33
|
+
|
|
34
|
+
return "".join(_title_part(part) for part in parts if part)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from stackforge.engines.dotnet.scaffolder import scaffold_folders
|
|
3
|
+
from stackforge.version import get_installed_version, notify_if_update_available
|
|
4
|
+
from stackforge.project_name import is_valid_project_name, to_pascal_case
|
|
5
|
+
from stackforge import ui
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
FRAMEWORK_CHOICES = frozenset({"dotnet", "django", "fastapi", "flask", "exit"})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def select_framework() -> str:
|
|
13
|
+
ui.info("\n Select a framework:\n")
|
|
14
|
+
ui.menu_item("dotnet", ".NET Clean Architecture (CQRS)")
|
|
15
|
+
ui.menu_item("django", "Django [coming soon]", style=ui.Fore.YELLOW)
|
|
16
|
+
ui.menu_item("fastapi", "FastAPI [coming soon]", style=ui.Fore.YELLOW)
|
|
17
|
+
ui.menu_item("flask", "Flask [coming soon]", style=ui.Fore.YELLOW)
|
|
18
|
+
ui.menu_item("exit", "Exit", style=ui.Fore.RED)
|
|
19
|
+
|
|
20
|
+
choice = ui.prompt("\n Framework [dotnet]:").lower() or "dotnet"
|
|
21
|
+
if choice not in FRAMEWORK_CHOICES:
|
|
22
|
+
ui.error(f"Unknown framework '{choice}'.")
|
|
23
|
+
ui.step("Options: dotnet, django, fastapi, flask, exit\n")
|
|
24
|
+
return ""
|
|
25
|
+
return choice
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
if "--version" in sys.argv or "-V" in sys.argv:
|
|
30
|
+
print(get_installed_version())
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
notify_if_update_available()
|
|
34
|
+
ui.banner(".NET Clean Architecture Scaffolder", get_installed_version())
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
while True:
|
|
38
|
+
framework = select_framework()
|
|
39
|
+
|
|
40
|
+
if not framework:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
if framework == "exit":
|
|
44
|
+
ui.warn("\n Exiting...")
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
if framework != "dotnet":
|
|
48
|
+
ui.warn("\n This framework is under development. Stay tuned!\n")
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
raw_name = ui.prompt(
|
|
52
|
+
"\n Enter project name (PascalCase, camelCase, snake_case, or kebab-case):"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if not raw_name:
|
|
56
|
+
ui.error("Project name cannot be empty.\n")
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
if not is_valid_project_name(raw_name):
|
|
60
|
+
ui.error("Invalid project name. Use letters, numbers, hyphens, or underscores.")
|
|
61
|
+
ui.step("Examples: MyApi, myApi, my_api, my-api\n")
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
project_name = to_pascal_case(raw_name)
|
|
65
|
+
if project_name != raw_name:
|
|
66
|
+
ui.info(f" Normalized to: {project_name}")
|
|
67
|
+
|
|
68
|
+
base_path = Path.home() / "stackforge" / project_name
|
|
69
|
+
|
|
70
|
+
if base_path.exists():
|
|
71
|
+
ui.error(f"\n Project '{project_name}' already exists in the stackforge workspace.")
|
|
72
|
+
ui.step(f"Path: {base_path}")
|
|
73
|
+
ui.step("Choose a different project name or delete the existing one.\n")
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
scaffold_folders(base_path, project_name)
|
|
78
|
+
|
|
79
|
+
ui.section("Project ready")
|
|
80
|
+
ui.success(f"Created '{project_name}'")
|
|
81
|
+
ui.success(f"Location: {base_path}")
|
|
82
|
+
ui.info("\n Next steps:")
|
|
83
|
+
ui.step("Open the project in Visual Studio or VS Code")
|
|
84
|
+
ui.step("Run: docker-compose up --build")
|
|
85
|
+
ui.step("Visit: http://localhost:5000/swagger")
|
|
86
|
+
ui.muted("\n Press Ctrl+C to exit or continue to scaffold another project.\n")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from colorama import Fore, Style, init
|
|
2
|
+
|
|
3
|
+
init(autoreset=True)
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"BANNER", "banner", "section", "step", "file_created", "success",
|
|
7
|
+
"warn", "error", "info", "prompt", "menu_item", "done", "muted",
|
|
8
|
+
"Fore", "Style",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
BANNER = r"""
|
|
12
|
+
_____ _ _ ______
|
|
13
|
+
/ ____| | | | | ____|
|
|
14
|
+
| (___ | |_ __ _ ___| | _| |__ ___ _ __ __ _ ___
|
|
15
|
+
\___ \| __/ _` |/ __| |/ / __/ _ \| '__/ _` |/ _ \
|
|
16
|
+
____) | || (_| | (__| <| | | (_) | | | (_| | __/
|
|
17
|
+
|_____/ \__\__,_|\___|_|\_\_| \___/|_| \__, |\___|
|
|
18
|
+
__/ |
|
|
19
|
+
|___/"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def banner(subtitle: str, version: str = "") -> None:
|
|
23
|
+
print(f"{Style.BRIGHT}{Fore.CYAN}{BANNER}{Style.RESET_ALL}")
|
|
24
|
+
print(f" {Style.BRIGHT}{Fore.WHITE}{subtitle}{Style.RESET_ALL}", end="")
|
|
25
|
+
if version:
|
|
26
|
+
print(f" {Fore.LIGHTBLACK_EX}v{version}{Style.RESET_ALL}")
|
|
27
|
+
else:
|
|
28
|
+
print()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def section(title: str) -> None:
|
|
32
|
+
print(f"\n {Style.BRIGHT}{Fore.CYAN}> {title}{Style.RESET_ALL}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def step(label: str) -> None:
|
|
36
|
+
print(f" {Fore.BLUE}|{Style.RESET_ALL} {label}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def file_created(path: str) -> None:
|
|
40
|
+
print(f" {Fore.GREEN}+{Style.RESET_ALL} {Fore.LIGHTWHITE_EX}{path}{Style.RESET_ALL}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def success(message: str) -> None:
|
|
44
|
+
print(f" {Fore.GREEN}+{Style.RESET_ALL} {message}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def warn(message: str) -> None:
|
|
48
|
+
print(f" {Fore.YELLOW}!{Style.RESET_ALL} {message}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def error(message: str) -> None:
|
|
52
|
+
print(f" {Fore.RED}x{Style.RESET_ALL} {message}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def info(message: str) -> None:
|
|
56
|
+
print(f" {Fore.CYAN}{message}{Style.RESET_ALL}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def prompt(label: str) -> str:
|
|
60
|
+
return input(f" {Style.BRIGHT}{Fore.CYAN}{label}{Style.RESET_ALL} ").strip()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def menu_item(key: str, label: str, *, style: str = Fore.GREEN) -> None:
|
|
64
|
+
print(f" {style}{key:<10}{Style.RESET_ALL} {label}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def done() -> None:
|
|
68
|
+
print(f"\n {Style.BRIGHT}{Fore.GREEN}+ Scaffold complete.{Style.RESET_ALL}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def muted(message: str) -> None:
|
|
72
|
+
print(f" {Fore.LIGHTBLACK_EX}{message}{Style.RESET_ALL}")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.error
|
|
3
|
+
import urllib.request
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
PACKAGE_NAME = "stackforge-cli"
|
|
7
|
+
PYPI_JSON_URL = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_installed_version() -> str:
|
|
11
|
+
try:
|
|
12
|
+
return version(PACKAGE_NAME)
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
return "0.0.0-dev"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _version_tuple(value: str) -> tuple[int, ...]:
|
|
18
|
+
parts: list[int] = []
|
|
19
|
+
for segment in value.split("."):
|
|
20
|
+
digits = "".join(c for c in segment if c.isdigit())
|
|
21
|
+
if digits:
|
|
22
|
+
parts.append(int(digits))
|
|
23
|
+
return tuple(parts)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def fetch_latest_version(timeout: float = 2.0) -> str | None:
|
|
27
|
+
request = urllib.request.Request(
|
|
28
|
+
PYPI_JSON_URL,
|
|
29
|
+
headers={"Accept": "application/json"},
|
|
30
|
+
)
|
|
31
|
+
try:
|
|
32
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
33
|
+
payload = json.load(response)
|
|
34
|
+
return payload["info"]["version"]
|
|
35
|
+
except (urllib.error.URLError, TimeoutError, KeyError, json.JSONDecodeError):
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def notify_if_update_available() -> None:
|
|
40
|
+
from stackforge import ui
|
|
41
|
+
|
|
42
|
+
installed = get_installed_version()
|
|
43
|
+
latest = fetch_latest_version()
|
|
44
|
+
if not latest or _version_tuple(latest) <= _version_tuple(installed):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
ui.warn(f"StackForge {latest} is available (you have {installed}).")
|
|
48
|
+
ui.step(f"Update: pip install --upgrade {PACKAGE_NAME}\n")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackforge-cli
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: A CLI scaffolder for .NET Clean Architecture projects
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: scaffolder,dotnet,clean-architecture,cli
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: colorama>=0.4.6
|
|
14
|
+
|
|
15
|
+
# StackForge
|
|
16
|
+
|
|
17
|
+
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install stackforge-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
stackforge
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then follow the prompts to scaffold a new project.
|
|
32
|
+
|
|
33
|
+
## Version and updates
|
|
34
|
+
|
|
35
|
+
Check the version you have installed:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
stackforge --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Update to the latest release:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install --upgrade stackforge-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
When you run `stackforge`, it checks PyPI for a newer version and prints an upgrade notice if one is available (network required; fails silently if offline).
|
|
48
|
+
|
|
49
|
+
## What it generates
|
|
50
|
+
|
|
51
|
+
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
52
|
+
- JWT authentication with ASP.NET Identity
|
|
53
|
+
- Swagger UI with Bearer token support
|
|
54
|
+
- PostgreSQL with EF Core
|
|
55
|
+
- Docker + docker-compose with healthchecks
|
|
56
|
+
- Role and user seeders (SuperAdmin out of the box)
|
|
57
|
+
- Git initialization on `main` with `staging` and `development` branches
|
|
58
|
+
- GitHub Actions workflow to create `staging` and `development` on first push to `main`
|
|
59
|
+
|
|
60
|
+
## Quickstart after scaffolding
|
|
61
|
+
|
|
62
|
+
1. Open the project in VS Code or Visual Studio
|
|
63
|
+
2. Run: `docker-compose up --build`
|
|
64
|
+
3. Visit: `http://localhost:5000/swagger`
|
|
65
|
+
|
|
66
|
+
## Git workflow
|
|
67
|
+
|
|
68
|
+
Each scaffolded project is initialized with three local branches:
|
|
69
|
+
|
|
70
|
+
| Branch | Purpose |
|
|
71
|
+
|---------------|----------------------------------|
|
|
72
|
+
| `main` | Production / default branch |
|
|
73
|
+
| `staging` | Pre-production environment |
|
|
74
|
+
| `development` | Active development environment |
|
|
75
|
+
|
|
76
|
+
A GitHub Actions workflow (`.github/workflows/sync-branches.yml`) is also included as a fallback: if you only push `main` first, it creates remote `staging` and `development` on GitHub automatically.
|
|
77
|
+
|
|
78
|
+
### Example: push to GitHub
|
|
79
|
+
|
|
80
|
+
From your scaffolded project directory (e.g. `~/stackforge/MyApi`):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 1. Create a new repo on GitHub (empty, no README), then add the remote
|
|
84
|
+
git remote add origin https://github.com/<your-user>/MyApi.git
|
|
85
|
+
|
|
86
|
+
# 2. Push all branches in one go
|
|
87
|
+
git push -u origin --all
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
That publishes `main`, `staging`, and `development` in a single command and sets upstream tracking for each.
|
|
91
|
+
|
|
92
|
+
### Day-to-day branching
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Work on a feature
|
|
96
|
+
git checkout development
|
|
97
|
+
git checkout -b feature/my-feature
|
|
98
|
+
# ... commit changes ...
|
|
99
|
+
git push -u origin feature/my-feature
|
|
100
|
+
|
|
101
|
+
# Merge into development when ready (via PR or locally)
|
|
102
|
+
git checkout development
|
|
103
|
+
git merge feature/my-feature
|
|
104
|
+
git push origin development
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Promote changes up the chain (`development` → `staging` → `main`) using pull requests or merges as your team prefers.
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
3
|
src/stackforge/__init__.py
|
|
4
|
+
src/stackforge/project_name.py
|
|
4
5
|
src/stackforge/scaffold.py
|
|
6
|
+
src/stackforge/ui.py
|
|
7
|
+
src/stackforge/version.py
|
|
5
8
|
src/stackforge/engines/dotnet/__init__.py
|
|
6
9
|
src/stackforge/engines/dotnet/api_generator.py
|
|
7
10
|
src/stackforge/engines/dotnet/config_generator.py
|
stackforge_cli-0.1.1/PKG-INFO
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: stackforge-cli
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: A CLI scaffolder for .NET Clean Architecture projects
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: scaffolder,dotnet,clean-architecture,cli
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: colorama>=0.4.6
|
|
14
|
-
|
|
15
|
-
# StackForge
|
|
16
|
-
|
|
17
|
-
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pip install stackforge
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
stackforge
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then follow the prompts to scaffold a new project.
|
|
32
|
-
|
|
33
|
-
## What it generates
|
|
34
|
-
|
|
35
|
-
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
36
|
-
- JWT authentication with ASP.NET Identity
|
|
37
|
-
- Swagger UI with Bearer token support
|
|
38
|
-
- PostgreSQL with EF Core
|
|
39
|
-
- Docker + docker-compose with healthchecks
|
|
40
|
-
- Role and user seeders (SuperAdmin out of the box)
|
|
41
|
-
- Git initialization with initial commit
|
|
42
|
-
|
|
43
|
-
## Quickstart after scaffolding
|
|
44
|
-
|
|
45
|
-
1. Open the project in VS Code or Visual Studio
|
|
46
|
-
2. Run: `docker-compose up --build`
|
|
47
|
-
3. Visit: `http://localhost:5000/swagger`
|
stackforge_cli-0.1.1/README.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# StackForge
|
|
2
|
-
|
|
3
|
-
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pip install stackforge
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
stackforge
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Then follow the prompts to scaffold a new project.
|
|
18
|
-
|
|
19
|
-
## What it generates
|
|
20
|
-
|
|
21
|
-
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
22
|
-
- JWT authentication with ASP.NET Identity
|
|
23
|
-
- Swagger UI with Bearer token support
|
|
24
|
-
- PostgreSQL with EF Core
|
|
25
|
-
- Docker + docker-compose with healthchecks
|
|
26
|
-
- Role and user seeders (SuperAdmin out of the box)
|
|
27
|
-
- Git initialization with initial commit
|
|
28
|
-
|
|
29
|
-
## Quickstart after scaffolding
|
|
30
|
-
|
|
31
|
-
1. Open the project in VS Code or Visual Studio
|
|
32
|
-
2. Run: `docker-compose up --build`
|
|
33
|
-
3. Visit: `http://localhost:5000/swagger`
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
def generate_sln(base: Path, project: str):
|
|
5
|
-
layers = ["API", "Application", "Domain", "Infrastructure", "Persistence"]
|
|
6
|
-
|
|
7
|
-
project_entries = ""
|
|
8
|
-
for layer in layers:
|
|
9
|
-
guid = str(__import__('uuid').uuid4()).upper()
|
|
10
|
-
project_entries += f"""Project("{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}") = "{layer}", "{layer}\\{layer}.csproj", "{{{guid}}}"
|
|
11
|
-
EndProject
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
content = f"""
|
|
15
|
-
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
16
|
-
# Visual Studio Version 17
|
|
17
|
-
{project_entries}"""
|
|
18
|
-
|
|
19
|
-
file_path = base / f"{project}.sln"
|
|
20
|
-
file_path.write_text(content)
|
|
21
|
-
print(f" [+] {project}.sln")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def generate_gitignore(base: Path):
|
|
25
|
-
content = """# Build results
|
|
26
|
-
bin/
|
|
27
|
-
obj/
|
|
28
|
-
out/
|
|
29
|
-
|
|
30
|
-
# Environment
|
|
31
|
-
.env
|
|
32
|
-
|
|
33
|
-
# VS / Rider
|
|
34
|
-
.vs/
|
|
35
|
-
.idea/
|
|
36
|
-
*.user
|
|
37
|
-
|
|
38
|
-
# Logs
|
|
39
|
-
*.log
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
file_path = base / ".gitignore"
|
|
43
|
-
file_path.write_text(content)
|
|
44
|
-
print(" [+] .gitignore")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def git_init(base: Path, project: str):
|
|
48
|
-
try:
|
|
49
|
-
subprocess.run(["git", "init"], cwd=base, check=True, capture_output=True)
|
|
50
|
-
print(" [+] git init")
|
|
51
|
-
|
|
52
|
-
subprocess.run(["git", "add", "."], cwd=base, check=True, capture_output=True)
|
|
53
|
-
print(" [+] git add .")
|
|
54
|
-
|
|
55
|
-
subprocess.run(
|
|
56
|
-
["git", "commit", "-m", f"initial: scaffold {project} with stackforge"],
|
|
57
|
-
cwd=base,
|
|
58
|
-
check=True,
|
|
59
|
-
capture_output=True
|
|
60
|
-
)
|
|
61
|
-
print(" [+] initial commit")
|
|
62
|
-
|
|
63
|
-
except subprocess.CalledProcessError as e:
|
|
64
|
-
print(f" [!] Git error: {e}")
|
|
65
|
-
except FileNotFoundError:
|
|
66
|
-
print(" [!] Git not found. Make sure Git is installed and in your PATH.")
|
|
67
|
-
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from stackforge.engines.dotnet.scaffolder import scaffold_folders
|
|
3
|
-
from colorama import init, Fore, Style
|
|
4
|
-
import re
|
|
5
|
-
|
|
6
|
-
init(autoreset=True)
|
|
7
|
-
|
|
8
|
-
def is_valid_project_name(name: str) -> bool:
|
|
9
|
-
return bool(re.match(r'^[A-Za-z][A-Za-z0-9]*$', name))
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def select_framework() -> str:
|
|
13
|
-
print(f" {Fore.CYAN}Select a framework:{Style.RESET_ALL}\n")
|
|
14
|
-
print(f" {Fore.GREEN}1.{Style.RESET_ALL} .NET Clean Architecture (CQRS)")
|
|
15
|
-
print(f" {Fore.YELLOW}2.{Style.RESET_ALL} Django [coming soon]")
|
|
16
|
-
print(f" {Fore.YELLOW}3.{Style.RESET_ALL} FastAPI [coming soon]")
|
|
17
|
-
print(f" {Fore.YELLOW}4.{Style.RESET_ALL} Flask [coming soon]")
|
|
18
|
-
print(f" {Fore.RED}5.{Style.RESET_ALL} exit")
|
|
19
|
-
|
|
20
|
-
choice = input(f"\n {Fore.CYAN}Choice [n]:{Style.RESET_ALL} ").strip() or "1"
|
|
21
|
-
return choice
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def main():
|
|
25
|
-
print(Fore.CYAN + r"""
|
|
26
|
-
_____ _ _ ______
|
|
27
|
-
/ ____| | | | | ____|
|
|
28
|
-
| (___ | |_ __ _ ___| | _| |__ ___ _ __ __ _ ___
|
|
29
|
-
\___ \| __/ _` |/ __| |/ / __/ _ \| '__/ _` |/ _ \
|
|
30
|
-
____) | || (_| | (__| <| | | (_) | | | (_| | __/
|
|
31
|
-
|_____/ \__\__,_|\___|_|\_\_| \___/|_| \__, |\___|
|
|
32
|
-
__/ |
|
|
33
|
-
|___/
|
|
34
|
-
""" + Style.RESET_ALL + f" {Fore.WHITE}.NET Clean Architecture Scaffolder{Style.RESET_ALL}\n")
|
|
35
|
-
|
|
36
|
-
while True:
|
|
37
|
-
framework = select_framework()
|
|
38
|
-
|
|
39
|
-
if framework == "5":
|
|
40
|
-
print(f"\n {Fore.YELLOW}Exiting...{Style.RESET_ALL}")
|
|
41
|
-
break
|
|
42
|
-
|
|
43
|
-
if framework != "1":
|
|
44
|
-
print(f"\n {Fore.YELLOW}[!] This framework is under development. Stay tuned!{Style.RESET_ALL}\n")
|
|
45
|
-
continue
|
|
46
|
-
|
|
47
|
-
project_name = input(f"\n {Fore.CYAN}Enter project name (PascalCase, no spaces):{Style.RESET_ALL} ").strip()
|
|
48
|
-
|
|
49
|
-
if not project_name:
|
|
50
|
-
print(f" {Fore.RED}[!] Project name cannot be empty.{Style.RESET_ALL}\n")
|
|
51
|
-
continue
|
|
52
|
-
|
|
53
|
-
if not is_valid_project_name(project_name):
|
|
54
|
-
print(f" {Fore.RED}[!] Project name must start with a letter and contain only letters and numbers.")
|
|
55
|
-
print(f" Example: RaiderXpress, MyApi, StackForge{Style.RESET_ALL}\n")
|
|
56
|
-
continue
|
|
57
|
-
|
|
58
|
-
base_path = Path.home() / "stackforge" / project_name
|
|
59
|
-
|
|
60
|
-
if base_path.exists():
|
|
61
|
-
print(f"\n {Fore.RED}[!] Project '{project_name}' already exists in the stackforge workspace.")
|
|
62
|
-
print(f" Path: {base_path}{Style.RESET_ALL}")
|
|
63
|
-
print(" Choose a different project name or delete the existing one.\n")
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
|
67
|
-
scaffold_folders(base_path, project_name)
|
|
68
|
-
|
|
69
|
-
print(f"""
|
|
70
|
-
{Fore.GREEN}✔ Project '{project_name}' created successfully!
|
|
71
|
-
✔ Location: {base_path}{Style.RESET_ALL}
|
|
72
|
-
|
|
73
|
-
{Fore.CYAN}Next steps:{Style.RESET_ALL}
|
|
74
|
-
1. Open the project in Visual Studio or VS Code
|
|
75
|
-
2. Run: docker-compose up --build
|
|
76
|
-
3. Visit: http://localhost:5000/swagger
|
|
77
|
-
|
|
78
|
-
Press Ctrl+C to exit or continue to scaffold another project.
|
|
79
|
-
""")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if __name__ == "__main__":
|
|
83
|
-
main()
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: stackforge-cli
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: A CLI scaffolder for .NET Clean Architecture projects
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: scaffolder,dotnet,clean-architecture,cli
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: OS Independent
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Requires-Python: >=3.10
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: colorama>=0.4.6
|
|
14
|
-
|
|
15
|
-
# StackForge
|
|
16
|
-
|
|
17
|
-
A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pip install stackforge
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Usage
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
stackforge
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then follow the prompts to scaffold a new project.
|
|
32
|
-
|
|
33
|
-
## What it generates
|
|
34
|
-
|
|
35
|
-
- .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
|
|
36
|
-
- JWT authentication with ASP.NET Identity
|
|
37
|
-
- Swagger UI with Bearer token support
|
|
38
|
-
- PostgreSQL with EF Core
|
|
39
|
-
- Docker + docker-compose with healthchecks
|
|
40
|
-
- Role and user seeders (SuperAdmin out of the box)
|
|
41
|
-
- Git initialization with initial commit
|
|
42
|
-
|
|
43
|
-
## Quickstart after scaffolding
|
|
44
|
-
|
|
45
|
-
1. Open the project in VS Code or Visual Studio
|
|
46
|
-
2. Run: `docker-compose up --build`
|
|
47
|
-
3. Visit: `http://localhost:5000/swagger`
|
|
File without changes
|
|
File without changes
|
{stackforge_cli-0.1.1 → stackforge_cli-0.1.3}/src/stackforge_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|