stackforge-cli 0.1.4__py3-none-any.whl → 0.1.5__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.
Files changed (55) hide show
  1. stackforge/engines/dotnet/api_generator.py +30 -305
  2. stackforge/engines/dotnet/config_generator.py +7 -64
  3. stackforge/engines/dotnet/docker_generator.py +6 -55
  4. stackforge/engines/dotnet/git_generator.py +7 -56
  5. stackforge/engines/dotnet/layers_generator.py +85 -526
  6. stackforge/engines/dotnet/program_generator.py +8 -232
  7. stackforge/templates/dotnet/API/Configurations/Swagger/ApiRoles.cs.tmpl +9 -0
  8. stackforge/templates/dotnet/API/ConfigureServices.cs.tmpl +120 -0
  9. stackforge/templates/dotnet/API/Controllers/BaseApiController.cs.tmpl +24 -0
  10. stackforge/templates/dotnet/API/Controllers/Main/AccountController.cs.tmpl +68 -0
  11. stackforge/templates/dotnet/API/DTOs/AccountDTOs.cs.tmpl +23 -0
  12. stackforge/templates/dotnet/API/Interface/IJwtService.cs.tmpl +7 -0
  13. stackforge/templates/dotnet/API/Options/JwtOptions.cs.tmpl +10 -0
  14. stackforge/templates/dotnet/API/Program.cs.tmpl +45 -0
  15. stackforge/templates/dotnet/API/Properties/launchSettings.json.tmpl +31 -0
  16. stackforge/templates/dotnet/API/Services/JwtService.cs.tmpl +41 -0
  17. stackforge/templates/dotnet/API/Swagger/EnumMemberSchemaFilter.cs.tmpl +25 -0
  18. stackforge/templates/dotnet/API/appsettings.Development.json.tmpl +17 -0
  19. stackforge/templates/dotnet/API/appsettings.json.tmpl +18 -0
  20. stackforge/templates/dotnet/Application/Common/Behaviors/ValidationBehavior.cs.tmpl +29 -0
  21. stackforge/templates/dotnet/Application/Common/Constants/ErrorMessages.cs.tmpl +10 -0
  22. stackforge/templates/dotnet/Application/Common/Exceptions/AppExceptions.cs.tmpl +12 -0
  23. stackforge/templates/dotnet/Application/Common/Exceptions/NotFoundException.cs.tmpl +10 -0
  24. stackforge/templates/dotnet/Application/Common/Models/ApiResponse.cs.tmpl +15 -0
  25. stackforge/templates/dotnet/Application/Common/Models/PagedList.cs.tmpl +30 -0
  26. stackforge/templates/dotnet/Application/Common/Models/PagedResponse.cs.tmpl +13 -0
  27. stackforge/templates/dotnet/Application/Common/Models/PaginatedResult.cs.tmpl +13 -0
  28. stackforge/templates/dotnet/Application/Common/Models/PagingParams.cs.tmpl +8 -0
  29. stackforge/templates/dotnet/Application/Common/Models/Result.cs.tmpl +30 -0
  30. stackforge/templates/dotnet/Application/Common/Models/Serialization/IsoDateTime.cs.tmpl +14 -0
  31. stackforge/templates/dotnet/Application/ConfigureServices.cs.tmpl +22 -0
  32. stackforge/templates/dotnet/Dockerfile.tmpl +8 -0
  33. stackforge/templates/dotnet/Domain/Common/BaseEntity.cs.tmpl +9 -0
  34. stackforge/templates/dotnet/Domain/Common/EnumMemberExtensions.cs.tmpl +16 -0
  35. stackforge/templates/dotnet/Domain/Entities/Identity/Role.cs.tmpl +9 -0
  36. stackforge/templates/dotnet/Domain/Entities/Identity/User.cs.tmpl +13 -0
  37. stackforge/templates/dotnet/Domain/Entities/Identity/UserRole.cs.tmpl +10 -0
  38. stackforge/templates/dotnet/Domain/GlobalUsings.cs.tmpl +2 -0
  39. stackforge/templates/dotnet/Infrastructure/ConfigureServices.cs.tmpl +14 -0
  40. stackforge/templates/dotnet/Persistence/Configurations/UserRoleConfiguration.cs.tmpl +24 -0
  41. stackforge/templates/dotnet/Persistence/ConfigureServices.cs.tmpl +18 -0
  42. stackforge/templates/dotnet/Persistence/DataContext.cs.tmpl +36 -0
  43. stackforge/templates/dotnet/Persistence/Seeders/RoleSeeder.cs.tmpl +19 -0
  44. stackforge/templates/dotnet/Persistence/Seeders/UserSeeder.cs.tmpl +28 -0
  45. stackforge/templates/dotnet/docker-compose.yml.tmpl +36 -0
  46. stackforge/templates/dotnet/dot_env.tmpl +10 -0
  47. stackforge/templates/dotnet/dot_github/workflows/sync-branches.yml.tmpl +29 -0
  48. stackforge/templates/dotnet/dot_gitignore.tmpl +15 -0
  49. stackforge/templating.py +39 -0
  50. {stackforge_cli-0.1.4.dist-info → stackforge_cli-0.1.5.dist-info}/METADATA +3 -1
  51. stackforge_cli-0.1.5.dist-info/RECORD +62 -0
  52. stackforge_cli-0.1.4.dist-info/RECORD +0 -19
  53. {stackforge_cli-0.1.4.dist-info → stackforge_cli-0.1.5.dist-info}/WHEEL +0 -0
  54. {stackforge_cli-0.1.4.dist-info → stackforge_cli-0.1.5.dist-info}/entry_points.txt +0 -0
  55. {stackforge_cli-0.1.4.dist-info → stackforge_cli-0.1.5.dist-info}/top_level.txt +0 -0
