stackforge-cli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,194 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def generate_configure_services(layer: str, base: Path, project: str):
5
+ templates = {
6
+ "Application": f"""using FluentValidation;
7
+ using MediatR;
8
+ using Microsoft.Extensions.DependencyInjection;
9
+ using System.Reflection;
10
+
11
+ namespace {project}.Application
12
+ {{
13
+ public static class ConfigureServices
14
+ {{
15
+ public static IServiceCollection AddApplicationServices(this IServiceCollection services)
16
+ {{
17
+ services.AddMediatR(cfg =>
18
+ cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
19
+
20
+ services.AddAutoMapper(cfg => cfg.AddMaps(Assembly.GetExecutingAssembly()));
21
+
22
+ services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
23
+
24
+ return services;
25
+ }}
26
+ }}
27
+ }}
28
+ """,
29
+ "Infrastructure": f"""using Microsoft.Extensions.DependencyInjection;
30
+
31
+ namespace {project}.Infrastructure
32
+ {{
33
+ public static class ConfigureServices
34
+ {{
35
+ public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
36
+ {{
37
+ // Register infrastructure services here
38
+
39
+ return services;
40
+ }}
41
+ }}
42
+ }}
43
+ """,
44
+ "Persistence": f"""using {project}.Persistence;
45
+ using Microsoft.EntityFrameworkCore;
46
+ using Microsoft.Extensions.Configuration;
47
+ using Microsoft.Extensions.DependencyInjection;
48
+
49
+ namespace {project}.Persistence
50
+ {{
51
+ public static class ConfigureServices
52
+ {{
53
+ public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
54
+ {{
55
+ services.AddDbContext<DataContext>(options =>
56
+ options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));
57
+
58
+ return services;
59
+ }}
60
+ }}
61
+ }}
62
+ """,
63
+ }
64
+
65
+ if layer not in templates:
66
+ return
67
+
68
+ file_path = base / layer / "ConfigureServices.cs"
69
+ file_path.write_text(templates[layer])
70
+ print(f" [+] {layer}/ConfigureServices.cs")
71
+
72
+
73
+ def generate_program_cs(base: Path, project: str):
74
+ content = f"""using {project}.Application;
75
+ using {project}.Infrastructure;
76
+ using {project}.Persistence;
77
+ using {project}.API.Interface;
78
+ using {project}.API.Options;
79
+ using {project}.API.Services;
80
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
81
+ using Microsoft.AspNetCore.Identity;
82
+ using Microsoft.IdentityModel.Tokens;
83
+ using {project}.Domain.Entities.Identity;
84
+ using System.Text;
85
+
86
+ var builder = WebApplication.CreateBuilder(args);
87
+
88
+ // Layer services
89
+ builder.Services.AddApplicationServices();
90
+ builder.Services.AddInfrastructureServices();
91
+ builder.Services.AddPersistenceServices(builder.Configuration);
92
+
93
+ // Identity
94
+ builder.Services.AddIdentity<User, Role>(options =>
95
+ {{
96
+ options.Password.RequireDigit = true;
97
+ options.Password.RequireLowercase = true;
98
+ options.Password.RequireUppercase = true;
99
+ options.Password.RequireNonAlphanumeric = true;
100
+ options.Password.RequiredLength = 8;
101
+ }})
102
+ .AddEntityFrameworkStores<DataContext>()
103
+ .AddDefaultTokenProviders();
104
+
105
+ // JWT
106
+ builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
107
+ var jwtOptions = new {project}.API.Options.JwtOptions();
108
+ builder.Configuration.GetSection("Jwt").Bind(jwtOptions);
109
+ builder.Services.AddScoped<IJwtService, JwtService>();
110
+
111
+ builder.Services.AddAuthentication(options =>
112
+ {{
113
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
114
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
115
+ }})
116
+ .AddJwtBearer(options =>
117
+ {{
118
+ options.TokenValidationParameters = new TokenValidationParameters
119
+ {{
120
+ ValidateIssuer = true,
121
+ ValidateAudience = true,
122
+ ValidateLifetime = true,
123
+ ValidateIssuerSigningKey = true,
124
+ ValidIssuer = jwtOptions.Issuer,
125
+ ValidAudience = jwtOptions.Audience,
126
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
127
+ }};
128
+ }});
129
+
130
+ // Controllers
131
+ builder.Services.AddControllers();
132
+
133
+ // Swagger
134
+ builder.Services.AddEndpointsApiExplorer();
135
+ builder.Services.AddSwaggerGen(c =>
136
+ {{
137
+ c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
138
+ {{
139
+ Name = "Authorization",
140
+ Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
141
+ Scheme = "bearer",
142
+ BearerFormat = "JWT",
143
+ In = Microsoft.OpenApi.Models.ParameterLocation.Header,
144
+ Description = "Enter your JWT token only (no 'Bearer' prefix needed)"
145
+ }});
146
+ c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
147
+ {{
148
+ {{
149
+ new Microsoft.OpenApi.Models.OpenApiSecurityScheme
150
+ {{
151
+ Reference = new Microsoft.OpenApi.Models.OpenApiReference
152
+ {{
153
+ Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
154
+ Id = "Bearer"
155
+ }}
156
+ }},
157
+ Array.Empty<string>()
158
+ }}
159
+ }});
160
+ }});
161
+
162
+ var app = builder.Build();
163
+
164
+ if (app.Environment.IsDevelopment())
165
+ {{
166
+ app.UseSwagger();
167
+ app.UseSwaggerUI();
168
+ }}
169
+
170
+ if (!app.Environment.IsDevelopment())
171
+ {{
172
+ app.UseHttpsRedirection();
173
+ }}
174
+ app.UseAuthentication();
175
+ app.UseAuthorization();
176
+ app.MapControllers();
177
+
178
+ using (var scope = app.Services.CreateScope())
179
+ {{
180
+ var db = scope.ServiceProvider.GetRequiredService<DataContext>();
181
+ db.Database.EnsureCreated();
182
+
183
+ var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
184
+ var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
185
+ await {project}.Persistence.Seeders.RoleSeeder.SeedAsync(roleManager);
186
+ await {project}.Persistence.Seeders.UserSeeder.SeedAsync(userManager);
187
+ }}
188
+
189
+ app.Run();
190
+ """
191
+
192
+ file_path = base / "API" / "Program.cs"
193
+ file_path.write_text(content)
194
+ print(" [+] API/Program.cs")
@@ -0,0 +1,71 @@
1
+ from pathlib import Path
2
+ from .csproj_generator import generate_csproj
3
+ from .program_generator import generate_configure_services, generate_program_cs
4
+ from .docker_generator import generate_dockerfile, generate_docker_compose
5
+ from .config_generator import generate_env, generate_appsettings, generate_appsettings_development
6
+ from .git_generator import generate_gitignore, generate_sln, git_init
7
+ from .api_generator import generate_api_structure
8
+ from .layers_generator import (
9
+ generate_application_layer,
10
+ generate_domain_layer,
11
+ generate_infrastructure_layer,
12
+ generate_persistence_layer,
13
+ )
14
+
15
+ def scaffold_folders(base: Path, project: str):
16
+ layers = [
17
+ "API",
18
+ "Application",
19
+ "Domain",
20
+ "Infrastructure",
21
+ "Persistence"
22
+ ]
23
+
24
+ print(f"\nScaffolding folders for project '{project}'...")
25
+
26
+ for layer in layers:
27
+ folder = base / layer
28
+ folder.mkdir(parents=True, exist_ok=True)
29
+ print(f" [+] {layer}")
30
+
31
+ print("\nFolder structure created.")
32
+
33
+ print("\nGenerating .csproj files...")
34
+ for layer in layers:
35
+ generate_csproj(layer, base)
36
+
37
+ print("\nGenerating service registrations...")
38
+ for layer in ["Application", "Infrastructure", "Persistence"]:
39
+ generate_configure_services(layer, base, project)
40
+
41
+ print("\nGenerating Program.cs...")
42
+ generate_program_cs(base, project)
43
+
44
+ print("\nGenerating Docker files...")
45
+ generate_dockerfile(base)
46
+ generate_docker_compose(base, project)
47
+
48
+ print("\nGenerating config files...")
49
+ generate_env(base, project)
50
+ generate_appsettings(base, project)
51
+ generate_appsettings_development(base, project)
52
+
53
+ print("\nGenerating API layer...")
54
+ generate_api_structure(base, project)
55
+
56
+ print("\nGenerating layer files...")
57
+ generate_application_layer(base, project)
58
+ generate_domain_layer(base, project)
59
+ generate_infrastructure_layer(base, project)
60
+ generate_persistence_layer(base, project)
61
+
62
+ print("\nGenerating solution file...")
63
+ generate_sln(base, project)
64
+
65
+ print("\nGenerating .gitignore...")
66
+ generate_gitignore(base)
67
+
68
+ print("\nInitializing Git repository...")
69
+ git_init(base, project)
70
+
71
+ print("\nDone.")
stackforge/scaffold.py ADDED
@@ -0,0 +1,82 @@
1
+ from pathlib import Path
2
+ from stackforge.engines.dotnet.scaffolder import scaffold_folders
3
+ import re
4
+
5
+
6
+ def is_valid_project_name(name: str) -> bool:
7
+ return bool(re.match(r'^[A-Za-z][A-Za-z0-9]*$', name))
8
+
9
+
10
+ def select_framework() -> str:
11
+ print(" Select a framework:\n")
12
+ print(" 1. .NET Clean Architecture (CQRS)")
13
+ print(" 2. Django [coming soon]")
14
+ print(" 3. FastAPI [coming soon]")
15
+ print(" 4. Flask [coming soon]")
16
+ print(" 5. exit")
17
+
18
+ choice = input("\n Choice [n]: ").strip() or "1"
19
+ return choice
20
+
21
+
22
+ def main():
23
+ print(r"""
24
+ _____ _ _ ______
25
+ / ____| | | | | ____|
26
+ | (___ | |_ __ _ ___| | _| |__ ___ _ __ __ _ ___
27
+ \___ \| __/ _` |/ __| |/ / __/ _ \| '__/ _` |/ _ \
28
+ ____) | || (_| | (__| <| | | (_) | | | (_| | __/
29
+ |_____/ \__\__,_|\___|_|\_\_| \___/|_| \__, |\___|
30
+ __/ |
31
+ |___/
32
+ .NET Clean Architecture Scaffolder
33
+ """)
34
+
35
+ while True:
36
+ framework = select_framework()
37
+
38
+ if framework == "5":
39
+ print("\n Exiting...")
40
+ break
41
+
42
+ if framework != "1":
43
+ print("\n [!] This framework is under development. Stay tuned!\n")
44
+ continue
45
+
46
+ project_name = input("\n Enter project name (PascalCase, no spaces): ").strip()
47
+
48
+ if not project_name:
49
+ print(" [!] Project name cannot be empty.\n")
50
+ continue
51
+
52
+ if not is_valid_project_name(project_name):
53
+ print(" [!] Project name must start with a letter and contain only letters and numbers.")
54
+ print(" Example: RaiderXpress, MyApi, StackForge\n")
55
+ continue
56
+
57
+ base_path = Path.home() / "stackforge" / project_name
58
+
59
+ if base_path.exists():
60
+ print(f"\n [!] Project '{project_name}' already exists in the stackforge workspace.")
61
+ print(f" Path: {base_path}")
62
+ print(" Choose a different project name or delete the existing one.\n")
63
+ continue
64
+
65
+ base_path.mkdir(parents=True, exist_ok=True)
66
+ scaffold_folders(base_path, project_name)
67
+
68
+ print(f"""
69
+ ✔ Project '{project_name}' created successfully!
70
+ ✔ Location: {base_path}
71
+
72
+ Next steps:
73
+ 1. Open the project in Visual Studio or VS Code
74
+ 2. Run: docker-compose up --build
75
+ 3. Visit: http://localhost:5000/swagger
76
+
77
+ Press Ctrl+C to exit or continue to scaffold another project.
78
+ """)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ main()
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: stackforge-cli
3
+ Version: 0.1.0
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
+
14
+ # StackForge
15
+
16
+ A CLI scaffolder for .NET Clean Architecture (CQRS) projects.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install stackforge
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ stackforge
28
+ ```
29
+
30
+ Then follow the prompts to scaffold a new project.
31
+
32
+ ## What it generates
33
+
34
+ - .NET 10 Clean Architecture structure (API, Application, Domain, Infrastructure, Persistence)
35
+ - JWT authentication with ASP.NET Identity
36
+ - Swagger UI with Bearer token support
37
+ - PostgreSQL with EF Core
38
+ - Docker + docker-compose with healthchecks
39
+ - Role and user seeders (SuperAdmin out of the box)
40
+ - Git initialization with initial commit
41
+
42
+ ## Quickstart after scaffolding
43
+
44
+ 1. Open the project in VS Code or Visual Studio
45
+ 2. Run: `docker-compose up --build`
46
+ 3. Visit: `http://localhost:5000/swagger`
@@ -0,0 +1,16 @@
1
+ stackforge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ stackforge/scaffold.py,sha256=0RfIzXY_b7-NzhRq6fT-5jT-wJ-IbXvDniUs8yxLLUs,2697
3
+ stackforge/engines/dotnet/__init__.py,sha256=bxCYj7mEmcLZZ06vgUPe8l9_PAp_dTDYLbaVK7sotQM,592
4
+ stackforge/engines/dotnet/api_generator.py,sha256=06dPkqhqadIV2xNrlrMzNNvf9n2iG2zro8lzZ6jk-uw,9783
5
+ stackforge/engines/dotnet/config_generator.py,sha256=cEKpUJ9t6gRNk9TnWQ1LXrxUk1WYLxDw_DH8T-X7gvA,2030
6
+ stackforge/engines/dotnet/csproj_generator.py,sha256=aGoqkJLIVggTxM_tlyMH_RESI4Qy0BY6Pdo3OJLF7ao,2483
7
+ stackforge/engines/dotnet/docker_generator.py,sha256=V4r2UKcMAR92-3yJBXjVfo1u-_HOtw9Zf2mrAp7bjDM,1624
8
+ stackforge/engines/dotnet/git_generator.py,sha256=_fRnGnMLitw5GPxt1yKVCwCDequpMF-XZe-qYzSn6Gs,1718
9
+ stackforge/engines/dotnet/layers_generator.py,sha256=EM5agcRcZAL_9s980sEP1iECuE518O-L6NndFtH3U2o,18132
10
+ stackforge/engines/dotnet/program_generator.py,sha256=bCjTI8-aY9kLOOVZASF9U8HIEkVgOhtAndkV0Sq8V10,6037
11
+ stackforge/engines/dotnet/scaffolder.py,sha256=Tco9MnM-qEBkTps472inZ62rTAcnecwpUBvAofE56gY,2289
12
+ stackforge_cli-0.1.0.dist-info/METADATA,sha256=qXkOAIe6WnQao8vOhYBiCUsrroKQdX_KjhJzY9DUHM8,1215
13
+ stackforge_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
+ stackforge_cli-0.1.0.dist-info/entry_points.txt,sha256=r6YqYMrqMdXBiKTqSNdbEjwuzhCSksAlAmdhnqIOvJE,56
15
+ stackforge_cli-0.1.0.dist-info/top_level.txt,sha256=rLeEUKSWLyFKeW9uVuvTDIxq05hOic0cG7Xy3XlSYI8,11
16
+ stackforge_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stackforge = stackforge.scaffold:main
@@ -0,0 +1 @@
1
+ stackforge