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.
- stackforge/__init__.py +0 -0
- stackforge/engines/dotnet/__init__.py +8 -0
- stackforge/engines/dotnet/api_generator.py +312 -0
- stackforge/engines/dotnet/config_generator.py +76 -0
- stackforge/engines/dotnet/csproj_generator.py +82 -0
- stackforge/engines/dotnet/docker_generator.py +63 -0
- stackforge/engines/dotnet/git_generator.py +67 -0
- stackforge/engines/dotnet/layers_generator.py +543 -0
- stackforge/engines/dotnet/program_generator.py +194 -0
- stackforge/engines/dotnet/scaffolder.py +71 -0
- stackforge/scaffold.py +82 -0
- stackforge_cli-0.1.0.dist-info/METADATA +46 -0
- stackforge_cli-0.1.0.dist-info/RECORD +16 -0
- stackforge_cli-0.1.0.dist-info/WHEEL +5 -0
- stackforge_cli-0.1.0.dist-info/entry_points.txt +2 -0
- stackforge_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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 @@
|
|
|
1
|
+
stackforge
|