@@ -1,314 +1,39 @@
1
1
  from pathlib import Path
2
2
  from stackforge import ui
3
+ from stackforge.templating import render_file
4
+
5
+ ENGINE = "dotnet"
6
+
7
+ API_FOLDERS = [
8
+ "API/Configurations/Swagger",
9
+ "API/Controllers/Main",
10
+ "API/Helpers",
11
+ "API/Interface",
12
+ "API/Options",
13
+ "API/Properties",
14
+ "API/Services",
15
+ "API/Swagger",
16
+ "API/DTOs",
17
+ ]
18
+
19
+ API_TEMPLATES = [
20
+ "API/Controllers/BaseApiController.cs",
21
+ "API/Options/JwtOptions.cs",
22
+ "API/Interface/IJwtService.cs",
23
+ "API/Services/JwtService.cs",
24
+ "API/Swagger/EnumMemberSchemaFilter.cs",
25
+ "API/Configurations/Swagger/ApiRoles.cs",
26
+ "API/Properties/launchSettings.json",
27
+ "API/DTOs/AccountDTOs.cs",
28
+ "API/Controllers/Main/AccountController.cs",
29
+ ]
3
30
 
4
31
 
5
32
  def generate_api_structure(base: Path, project: str):
6
- # Folders
7
- folders = [
8
- "API/Configurations/Swagger",
9
- "API/Controllers/Main",
10
- "API/Helpers",
11
- "API/Interface",
12
- "API/Options",
13
- "API/Properties",
14
- "API/Services",
15
- "API/Swagger",
16
- "API/DTOs",
17
- ]
18
-
19
- for folder in folders:
33
+ for folder in API_FOLDERS:
20
34
  (base / folder).mkdir(parents=True, exist_ok=True)
21
35
 
22
- _generate_base_controller(base, project)
23
- _generate_jwt_options(base, project)
24
- _generate_ijwt_service(base, project)
25
- _generate_jwt_service(base, project)
26
- _generate_enum_schema_filter(base, project)
27
- _generate_api_roles(base, project)
28
- _generate_launch_settings(base)
29
- _generate_login_dto(base, project)
30
- _generate_account_controller(base, project)
36
+ for template in API_TEMPLATES:
37
+ render_file(ENGINE, base, template, project)
31
38
 
32
39
  ui.file_created("API/ structure and files")
