stackforge-cli 0.1.2__py3-none-any.whl → 0.1.4__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.
@@ -1,195 +1,245 @@
1
- from pathlib import Path
2
- from stackforge import ui
3
-
4
-
5
- def generate_configure_services(layer: str, base: Path, project: str):
6
- templates = {
7
- "Application": f"""using FluentValidation;
8
- using MediatR;
9
- using Microsoft.Extensions.DependencyInjection;
10
- using System.Reflection;
11
-
12
- namespace {project}.Application
13
- {{
14
- public static class ConfigureServices
15
- {{
16
- public static IServiceCollection AddApplicationServices(this IServiceCollection services)
17
- {{
18
- services.AddMediatR(cfg =>
19
- cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
20
-
21
- services.AddAutoMapper(cfg => cfg.AddMaps(Assembly.GetExecutingAssembly()));
22
-
23
- services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
24
-
25
- return services;
26
- }}
27
- }}
28
- }}
29
- """,
30
- "Infrastructure": f"""using Microsoft.Extensions.DependencyInjection;
31
-
32
- namespace {project}.Infrastructure
33
- {{
34
- public static class ConfigureServices
35
- {{
36
- public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
37
- {{
38
- // Register infrastructure services here
39
-
40
- return services;
41
- }}
42
- }}
43
- }}
44
- """,
45
- "Persistence": f"""using {project}.Persistence;
46
- using Microsoft.EntityFrameworkCore;
47
- using Microsoft.Extensions.Configuration;
48
- using Microsoft.Extensions.DependencyInjection;
49
-
50
- namespace {project}.Persistence
51
- {{
52
- public static class ConfigureServices
53
- {{
54
- public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
55
- {{
56
- services.AddDbContext<DataContext>(options =>
57
- options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));
58
-
59
- return services;
60
- }}
61
- }}
62
- }}
63
- """,
64
- }
65
-
66
- if layer not in templates:
67
- return
68
-
69
- file_path = base / layer / "ConfigureServices.cs"
70
- file_path.write_text(templates[layer])
71
- ui.file_created(f"{layer}/ConfigureServices.cs")
72
-
73
-
74
- def generate_program_cs(base: Path, project: str):
75
- content = f"""using {project}.Application;
76
- using {project}.Infrastructure;
77
- using {project}.Persistence;
78
- using {project}.API.Interface;
79
- using {project}.API.Options;
80
- using {project}.API.Services;
81
- using Microsoft.AspNetCore.Authentication.JwtBearer;
82
- using Microsoft.AspNetCore.Identity;
83
- using Microsoft.IdentityModel.Tokens;
84
- using {project}.Domain.Entities.Identity;
85
- using System.Text;
86
-
87
- var builder = WebApplication.CreateBuilder(args);
88
-
89
- // Layer services
90
- builder.Services.AddApplicationServices();
91
- builder.Services.AddInfrastructureServices();
92
- builder.Services.AddPersistenceServices(builder.Configuration);
93
-
94
- // Identity
95
- builder.Services.AddIdentity<User, Role>(options =>
96
- {{
97
- options.Password.RequireDigit = true;
98
- options.Password.RequireLowercase = true;
99
- options.Password.RequireUppercase = true;
100
- options.Password.RequireNonAlphanumeric = true;
101
- options.Password.RequiredLength = 8;
102
- }})
103
- .AddEntityFrameworkStores<DataContext>()
104
- .AddDefaultTokenProviders();
105
-
106
- // JWT
107
- builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt"));
108
- var jwtOptions = new {project}.API.Options.JwtOptions();
109
- builder.Configuration.GetSection("Jwt").Bind(jwtOptions);
110
- builder.Services.AddScoped<IJwtService, JwtService>();
111
-
112
- builder.Services.AddAuthentication(options =>
113
- {{
114
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
115
- options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
116
- }})
117
- .AddJwtBearer(options =>
118
- {{
119
- options.TokenValidationParameters = new TokenValidationParameters
120
- {{
121
- ValidateIssuer = true,
122
- ValidateAudience = true,
123
- ValidateLifetime = true,
124
- ValidateIssuerSigningKey = true,
125
- ValidIssuer = jwtOptions.Issuer,
126
- ValidAudience = jwtOptions.Audience,
127
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
128
- }};
129
- }});
130
-
131
- // Controllers
132
- builder.Services.AddControllers();
133
-
134
- // Swagger
135
- builder.Services.AddEndpointsApiExplorer();
136
- builder.Services.AddSwaggerGen(c =>
137
- {{
138
- c.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
139
- {{
140
- Name = "Authorization",
141
- Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
142
- Scheme = "bearer",
143
- BearerFormat = "JWT",
144
- In = Microsoft.OpenApi.Models.ParameterLocation.Header,
145
- Description = "Enter your JWT token only (no 'Bearer' prefix needed)"
146
- }});
147
- c.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
148
- {{
149
- {{
150
- new Microsoft.OpenApi.Models.OpenApiSecurityScheme
151
- {{
152
- Reference = new Microsoft.OpenApi.Models.OpenApiReference
153
- {{
154
- Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
155
- Id = "Bearer"
156
- }}
157
- }},
158
- Array.Empty<string>()
159
- }}
160
- }});
161
- }});
162
-
163
- var app = builder.Build();
164
-
165
- if (app.Environment.IsDevelopment())
166
- {{
167
- app.UseSwagger();
168
- app.UseSwaggerUI();
169
- }}
170
-
171
- if (!app.Environment.IsDevelopment())
172
- {{
173
- app.UseHttpsRedirection();
174
- }}
175
- app.UseAuthentication();
176
- app.UseAuthorization();
177
- app.MapControllers();
178
-
179
- using (var scope = app.Services.CreateScope())
180
- {{
181
- var db = scope.ServiceProvider.GetRequiredService<DataContext>();
182
- db.Database.EnsureCreated();
183
-
184
- var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
185
- var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
186
- await {project}.Persistence.Seeders.RoleSeeder.SeedAsync(roleManager);
187
- await {project}.Persistence.Seeders.UserSeeder.SeedAsync(userManager);
188
- }}
189
-
190
- app.Run();
191
- """
192
-
193
- file_path = base / "API" / "Program.cs"
194
- file_path.write_text(content)
195
- ui.file_created("API/Program.cs")
1
+ from pathlib import Path
2
+ from stackforge import ui
3
+
4
+
5
+ def generate_configure_services(layer: str, base: Path, project: str):
6
+ templates = {
7
+ "Application": f"""using FluentValidation;
8
+ using MediatR;
9
+ using Microsoft.Extensions.DependencyInjection;
10
+ using System.Reflection;
11
+
12
+ namespace {project}.Application
13
+ {{
14
+ public static class ConfigureServices
15
+ {{
16
+ public static IServiceCollection AddApplicationServices(this IServiceCollection services)
17
+ {{
18
+ services.AddMediatR(cfg =>
19
+ cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
20
+
21
+ services.AddAutoMapper(cfg => cfg.AddMaps(Assembly.GetExecutingAssembly()));
22
+
23
+ services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
24
+
25
+ return services;
26
+ }}
27
+ }}
28
+ }}
29
+ """,
30
+ "Infrastructure": f"""using Microsoft.Extensions.DependencyInjection;
31
+
32
+ namespace {project}.Infrastructure
33
+ {{
34
+ public static class ConfigureServices
35
+ {{
36
+ public static IServiceCollection AddInfrastructureServices(this IServiceCollection services)
37
+ {{
38
+ // Register infrastructure services here
39
+
40
+ return services;
41
+ }}
42
+ }}
43
+ }}
44
+ """,
45
+ "Persistence": f"""using {project}.Persistence;
46
+ using Microsoft.EntityFrameworkCore;
47
+ using Microsoft.Extensions.Configuration;
48
+ using Microsoft.Extensions.DependencyInjection;
49
+
50
+ namespace {project}.Persistence
51
+ {{
52
+ public static class ConfigureServices
53
+ {{
54
+ public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
55
+ {{
56
+ services.AddDbContext<DataContext>(options =>
57
+ options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));
58
+
59
+ return services;
60
+ }}
61
+ }}
62
+ }}
63
+ """,
64
+ "API": f"""using {project}.API.Interface;
65
+ using {project}.API.Options;
66
+ using {project}.API.Services;
67
+ using {project}.Domain.Entities.Identity;
68
+ using {project}.Persistence;
69
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
70
+ using Microsoft.AspNetCore.Authorization;
71
+ using Microsoft.AspNetCore.Identity;
72
+ using Microsoft.IdentityModel.Tokens;
73
+ using Microsoft.OpenApi.Models;
74
+ using System.Text;
75
+ using System.Text.Json.Serialization;
76
+
77
+ namespace {project}.API
78
+ {{
79
+ public static class ConfigureServices
80
+ {{
81
+ public const string CorsPolicy = "Cors";
82
+
83
+ public static IServiceCollection AddApiServices(this IServiceCollection services, IConfiguration configuration)
84
+ {{
85
+ services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
86
+ var jwtOptions = new JwtOptions();
87
+ configuration.GetSection("Jwt").Bind(jwtOptions);
88
+
89
+ services.AddControllers()
90
+ .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
91
+
92
+ services.AddEndpointsApiExplorer();
93
+
94
+ services.AddSwaggerGen(config =>
95
+ {{
96
+ config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
97
+ {{
98
+ Name = "Authorization",
99
+ Type = SecuritySchemeType.Http,
100
+ Scheme = "bearer",
101
+ BearerFormat = "JWT",
102
+ In = ParameterLocation.Header,
103
+ Description = "Enter your JWT token only (no 'Bearer' prefix needed)"
104
+ }});
105
+
106
+ config.AddSecurityRequirement(new OpenApiSecurityRequirement
107
+ {{
108
+ {{
109
+ new OpenApiSecurityScheme
110
+ {{
111
+ Reference = new OpenApiReference
112
+ {{
113
+ Type = ReferenceType.SecurityScheme,
114
+ Id = "Bearer"
115
+ }}
116
+ }},
117
+ Array.Empty<string>()
118
+ }}
119
+ }});
120
+ }});
121
+
122
+ // Browser origins allowed to call the API. Format: scheme + host (+ port if non-default), no trailing slash.
123
+ // Only add frontends you own: an allowed origin's scripts can call this API from a visitor's browser.
124
+ // Bearer tokens travel in the Authorization header, so credentials (cookies) are not allowed.
125
+ services.AddCors(options =>
126
+ {{
127
+ options.AddPolicy(CorsPolicy, policy =>
128
+ {{
129
+ policy.AllowAnyMethod()
130
+ .AllowAnyHeader()
131
+ .WithExposedHeaders("WWW-Authenticate", "Retry-After")
132
+ .WithOrigins(
133
+ "http://localhost:3000"
134
+ );
135
+ }});
136
+ }});
137
+
138
+ services.AddIdentity<User, Role>(options =>
139
+ {{
140
+ options.Password.RequireDigit = true;
141
+ options.Password.RequireLowercase = true;
142
+ options.Password.RequireUppercase = true;
143
+ options.Password.RequireNonAlphanumeric = true;
144
+ options.Password.RequiredLength = 8;
145
+ }})
146
+ .AddEntityFrameworkStores<DataContext>()
147
+ .AddDefaultTokenProviders();
148
+
149
+ services.AddAuthentication(options =>
150
+ {{
151
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
152
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
153
+ }})
154
+ .AddJwtBearer(options =>
155
+ {{
156
+ options.TokenValidationParameters = new TokenValidationParameters
157
+ {{
158
+ ValidateIssuer = true,
159
+ ValidateAudience = true,
160
+ ValidateLifetime = true,
161
+ ValidateIssuerSigningKey = true,
162
+ ValidIssuer = jwtOptions.Issuer,
163
+ ValidAudience = jwtOptions.Audience,
164
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecretKey))
165
+ }};
166
+ }});
167
+
168
+ // Deny by default: endpoints without an explicit policy require an authenticated user.
169
+ // Public endpoints must opt out with [AllowAnonymous].
170
+ services.AddAuthorization(options =>
171
+ {{
172
+ options.FallbackPolicy = new AuthorizationPolicyBuilder()
173
+ .RequireAuthenticatedUser()
174
+ .Build();
175
+ }});
176
+
177
+ //Scopes
178
+ services.AddScoped<IJwtService, JwtService>();
179
+
180
+ return services;
181
+ }}
182
+ }}
183
+ }}
184
+ """,
185
+ }
186
+
187
+ if layer not in templates:
188
+ return
189
+
190
+ file_path = base / layer / "ConfigureServices.cs"
191
+ file_path.write_text(templates[layer])
192
+ ui.file_created(f"{layer}/ConfigureServices.cs")
193
+
194
+
195
+ def generate_program_cs(base: Path, project: str):
196
+ content = f"""using {project}.API;
197
+ using {project}.Application;
198
+ using {project}.Domain.Entities.Identity;
199
+ using {project}.Infrastructure;
200
+ using {project}.Persistence;
201
+ using Microsoft.AspNetCore.Identity;
202
+
203
+ var builder = WebApplication.CreateBuilder(args);
204
+
205
+ // Layer services
206
+ builder.Services.AddApplicationServices();
207
+ builder.Services.AddInfrastructureServices();
208
+ builder.Services.AddPersistenceServices(builder.Configuration);
209
+ builder.Services.AddApiServices(builder.Configuration);
210
+
211
+ var app = builder.Build();
212
+
213
+ if (app.Environment.IsDevelopment())
214
+ {{
215
+ app.UseSwagger();
216
+ app.UseSwaggerUI();
217
+ }}
218
+
219
+ if (!app.Environment.IsDevelopment())
220
+ {{
221
+ app.UseHttpsRedirection();
222
+ }}
223
+ app.UseRouting();
224
+ app.UseCors({project}.API.ConfigureServices.CorsPolicy);
225
+ app.UseAuthentication();
226
+ app.UseAuthorization();
227
+ app.MapControllers();
228
+
229
+ using (var scope = app.Services.CreateScope())
230
+ {{
231
+ var db = scope.ServiceProvider.GetRequiredService<DataContext>();
232
+ db.Database.EnsureCreated();
233
+
234
+ var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
235
+ var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
236
+ await {project}.Persistence.Seeders.RoleSeeder.SeedAsync(roleManager);
237
+ await {project}.Persistence.Seeders.UserSeeder.SeedAsync(userManager);
238
+ }}
239
+
240
+ app.Run();
241
+ """
242
+
243
+ file_path = base / "API" / "Program.cs"
244
+ file_path.write_text(content)
245
+ ui.file_created("API/Program.cs")
@@ -1,74 +1,74 @@
1
- from pathlib import Path
2
- from stackforge import ui
3
- from .csproj_generator import generate_csproj
4
- from .program_generator import generate_configure_services, generate_program_cs
5
- from .docker_generator import generate_dockerfile, generate_docker_compose
6
- from .config_generator import generate_env, generate_appsettings, generate_appsettings_development
7
- from .git_generator import generate_gitignore, generate_github_workflow, generate_sln, git_init
8
- from .api_generator import generate_api_structure
9
- from .layers_generator import (
10
- generate_application_layer,
11
- generate_domain_layer,
12
- generate_infrastructure_layer,
13
- generate_persistence_layer,
14
- )
15
-
16
- def scaffold_folders(base: Path, project: str):
17
- layers = [
18
- "API",
19
- "Application",
20
- "Domain",
21
- "Infrastructure",
22
- "Persistence"
23
- ]
24
-
25
- ui.section(f"Scaffolding '{project}'")
26
-
27
- ui.step("Creating folders")
28
- for layer in layers:
29
- folder = base / layer
30
- folder.mkdir(parents=True, exist_ok=True)
31
- ui.file_created(layer)
32
-
33
- ui.section("Generating .csproj files")
34
- for layer in layers:
35
- generate_csproj(layer, base)
36
-
37
- ui.section("Generating service registrations")
38
- for layer in ["Application", "Infrastructure", "Persistence"]:
39
- generate_configure_services(layer, base, project)
40
-
41
- ui.section("Generating Program.cs")
42
- generate_program_cs(base, project)
43
-
44
- ui.section("Generating Docker files")
45
- generate_dockerfile(base)
46
- generate_docker_compose(base, project)
47
-
48
- ui.section("Generating config files")
49
- generate_env(base, project)
50
- generate_appsettings(base, project)
51
- generate_appsettings_development(base, project)
52
-
53
- ui.section("Generating API layer")
54
- generate_api_structure(base, project)
55
-
56
- ui.section("Generating 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
- ui.section("Generating solution file")
63
- generate_sln(base, project)
64
-
65
- ui.section("Generating .gitignore")
66
- generate_gitignore(base)
67
-
68
- ui.section("Generating GitHub workflow")
69
- generate_github_workflow(base)
70
-
71
- ui.section("Initializing Git repository")
72
- git_init(base, project)
73
-
74
- ui.done()
1
+ from pathlib import Path
2
+ from stackforge import ui
3
+ from .csproj_generator import generate_csproj
4
+ from .program_generator import generate_configure_services, generate_program_cs
5
+ from .docker_generator import generate_dockerfile, generate_docker_compose
6
+ from .config_generator import generate_env, generate_appsettings, generate_appsettings_development
7
+ from .git_generator import generate_gitignore, generate_github_workflow, generate_sln, git_init
8
+ from .api_generator import generate_api_structure
9
+ from .layers_generator import (
10
+ generate_application_layer,
11
+ generate_domain_layer,
12
+ generate_infrastructure_layer,
13
+ generate_persistence_layer,
14
+ )
15
+
16
+ def scaffold_folders(base: Path, project: str):
17
+ layers = [
18
+ "API",
19
+ "Application",
20
+ "Domain",
21
+ "Infrastructure",
22
+ "Persistence"
23
+ ]
24
+
25
+ ui.section(f"Scaffolding '{project}'")
26
+
27
+ ui.step("Creating folders")
28
+ for layer in layers:
29
+ folder = base / layer
30
+ folder.mkdir(parents=True, exist_ok=True)
31
+ ui.file_created(layer)
32
+
33
+ ui.section("Generating .csproj files")
34
+ for layer in layers:
35
+ generate_csproj(layer, base)
36
+
37
+ ui.section("Generating service registrations")
38
+ for layer in ["Application", "Infrastructure", "Persistence", "API"]:
39
+ generate_configure_services(layer, base, project)
40
+
41
+ ui.section("Generating Program.cs")
42
+ generate_program_cs(base, project)
43
+
44
+ ui.section("Generating Docker files")
45
+ generate_dockerfile(base)
46
+ generate_docker_compose(base, project)
47
+
48
+ ui.section("Generating config files")
49
+ generate_env(base, project)
50
+ generate_appsettings(base, project)
51
+ generate_appsettings_development(base, project)
52
+
53
+ ui.section("Generating API layer")
54
+ generate_api_structure(base, project)
55
+
56
+ ui.section("Generating 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
+ ui.section("Generating solution file")
63
+ generate_sln(base, project)
64
+
65
+ ui.section("Generating .gitignore")
66
+ generate_gitignore(base)
67
+
68
+ ui.section("Generating GitHub workflow")
69
+ generate_github_workflow(base)
70
+
71
+ ui.section("Initializing Git repository")
72
+ git_init(base, project)
73
+
74
+ ui.done()
@@ -1,34 +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)
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)