33
-
34
-
35
- def _generate_base_controller(base: Path, project: str):
36
- content = f"""using Microsoft.AspNetCore.Mvc;
37
- using System.Security.Claims;
38
-
39
- namespace {project}.API.Controllers
40
- {{
41
- [Route("api/[controller]")]
42
- [ApiController]
43
- public class BaseApiController : ControllerBase
44
- {{
45
- protected Guid GetUserId() => Guid.Parse(User?.FindFirst(ClaimTypes.NameIdentifier)?.Value!);
46
-
47
- protected string GetCurrentEmail() => User?.FindFirstValue(ClaimTypes.Email)!;
48
-
49
- protected Guid? GetOrganizationId()
50
- {{
51
- var claim = User?.FindFirst("OrganizationId");
52
-
53
- if (claim == null || !Guid.TryParse(claim.Value, out var orgId))
54
- return null;
55
-
56
- return orgId;
57
- }}
58
- }}
59
- }}
60
- """
61
- (base / "API/Controllers/BaseApiController.cs").write_text(content)
62
-
63
-
64
- def _generate_jwt_options(base: Path, project: str):
65
- content = f"""namespace {project}.API.Options
66
- {{
67
- public class JwtOptions
68
- {{
69
- public string Issuer {{ get; set; }} = string.Empty;
70
- public string Audience {{ get; set; }} = string.Empty;
71
- public string SecretKey {{ get; set; }} = string.Empty;
72
- public int ExpiryMinutes {{ get; set; }}
73
- }}
74
- }}
75
- """
76
- (base / "API/Options/JwtOptions.cs").write_text(content)
77
-
78
-
79
- def _generate_ijwt_service(base: Path, project: str):
80
- content = f"""namespace {project}.API.Interface
81
- {{
82
- public interface IJwtService
83
- {{
84
- string GenerateToken(Guid userId, string email);
85
- }}
86
- }}
87
- """
88
- (base / "API/Interface/IJwtService.cs").write_text(content)
89
-
90
-
91
- def _generate_jwt_service(base: Path, project: str):
92
- content = f"""using {project}.API.Interface;
93
- using {project}.API.Options;
94
- using Microsoft.Extensions.Options;
95
- using Microsoft.IdentityModel.Tokens;
96
- using System.IdentityModel.Tokens.Jwt;
97
- using System.Security.Claims;
98
- using System.Text;
99
-
100
- namespace {project}.API.Services
101
- {{
102
- public class JwtService : IJwtService
103
- {{
104
- private readonly JwtOptions _options;
105
-
106
- public JwtService(IOptions<JwtOptions> options)
107
- {{
108
- _options = options.Value;
109
- }}
110
-
111
- public string GenerateToken(Guid userId, string email)
112
- {{
113
- var claims = new[]
114
- {{
115
- new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
116
- new Claim(ClaimTypes.Email, email),
117
- }};
118
-
119
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey));
120
- var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
121
-
122
- var token = new JwtSecurityToken(
123
- issuer: _options.Issuer,
124
- audience: _options.Audience,
125
- claims: claims,
126
- expires: DateTime.UtcNow.AddMinutes(_options.ExpiryMinutes),
127
- signingCredentials: creds);
128
-
129
- return new JwtSecurityTokenHandler().WriteToken(token);
130
- }}
131
- }}
132
- }}
133
- """
134
- (base / "API/Services/JwtService.cs").write_text(content)
135
-
136
-
137
- def _generate_enum_schema_filter(base: Path, project: str):
138
- content = f"""using Microsoft.OpenApi.Any;
139
- using Microsoft.OpenApi.Models;
140
- using Swashbuckle.AspNetCore.SwaggerGen;
141
- using System.Runtime.Serialization;
142
-
143
- namespace {project}.API.Swagger
144
- {{
145
- public class EnumMemberSchemaFilter : ISchemaFilter
146
- {{
147
- public void Apply(OpenApiSchema schema, SchemaFilterContext context)
148
- {{
149
- if (!context.Type.IsEnum) return;
150
-
151
- schema.Enum.Clear();
152
- foreach (var member in Enum.GetNames(context.Type))
153
- {{
154
- var field = context.Type.GetField(member);
155
- var attr = field?.GetCustomAttributes(typeof(EnumMemberAttribute), false)
156
- .OfType<EnumMemberAttribute>()
157
- .FirstOrDefault();
158
- schema.Enum.Add(new OpenApiString(attr?.Value ?? member));
159
- }}
160
- }}
161
- }}
162
- }}
163
- """
164
- (base / "API/Swagger/EnumMemberSchemaFilter.cs").write_text(content)
165
-
166
-
167
- def _generate_api_roles(base: Path, project: str):
168
- content = f"""namespace {project}.API.Configurations.Swagger
169
- {{
170
- public static class ApiRoles
171
- {{
172
- public const string SuperAdmin = "SuperAdmin";
173
- public const string Admin = "Admin";
174
- public const string User = "User";
175
- }}
176
- }}
177
- """
178
- (base / "API/Configurations/Swagger/ApiRoles.cs").write_text(content)
179
-
180
-
181
- def _generate_launch_settings(base: Path):
182
- content = """{
183
- "$schema": "https://json.schemastore.org/launchsettings.json",
184
- "profiles": {
185
- "http": {
186
- "commandName": "Project",
187
- "environmentVariables": {
188
- "ASPNETCORE_ENVIRONMENT": "Development"
189
- },
190
- "dotnetRunMessages": true,
191
- "applicationUrl": "http://localhost:5000"
192
- },
193
- "https": {
194
- "commandName": "Project",
195
- "environmentVariables": {
196
- "ASPNETCORE_ENVIRONMENT": "Development"
197
- },
198
- "dotnetRunMessages": true,
199
- "applicationUrl": "https://localhost:7013;http://localhost:5282"
200
- },
201
- "Container (Dockerfile)": {
202
- "commandName": "Docker",
203
- "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
204
- "environmentVariables": {
205
- "ASPNETCORE_HTTPS_PORTS": "8081",
206
- "ASPNETCORE_HTTP_PORTS": "8080"
207
- },
208
- "publishAllPorts": true,
209
- "useSSL": true
210
- }
211
- }
212
- }
213
- """
214
- (base / "API/Properties/launchSettings.json").write_text(content)
215
-
216
- def _generate_login_dto(base: Path, project: str):
217
- content = f"""namespace {project}.API.DTOs
218
- {{
219
- public class LoginRequest
220
- {{
221
- public string Email {{ get; set; }} = string.Empty;
222
- public string Password {{ get; set; }} = string.Empty;
223
- }}
224
-
225
- public class LoginResponse
226
- {{
227
- public string Token {{ get; set; }} = string.Empty;
228
- public string Email {{ get; set; }} = string.Empty;
229
- public string FullName {{ get; set; }} = string.Empty;
230
- }}
231
-
232
- public class CurrentUserResponse
233
- {{
234
- public Guid Id {{ get; set; }}
235
- public string Email {{ get; set; }} = string.Empty;
236
- public string FullName {{ get; set; }} = string.Empty;
237
- public IList<string> Roles {{ get; set; }} = new List<string>();
238
- }}
239
- }}
240
- """
241
- (base / "API/DTOs/AccountDTOs.cs").write_text(content)
242
-
243
-
244
- def _generate_account_controller(base: Path, project: str):
245
- content = f"""using {project}.API.Controllers;
246
- using {project}.API.DTOs;
247
- using {project}.API.Interface;
248
- using {project}.Domain.Entities.Identity;
249
- using Microsoft.AspNetCore.Authorization;
250
- using Microsoft.AspNetCore.Identity;
251
- using Microsoft.AspNetCore.Mvc;
252
-
253
- namespace {project}.API.Controllers.Main
254
- {{
255
- public class AccountController : BaseApiController
256
- {{
257
- private readonly UserManager<User> _userManager;
258
- private readonly SignInManager<User> _signInManager;
259
- private readonly IJwtService _jwtService;
260
-
261
- public AccountController(
262
- UserManager<User> userManager,
263
- SignInManager<User> signInManager,
264
- IJwtService jwtService)
265
- {{
266
- _userManager = userManager;
267
- _signInManager = signInManager;
268
- _jwtService = jwtService;
269
- }}
270
-
271
- [AllowAnonymous]
272
- [HttpPost("login")]
273
- public async Task<IActionResult> Login([FromBody] LoginRequest request)
274
- {{
275
- var user = await _userManager.FindByEmailAsync(request.Email);
276
- if (user == null)
277
- return Unauthorized("Invalid credentials.");
278
-
279
- var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);
280
- if (!result.Succeeded)
281
- return Unauthorized("Invalid credentials.");
282
-
283
- var token = _jwtService.GenerateToken(user.Id, user.Email!);
284
-
285
- return Ok(new LoginResponse
286
- {{
287
- Token = token,
288
- Email = user.Email!,
289
- FullName = $"{{user.FirstName}} {{user.LastName}}"
290
- }});
291
- }}
292
-
293
- [Authorize]
294
- [HttpGet("me")]
295
- public async Task<IActionResult> Me()
296
- {{
297
- var user = await _userManager.FindByIdAsync(GetUserId().ToString());
298
- if (user == null)
299
- return NotFound("User not found.");
300
-
301
- var roles = await _userManager.GetRolesAsync(user);
302
-
303
- return Ok(new CurrentUserResponse
304
- {{
305
- Id = user.Id,
306
- Email = user.Email!,
307
- FullName = $"{{user.FirstName}} {{user.LastName}}",
308
- Roles = roles
309
- }});
310
- }}
311
- }}
312
- }}
313
- """
314
- (base / "API/Controllers/Main/AccountController.cs").write_text(content)
@@ -1,77 +1,20 @@
1
1
  from pathlib import Path
2
2
  from stackforge import ui
3
+ from stackforge.templating import render_file
3
4
 
5
+ ENGINE = "dotnet"
4
6
 
5
- def generate_env(base: Path, project: str):
6
- project_lower = project.lower()
7
-
8
- content = f"""ASPNETCORE_ENVIRONMENT=Development
9
- DB_HOST=db
10
- DB_PORT=5432
11
- DB_NAME={project_lower}_db
12
- DB_USER=postgres
13
- DB_PASSWORD=postgres
14
- JWT_ISSUER=https://localhost:5000
15
- JWT_AUDIENCE=https://localhost:5000
16
- JWT_SECRET_KEY=your-super-secret-key-change-this-in-production
17
- JWT_EXPIRY_MINUTES=60
18
- """
19
7
 
20
- file_path = base / ".env"
21
- file_path.write_text(content)
8
+ def generate_env(base: Path, project: str):
9
+ render_file(ENGINE, base, ".env", project)
22
10
  ui.file_created(".env")
23
11
 
24
12
 
25
13
  def generate_appsettings(base: Path, project: str):
26
- project_lower = project.lower()
27
-
28
- content = f"""{{
29
- "ConnectionStrings": {{
30
- "DefaultConnection": "Host=db;Port=5432;Database={project_lower}_db;Username=postgres;Password=postgres"
31
- }},
32
- "Jwt": {{
33
- "Issuer": "https://localhost:5000",
34
- "Audience": "https://localhost:5000",
35
- "SecretKey": "your-super-secret-key-change-this-in-production",
36
- "ExpiryMinutes": 60
37
- }},
38
- "Logging": {{
39
- "LogLevel": {{
40
- "Default": "Information",
41
- "Microsoft.AspNetCore": "Warning"
42
- }}
43
- }},
44
- "AllowedHosts": "*"
45
- }}
46
- """
47
-
48
- file_path = base / "API" / "appsettings.json"
49
- file_path.write_text(content)
14
+ render_file(ENGINE, base, "API/appsettings.json", project)
50
15
  ui.file_created("API/appsettings.json")
51
16
 
52
17
 
53
18
  def generate_appsettings_development(base: Path, project: str):
54
- project_lower = project.lower()
55
-
56
- content = f"""{{
57
- "ConnectionStrings": {{
58
- "DefaultConnection": "Host=localhost;Port=5432;Database={project_lower}_db;Username=postgres;Password=postgres"
59
- }},
60
- "Jwt": {{
61
- "Issuer": "https://localhost:5000",
62
- "Audience": "https://localhost:5000",
63
- "SecretKey": "your-super-secret-key-change-this-in-production",
64
- "ExpiryMinutes": 60
65
- }},
66
- "Logging": {{
67
- "LogLevel": {{
68
- "Default": "Information",
69
- "Microsoft.AspNetCore": "Warning"
70
- }}
71
- }}
72
- }}
73
- """
74
-
75
- file_path = base / "API" / "appsettings.Development.json"
76
- file_path.write_text(content)
77
- ui.file_created("API/appsettings.Development.json")
19
+ render_file(ENGINE, base, "API/appsettings.Development.json", project)
20
+ ui.file_created("API/appsettings.Development.json")
@@ -1,64 +1,15 @@
1
1
  from pathlib import Path
2
2
  from stackforge import ui
3
+ from stackforge.templating import render_file
3
4
 
5
+ ENGINE = "dotnet"
4
6
 
5
- def generate_dockerfile(base: Path):
6
- content = """FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
7
- WORKDIR /app
8
7
 
9
- COPY . .
10
-
11
- RUN dotnet publish API/API.csproj -c Release -o out
12
-
13
- ENTRYPOINT ["dotnet", "out/API.dll"]
14
- """
15
-
16
- file_path = base / "Dockerfile"
17
- file_path.write_text(content)
8
+ def generate_dockerfile(base: Path, project: str = ""):
9
+ render_file(ENGINE, base, "Dockerfile", project)
18
10
  ui.file_created("Dockerfile")
19
11
 
20
12
 
21
13
  def generate_docker_compose(base: Path, project: str):
22
- project_lower = project.lower()
23
-
24
- content = f"""services:
25
- api:
26
- build: .
27
- container_name: {project_lower}_api
28
- ports:
29
- - "5000:8080"
30
- environment:
31
- - ASPNETCORE_ENVIRONMENT=Development
32
- - ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database={project_lower}_db;Username=postgres;Password=postgres
33
- - Jwt__Issuer=http://localhost:5000
34
- - Jwt__Audience=http://localhost:5000
35
- - Jwt__SecretKey=your-super-secret-key-change-this-in-production
36
- - Jwt__ExpiryMinutes=60
37
- depends_on:
38
- db:
39
- condition: service_healthy
40
-
41
- db:
42
- image: postgres:latest
43
- container_name: {project_lower}_db
44
- environment:
45
- POSTGRES_DB: {project_lower}_db
46
- POSTGRES_USER: postgres
47
- POSTGRES_PASSWORD: postgres
48
- ports:
49
- - "5432:5432"
50
- volumes:
51
- - {project_lower}_pgdata:/var/lib/postgresql
52
- healthcheck:
53
- test: ["CMD-SHELL", "pg_isready -U postgres"]
54
- interval: 5s
55
- timeout: 5s
56
- retries: 5
57
-
58
- volumes:
59
- {project_lower}_pgdata:
60
- """
61
-
62
- file_path = base / "docker-compose.yml"
63
- file_path.write_text(content)
64
- ui.file_created("docker-compose.yml")
14
+ render_file(ENGINE, base, "docker-compose.yml", project)
15
+ ui.file_created("docker-compose.yml")
@@ -2,6 +2,9 @@ import uuid
2
2
  import subprocess
3
3
  from pathlib import Path
4
4
  from stackforge import ui
5
+ from stackforge.templating import render_file
6
+
7
+ ENGINE = "dotnet"
5
8
 
6
9
  def generate_sln(base: Path, project: str):
7
10
  layers = ["API", "Application", "Domain", "Infrastructure", "Persistence"]
@@ -37,65 +40,13 @@ EndGlobal
37
40
  ui.file_created(f"{project}.sln")
38
41
 
39
42
 
40
- def generate_gitignore(base: Path):
41
- content = """# Build results
42
- bin/
43
- obj/
44
- out/
45
-
46
- # Environment
47
- .env
48
-
49
- # VS / Rider
50
- .vs/
51
- .idea/
52
- *.user
53
-
54
- # Logs
55
- *.log
56
- """
57
-
58
- file_path = base / ".gitignore"
59
- file_path.write_text(content)
43
+ def generate_gitignore(base: Path, project: str = ""):
44
+ render_file(ENGINE, base, ".gitignore", project)
60
45
  ui.file_created(".gitignore")
61
46
 
62
47
 
63
- def generate_github_workflow(base: Path):
64
- workflow_dir = base / ".github" / "workflows"
65
- workflow_dir.mkdir(parents=True, exist_ok=True)
66
-
67
- content = """name: Sync Environment Branches
68
-
69
- on:
70
- push:
71
- branches:
72
- - main
73
-
74
- permissions:
75
- contents: write
76
-
77
- jobs:
78
- sync-branches:
79
- runs-on: ubuntu-latest
80
- steps:
81
- - uses: actions/checkout@v4
82
- with:
83
- fetch-depth: 0
84
-
85
- - name: Create staging branch if missing
86
- run: |
87
- if ! git ls-remote --exit-code origin refs/heads/staging; then
88
- git push origin HEAD:refs/heads/staging
89
- fi
90
-
91
- - name: Create development branch if missing
92
- run: |
93
- if ! git ls-remote --exit-code origin refs/heads/development; then
94
- git push origin HEAD:refs/heads/development
95
- fi
96
- """
97
- file_path = workflow_dir / "sync-branches.yml"
98
- file_path.write_text(content.lstrip())
48
+ def generate_github_workflow(base: Path, project: str = ""):
49
+ render_file(ENGINE, base, ".github/workflows/sync-branches.yml", project)
99
50
  ui.file_created(".github/workflows/sync-branches.yml")
100
51
 
101
52