Compare commits
10 Commits
f329b00687
...
9d1442b46b
Author | SHA1 | Date | |
---|---|---|---|
9d1442b46b | |||
be6b3691b4 | |||
02a82589ae | |||
2d4bdbceeb | |||
9c3e2a25c0 | |||
3d96389f7f | |||
ff9be76bf8 | |||
1b6f0ee277 | |||
7f67201bb2 | |||
c220c214d2 |
@ -20,7 +20,7 @@ public class CrudRouterGenerator
|
||||
"",
|
||||
"@router.get(\"/\")",
|
||||
"async def list(",
|
||||
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||
"\tservice: Annotated[%service_class_name%, Depends()]",
|
||||
"):",
|
||||
"\ttry:",
|
||||
"\t\tresult = await service.list()",
|
||||
@ -32,7 +32,7 @@ public class CrudRouterGenerator
|
||||
"@router.get(\"/{id}\")",
|
||||
"async def get_by_id(",
|
||||
"\tid: int,",
|
||||
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||
"\tservice: Annotated[%service_class_name%, Depends()]",
|
||||
"):",
|
||||
"\ttry:",
|
||||
"\t\tresult = await service.get_by_id(id)",
|
||||
@ -44,7 +44,7 @@ public class CrudRouterGenerator
|
||||
"@router.post(\"/\")",
|
||||
"async def create(",
|
||||
"\trequest: Create%entity_class_name%Request,",
|
||||
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||
"\tservice: Annotated[%service_class_name%, Depends()]",
|
||||
"):",
|
||||
"\ttry:",
|
||||
"\t\tawait service.create(request.model_dump())",
|
||||
@ -57,7 +57,7 @@ public class CrudRouterGenerator
|
||||
"async def update(",
|
||||
"\tid: int,",
|
||||
"\trequest: Update%entity_class_name%Request,",
|
||||
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||
"\tservice: Annotated[%service_class_name%, Depends()]",
|
||||
"):",
|
||||
"\ttry:",
|
||||
"\t\tupdated = await service.update(id, request.model_dump(exclude_unset=True))",
|
||||
@ -69,7 +69,7 @@ public class CrudRouterGenerator
|
||||
"@router.delete(\"/{id}\")",
|
||||
"async def delete(",
|
||||
"\tid: int,",
|
||||
"\tservice: Annotated[%service_class_name%, Depends(%service_class_name%)]",
|
||||
"\tservice: Annotated[%service_class_name%, Depends()]",
|
||||
"):",
|
||||
"\ttry:",
|
||||
"\t\tdeleted = await service.delete(id)",
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Humanizer;
|
||||
using MycroForge.CLI.Commands;
|
||||
using MycroForge.Core;
|
||||
|
||||
namespace MycroForge.CLI.CodeGen;
|
||||
@ -143,16 +144,14 @@ public partial class EntityLinker
|
||||
|
||||
private async Task<EntityModel> LoadEntity(string name)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var path = $"{Features.Db.FeatureName}/entities";
|
||||
|
||||
if (name.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
|
||||
{
|
||||
path = Path.Combine(path, fullName[0]);
|
||||
name = fullName[1];
|
||||
}
|
||||
if (fqn.HasPath)
|
||||
path = Path.Combine(path, fqn.Path);
|
||||
|
||||
path = Path.Combine(path, $"{name.Underscore().ToLower()}.py");
|
||||
var entity = new EntityModel(name, path, await _context.ReadFile(path));
|
||||
path = Path.Combine(path, $"{fqn.SnakeCasedName}.py");
|
||||
var entity = new EntityModel(fqn.PascalizedName, path, await _context.ReadFile(path));
|
||||
entity.Initialize();
|
||||
return entity;
|
||||
}
|
||||
|
28
MycroForge.CLI/Commands/FullyQualifiedName.cs
Normal file
28
MycroForge.CLI/Commands/FullyQualifiedName.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Humanizer;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public class FullyQualifiedName
|
||||
{
|
||||
public string Path { get; }
|
||||
public string PascalizedName { get; }
|
||||
public string SnakeCasedName { get; }
|
||||
|
||||
public bool HasPath => Path.Length > 0;
|
||||
|
||||
|
||||
public FullyQualifiedName(string name)
|
||||
{
|
||||
var path = string.Empty;
|
||||
|
||||
if (name.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
|
||||
{
|
||||
path = fullName[0];
|
||||
name = fullName[1];
|
||||
}
|
||||
|
||||
Path = path;
|
||||
PascalizedName = name.Pascalize();
|
||||
SnakeCasedName = name.Underscore().ToLower();
|
||||
}
|
||||
}
|
39
MycroForge.CLI/Commands/MycroForge.Add.Api.cs
Normal file
39
MycroForge.CLI/Commands/MycroForge.Add.Api.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Api : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private static readonly Option<int> ApiPortOption = new(name: "--api-port", description: "The API port");
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Api(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base(Features.Api.FeatureName, "Add FastAPI to the project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddOption(ApiPortOption);
|
||||
this.SetHandler(ExecuteAsync, ApiPortOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(int apiPort)
|
||||
{
|
||||
_optionsContainer.Set(new Features.Api.Options { ApiPort = apiPort });
|
||||
var feature = _features.First(f => f.Name == Features.Api.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
MycroForge.CLI/Commands/MycroForge.Add.Db.cs
Normal file
48
MycroForge.CLI/Commands/MycroForge.Add.Db.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Db : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private static readonly Option<int> DbhPortOption = new(
|
||||
aliases: ["--database-host-port", "--dbh-port"],
|
||||
description: "The database host port"
|
||||
);
|
||||
|
||||
private static readonly Option<int> DbuPortOption = new(
|
||||
aliases: ["--database-ui-port", "--dbu-port"],
|
||||
description: "The database UI port"
|
||||
);
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Db(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base(Features.Db.FeatureName, "Add SQLAlchemy & Alembic to the project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddOption(DbhPortOption);
|
||||
AddOption(DbuPortOption);
|
||||
this.SetHandler(ExecuteAsync, DbhPortOption, DbuPortOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(int dbhPort, int dbuPort)
|
||||
{
|
||||
_optionsContainer.Set(new Features.Db.Options { DbhPort = dbhPort, DbuPort = dbuPort });
|
||||
var feature = _features.First(f => f.Name == Features.Db.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
MycroForge.CLI/Commands/MycroForge.Add.Git.cs
Normal file
32
MycroForge.CLI/Commands/MycroForge.Add.Git.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class Git : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public Git(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
base(Features.Git.FeatureName, "Add git to the project")
|
||||
{
|
||||
_context = context;
|
||||
_features = features.ToList();
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var feature = _features.First(f => f.Name == Features.Git.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
MycroForge.CLI/Commands/MycroForge.Add.GitIgnore.cs
Normal file
32
MycroForge.CLI/Commands/MycroForge.Add.GitIgnore.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.CLI.Features;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add
|
||||
{
|
||||
public class GitIgnore : Command, ISubCommandOf<Add>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
|
||||
public GitIgnore(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
base(Features.GitIgnore.FeatureName, "Add a default .gitignore file to the project")
|
||||
{
|
||||
_context = context;
|
||||
_features = features.ToList();
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var feature = _features.First(f => f.Name == Features.GitIgnore.FeatureName);
|
||||
await feature.ExecuteAsync(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
MycroForge.CLI/Commands/MycroForge.Add.cs
Normal file
17
MycroForge.CLI/Commands/MycroForge.Add.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Add : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
public Add(IEnumerable<ISubCommandOf<Add>> commands) :
|
||||
base("add", "Add features to the project")
|
||||
{
|
||||
foreach (var command in commands.Cast<Command>())
|
||||
AddCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,17 +28,12 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(string entity)
|
||||
{
|
||||
var path = string.Empty;
|
||||
if (entity.Split(':').Select(s => s.Trim()).ToArray() is { Length: 2 } fullName)
|
||||
{
|
||||
path = fullName[0];
|
||||
entity = fullName[1];
|
||||
}
|
||||
var fqn = new FullyQualifiedName(entity);
|
||||
|
||||
await new CrudServiceGenerator(_context).Generate(path, entity);
|
||||
await new RequestClassGenerator(_context).Generate(path, entity, RequestClassGenerator.Type.Create);
|
||||
await new RequestClassGenerator(_context).Generate(path, entity, RequestClassGenerator.Type.Update);
|
||||
await new CrudRouterGenerator(_context).Generate(path, entity);
|
||||
await new CrudServiceGenerator(_context).Generate(fqn.Path, fqn.PascalizedName);
|
||||
await new RequestClassGenerator(_context).Generate(fqn.Path, fqn.PascalizedName, RequestClassGenerator.Type.Create);
|
||||
await new RequestClassGenerator(_context).Generate(fqn.Path, fqn.PascalizedName, RequestClassGenerator.Type.Update);
|
||||
await new CrudRouterGenerator(_context).Generate(fqn.Path, fqn.PascalizedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.CommandLine;
|
||||
using Humanizer;
|
||||
using MycroForge.CLI.CodeGen;
|
||||
using MycroForge.Core.Contract;
|
||||
using MycroForge.CLI.Extensions;
|
||||
using MycroForge.Core;
|
||||
@ -42,30 +43,31 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(string name)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var folderPath = $"{Features.Api.FeatureName}/routers";
|
||||
|
||||
_context.AssertDirectoryExists(folderPath);
|
||||
|
||||
if (name.FullyQualifiedName() is { Length: 2 } fullName)
|
||||
{
|
||||
folderPath = Path.Combine(folderPath, fullName[0]);
|
||||
name = fullName[1];
|
||||
}
|
||||
|
||||
var moduleImportPath = folderPath.Replace('\\', '.').Replace('/', '.');
|
||||
var moduleName = name.Underscore().ToLower();
|
||||
var fileName = $"{moduleName}.py";
|
||||
if (fqn.HasPath)
|
||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
||||
|
||||
var fileName = $"{fqn.SnakeCasedName}.py";
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
|
||||
await _context.CreateFile(filePath, Template);
|
||||
|
||||
var moduleImportPath = folderPath
|
||||
.Replace('\\', '.')
|
||||
.Replace('/', '.');
|
||||
|
||||
var main = await _context.ReadFile("main.py");
|
||||
|
||||
main += string.Join('\n',
|
||||
$"\n\nfrom {moduleImportPath} import {moduleName}",
|
||||
$"app.include_router(prefix=\"/{name.Kebaberize()}\", router={moduleName}.router)"
|
||||
);
|
||||
|
||||
|
||||
main = new MainModifier(main)
|
||||
.Initialize()
|
||||
.Import(from: moduleImportPath, import: fqn.SnakeCasedName)
|
||||
.IncludeRouter(prefix: name.Kebaberize(), router: fqn.SnakeCasedName)
|
||||
.Rewrite();
|
||||
|
||||
await _context.WriteFile("main.py", main);
|
||||
}
|
||||
}
|
||||
|
@ -69,28 +69,25 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var folderPath = $"{Features.Db.FeatureName}/entities";
|
||||
|
||||
_context.AssertDirectoryExists(Features.Db.FeatureName);
|
||||
|
||||
if (name.FullyQualifiedName() is { Length: 2 } fullName)
|
||||
{
|
||||
folderPath = Path.Combine(folderPath, fullName[0]);
|
||||
name = fullName[1];
|
||||
}
|
||||
if (fqn.HasPath)
|
||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
||||
|
||||
var _columns = GetColumnDefinitions(columns.ToArray());
|
||||
var className = name.Underscore().Pascalize();
|
||||
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
|
||||
var columnDefinitions = string.Join("\n\t", _columns.Select(ColumnToString));
|
||||
|
||||
var code = string.Join('\n', Template);
|
||||
code = code.Replace("%type_imports%", typeImports);
|
||||
code = code.Replace("%class_name%", className);
|
||||
code = code.Replace("%table_name%", name.Underscore().ToLower().Pluralize());
|
||||
code = code.Replace("%class_name%", fqn.PascalizedName);
|
||||
code = code.Replace("%table_name%", fqn.SnakeCasedName.Pluralize());
|
||||
code = code.Replace("%column_definitions%", columnDefinitions);
|
||||
|
||||
var fileName = $"{name.Underscore().ToLower()}.py";
|
||||
var fileName = $"{fqn.SnakeCasedName}.py";
|
||||
var filePath = Path.Combine(folderPath, fileName);
|
||||
await _context.CreateFile(filePath, code);
|
||||
|
||||
@ -104,11 +101,11 @@ public partial class MycroForge
|
||||
.ToLower();
|
||||
|
||||
var env = await _context.ReadFile($"{Features.Db.FeatureName}/env.py");
|
||||
env = new DbEnvModifier(env, importPath, className).Rewrite();
|
||||
env = new DbEnvModifier(env, importPath, fqn.PascalizedName).Rewrite();
|
||||
await _context.WriteFile($"{Features.Db.FeatureName}/env.py", env);
|
||||
|
||||
var main = await _context.ReadFile("main.py");
|
||||
main = new MainModifier(main).Initialize().Import(importPath, className).Rewrite();
|
||||
main = new MainModifier(main).Initialize().Import(importPath, fqn.PascalizedName).Rewrite();
|
||||
await _context.WriteFile("main.py", main);
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,12 @@ public partial class MycroForge
|
||||
{
|
||||
public partial class Db
|
||||
{
|
||||
public partial class Run : Command, ISubCommandOf<Db>
|
||||
public class Run : Command, ISubCommandOf<Db>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Run(ProjectContext context) :
|
||||
base("run", $"Runs {Features.Db.FeatureName}.docker-compose.yml")
|
||||
base("run", $"Runs the services defined in {Features.Db.FeatureName}.docker-compose.yml")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
@ -22,7 +22,7 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
var env = $"DBH_PORT={config.Db.DbhPort} DBU_PORT={config.Db.DbuPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml up -d");
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ public partial class MycroForge
|
||||
{
|
||||
public partial class Db
|
||||
{
|
||||
public partial class Stop : Command, ISubCommandOf<Db>
|
||||
public class Stop : Command, ISubCommandOf<Db>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Stop(ProjectContext context) :
|
||||
base("stop", $"Stops {Features.Db.FeatureName}.docker-compose.yml")
|
||||
base("stop", $"Stops the services defined in {Features.Db.FeatureName}.docker-compose.yml")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
@ -22,7 +22,7 @@ public partial class MycroForge
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
var config = await _context.LoadConfig();
|
||||
var env = $"DB_PORT={config.Db.DbPort} PMA_PORT={config.Db.PmaPort}";
|
||||
var env = $"DB_PORT={config.Db.DbhPort} PMA_PORT={config.Db.DbuPort}";
|
||||
await _context.Bash($"{env} docker compose -f {Features.Db.FeatureName}.docker-compose.yml down");
|
||||
}
|
||||
}
|
||||
|
@ -57,18 +57,16 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(string name, bool withSession)
|
||||
{
|
||||
var fqn = new FullyQualifiedName(name);
|
||||
var folderPath = string.Empty;
|
||||
|
||||
if (name.FullyQualifiedName() is { Length: 2} fullName)
|
||||
{
|
||||
folderPath = Path.Combine(folderPath, fullName[0]);
|
||||
name = fullName[1];
|
||||
}
|
||||
if (fqn.HasPath)
|
||||
folderPath = Path.Combine(folderPath, fqn.Path);
|
||||
|
||||
var filePath = Path.Combine(folderPath, $"{name.Underscore().ToLower()}.py");
|
||||
var className = Path.GetFileName(name).Pascalize();
|
||||
var code = string.Join('\n', withSession ? WithSessionTemplate : DefaultTemplate)
|
||||
.Replace("%class_name%", className);
|
||||
var filePath = Path.Combine(folderPath, $"{fqn.SnakeCasedName}.py");
|
||||
var template = withSession ? WithSessionTemplate : DefaultTemplate;
|
||||
var code = string.Join('\n', template)
|
||||
.Replace("%class_name%", fqn.PascalizedName);
|
||||
|
||||
await _context.CreateFile(filePath, code);
|
||||
}
|
||||
|
21
MycroForge.CLI/Commands/MycroForge.Init.Binder.cs
Normal file
21
MycroForge.CLI/Commands/MycroForge.Init.Binder.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.CommandLine.Binding;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Init
|
||||
{
|
||||
public class Binder : BinderBase<Options>
|
||||
{
|
||||
protected override Options GetBoundValue(BindingContext ctx) => new()
|
||||
{
|
||||
Name = ctx.ParseResult.GetValueForArgument(NameArgument),
|
||||
Without = ctx.ParseResult.GetValueForOption(WithoutOption),
|
||||
ApiPort = ctx.ParseResult.GetValueForOption(ApiPortOption),
|
||||
DbhPort = ctx.ParseResult.GetValueForOption(DbhPortOption),
|
||||
DbuPort = ctx.ParseResult.GetValueForOption(DbuPortOption),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
27
MycroForge.CLI/Commands/MycroForge.Init.Options.cs
Normal file
27
MycroForge.CLI/Commands/MycroForge.Init.Options.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Init
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public IEnumerable<string>? Without { get; set; }
|
||||
public int? ApiPort { get; set; }
|
||||
public int? DbhPort { get; set; }
|
||||
public int? DbuPort { get; set; }
|
||||
|
||||
public Features.Api.Options ApiOptions => new()
|
||||
{
|
||||
ApiPort = ApiPort <= 0 ? 8000 : ApiPort
|
||||
};
|
||||
|
||||
public Features.Db.Options DbOptions => new()
|
||||
{
|
||||
DbhPort = DbhPort <= 0 ? 5050 : DbhPort,
|
||||
DbuPort = DbuPort <= 0 ? 5051 : DbhPort
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,58 +7,12 @@ namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
#region GitIgnore
|
||||
|
||||
private static readonly string[] GitIgnore =
|
||||
[
|
||||
"# Byte-compiled / optimized / DLL files", "__pycache__/", "*.py[cod]", "*$py.class", "# C extensions",
|
||||
"*.so", "# Distribution / packaging", ".Python", "build/", "develop-eggs/", "dist/", "downloads/", "eggs/",
|
||||
".eggs/", "lib/", "lib64/", "parts/", "sdist/", "var/", "wheels/", "share/python-wheels/", "*.egg-info/",
|
||||
".installed.cfg", "*.egg", "MANIFEST", "# PyInstaller",
|
||||
"# Usually these files are written by a python script from a template",
|
||||
"# before PyInstaller builds the exe, so as to inject date/other infos into it.", "*.manifest", "*.spec",
|
||||
"# Installer logs", "pip-log.txt", "pip-delete-this-directory.txt", "# Unit test / coverage reports",
|
||||
"htmlcov/", ".tox/", ".nox/", ".coverage", ".coverage.*", ".cache", "nosetests.xml", "coverage.xml",
|
||||
"*.cover", "*.py,cover", ".hypothesis/", ".pytest_cache/", "cover/", "# Translations", "*.mo", "*.pot",
|
||||
"# Django stuff:", "*.log", "local_settings.py", "db.sqlite3", "db.sqlite3-journal", "# Flask stuff:",
|
||||
"instance/", ".webassets-cache", "# Scrapy stuff:", ".scrapy", "# Sphinx documentation", "docs/_build/",
|
||||
"# PyBuilder", ".pybuilder/", "target/", "# Jupyter Notebook", ".ipynb_checkpoints", "# IPython",
|
||||
"profile_default/", "ipython_config.py", "# pyenv",
|
||||
"# For a library or package, you might want to ignore these files since the code is",
|
||||
"# intended to run in multiple environments; otherwise, check them in:", "# .python-version", "# pipenv",
|
||||
"# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.",
|
||||
"# However, in case of collaboration, if having platform-specific dependencies or dependencies",
|
||||
"# having no cross-platform support, pipenv may install dependencies that don't work, or not",
|
||||
"# install all needed dependencies.", "#Pipfile.lock", "# poetry",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.",
|
||||
"# This is especially recommended for binary packages to ensure reproducibility, and is more",
|
||||
"# commonly ignored for libraries.",
|
||||
"# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control",
|
||||
"#poetry.lock", "# pdm",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.",
|
||||
"#pdm.lock",
|
||||
"# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it",
|
||||
"# in version control.", "# https://pdm.fming.dev/#use-with-ide", ".pdm.toml",
|
||||
"# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm", "__pypackages__/",
|
||||
"# Celery stuff", "celerybeat-schedule", "celerybeat.pid", "# SageMath parsed files", "*.sage.py",
|
||||
"# Environments", ".env", ".venv", "env/", "venv/", "ENV/", "env.bak/", "venv.bak/",
|
||||
"# Spyder project settings", ".spyderproject", ".spyproject", "# Rope project settings", ".ropeproject",
|
||||
"# mkdocs documentation", "/site", "# mypy", ".mypy_cache/", ".dmypy.json", "dmypy.json",
|
||||
"# Pyre type checker", ".pyre/", "# pytype static type analyzer", ".pytype/", "# Cython debug symbols",
|
||||
"cython_debug/", "# PyCharm",
|
||||
"# JetBrains specific template is maintained in a separate JetBrains.gitignore that can",
|
||||
"# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore",
|
||||
"# and can be added to the global gitignore or merged into this file. For a more nuclear",
|
||||
"# option (not recommended) you can uncomment the following to ignore the entire idea folder.", "#.idea/"
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
public class Init : Command, ISubCommandOf<MycroForge>
|
||||
public partial class Init : Command, ISubCommandOf<MycroForge>
|
||||
{
|
||||
private static readonly string[] DefaultFeatures =
|
||||
[
|
||||
Features.Git.FeatureName,
|
||||
Features.GitIgnore.FeatureName,
|
||||
Features.Api.FeatureName,
|
||||
Features.Db.FeatureName
|
||||
];
|
||||
@ -72,44 +26,61 @@ public partial class MycroForge
|
||||
AllowMultipleArgumentsPerToken = true
|
||||
}.FromAmong(DefaultFeatures);
|
||||
|
||||
private static readonly Option<int> ApiPortOption =
|
||||
new(name: "--api-port", description: "The API port");
|
||||
|
||||
private static readonly Option<int> DbhPortOption = new(
|
||||
aliases: ["--database-host-port", "--dbh-port"],
|
||||
description: "The database host port"
|
||||
);
|
||||
|
||||
private static readonly Option<int> DbuPortOption = new(
|
||||
aliases: ["--database-ui-port", "--dbu-port"],
|
||||
description: "The database UI port"
|
||||
);
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
private readonly List<IFeature> _features;
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
|
||||
public Init(ProjectContext context, IEnumerable<IFeature> features) :
|
||||
public Init(ProjectContext context, OptionsContainer optionsContainer, IEnumerable<IFeature> features) :
|
||||
base("init", "Initialize a new project")
|
||||
{
|
||||
_context = context;
|
||||
_optionsContainer = optionsContainer;
|
||||
_features = features.ToList();
|
||||
|
||||
AddArgument(NameArgument);
|
||||
AddOption(WithoutOption);
|
||||
this.SetHandler(ExecuteAsync, NameArgument, WithoutOption);
|
||||
AddOption(ApiPortOption);
|
||||
AddOption(DbhPortOption);
|
||||
AddOption(DbuPortOption);
|
||||
|
||||
this.SetHandler(ExecuteAsync, new Binder());
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(string name, IEnumerable<string> without)
|
||||
private async Task ExecuteAsync(Options options)
|
||||
{
|
||||
// Validate excluded features
|
||||
var withoutList = without.ToList();
|
||||
var withoutList = (options.Without ?? Enumerable.Empty<string>()).ToList();
|
||||
foreach (var feature in withoutList)
|
||||
if (_features.All(f => f.Name != feature))
|
||||
throw new Exception($"Feature {feature} does not exist.");
|
||||
|
||||
// Create the project directory and change the directory for the ProjectContext
|
||||
var projectRoot = await CreateDirectory(name);
|
||||
var projectRoot = await CreateDirectory(options.Name);
|
||||
_context.ChangeRootDirectory(projectRoot);
|
||||
|
||||
// Create the config file and initialize the config
|
||||
await _context.CreateFile("m4g.json", "{}");
|
||||
|
||||
// Create the entrypoint file
|
||||
await _context.CreateFile("main.py");
|
||||
|
||||
// Create the default .gitignore folder
|
||||
await _context.CreateFile(".gitignore", GitIgnore);
|
||||
|
||||
// Create the venv
|
||||
await _context.Bash($"python3 -m venv {Path.Combine(projectRoot, ".venv")}");
|
||||
|
||||
// Pass feature arguments to the ArgsContainer
|
||||
_optionsContainer.Set(options.ApiOptions);
|
||||
_optionsContainer.Set(options.DbOptions);
|
||||
|
||||
// Initialize default features
|
||||
foreach (var feature in _features.Where(f => DefaultFeatures.Contains(f.Name)))
|
||||
{
|
||||
|
@ -24,9 +24,17 @@ public partial class MycroForge
|
||||
|
||||
private async Task ExecuteAsync(IEnumerable<string> packages)
|
||||
{
|
||||
var packs = packages.ToArray();
|
||||
|
||||
if (packs.Length == 0)
|
||||
{
|
||||
Console.WriteLine("m4g install requires at least one package.");
|
||||
return;
|
||||
}
|
||||
|
||||
await _context.Bash(
|
||||
"source .venv/bin/activate",
|
||||
$"pip install {string.Join(' ', packages)}",
|
||||
$"pip install {string.Join(' ', packs)}",
|
||||
"pip freeze > requirements.txt"
|
||||
);
|
||||
}
|
||||
|
31
MycroForge.CLI/Commands/MycroForge.Plugin.Init.cs
Normal file
31
MycroForge.CLI/Commands/MycroForge.Plugin.Init.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public partial class MycroForge
|
||||
{
|
||||
public partial class Plugin
|
||||
{
|
||||
public class Init : Command, ISubCommandOf<Plugin>
|
||||
{
|
||||
private static readonly Argument<string> NameArgument =
|
||||
new(name: "name", description: "The name of your project");
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public Init(ProjectContext context) : base("init", "Initialize a basic plugin project")
|
||||
{
|
||||
_context = context;
|
||||
AddArgument(NameArgument);
|
||||
this.SetHandler(ExecuteAsync, NameArgument);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(string name)
|
||||
{
|
||||
await _context.Bash($"dotnet new m4gp -n {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ public partial class MycroForge
|
||||
private void ExecuteAsync()
|
||||
{
|
||||
foreach (var plugin in Plugins.Loaded)
|
||||
Console.WriteLine($"name: {plugin.Name}, command: {plugin.Command}");
|
||||
Console.WriteLine($"{plugin.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core.Contract;
|
||||
using Core_RootCommand = MycroForge.Core.RootCommand;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public sealed partial class MycroForge : Core_RootCommand
|
||||
public sealed partial class MycroForge : RootCommand
|
||||
{
|
||||
public override string Name => "m4g";
|
||||
|
||||
|
25
MycroForge.CLI/Commands/OptionsContainer.cs
Normal file
25
MycroForge.CLI/Commands/OptionsContainer.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace MycroForge.CLI.Commands;
|
||||
|
||||
public class OptionsContainer
|
||||
{
|
||||
private readonly Dictionary<Type, object> _args = new();
|
||||
|
||||
public void Set<T>(T? args)
|
||||
{
|
||||
if (args is null)
|
||||
throw new ArgumentNullException();
|
||||
|
||||
_args[args.GetType()] = args;
|
||||
}
|
||||
|
||||
public T Get<T>()
|
||||
{
|
||||
if (!_args.ContainsKey(typeof(T)))
|
||||
throw new KeyNotFoundException();
|
||||
|
||||
if (_args[typeof(T)] is not T args)
|
||||
throw new InvalidCastException();
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
@ -10,11 +10,13 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection RegisterCommandDefaults(this IServiceCollection services)
|
||||
{
|
||||
// Register ProjectContext & features
|
||||
// Register ProjectContext, OptionsContainer & features
|
||||
services.AddScoped<ProjectContext>();
|
||||
services.AddScoped<IFeature, Git>();
|
||||
services.AddScoped<OptionsContainer>();
|
||||
services.AddScoped<IFeature, Api>();
|
||||
services.AddScoped<IFeature, Db>();
|
||||
services.AddScoped<IFeature, Git>();
|
||||
services.AddScoped<IFeature, GitIgnore>();
|
||||
|
||||
// Register "m4g"
|
||||
services.AddScoped<Commands.MycroForge>();
|
||||
@ -22,6 +24,13 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Install>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Uninstall>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Hydrate>();
|
||||
|
||||
// Register "m4g add"
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Add>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Add>, Commands.MycroForge.Add.Api>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Add>, Commands.MycroForge.Add.Db>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Add>, Commands.MycroForge.Add.Git>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Add>, Commands.MycroForge.Add.GitIgnore>();
|
||||
|
||||
// Register "m4g generate"
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Generate>();
|
||||
@ -50,6 +59,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// Register "m4g plugin"
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge>, Commands.MycroForge.Plugin>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Plugin>, Commands.MycroForge.Plugin.Init>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Plugin>, Commands.MycroForge.Plugin.List>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Plugin>, Commands.MycroForge.Plugin.Install>();
|
||||
services.AddScoped<ISubCommandOf<Commands.MycroForge.Plugin>, Commands.MycroForge.Plugin.Uninstall>();
|
||||
|
@ -2,11 +2,6 @@
|
||||
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string[] FullyQualifiedName(this string name)
|
||||
{
|
||||
return name.Split(':').Select(s => s.Trim()).ToArray();
|
||||
}
|
||||
|
||||
public static string DeduplicateDots(this string path)
|
||||
{
|
||||
while (path.Contains(".."))
|
||||
|
9
MycroForge.CLI/Features/Api.Options.cs
Normal file
9
MycroForge.CLI/Features/Api.Options.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace MycroForge.CLI.Features;
|
||||
|
||||
public sealed partial class Api
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
public int? ApiPort { get; set; }
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using MycroForge.Core;
|
||||
using MycroForge.CLI.Commands;
|
||||
using MycroForge.Core;
|
||||
|
||||
namespace MycroForge.CLI.Features;
|
||||
|
||||
public sealed class Api : IFeature
|
||||
public sealed partial class Api : IFeature
|
||||
{
|
||||
#region Main
|
||||
#region Templates
|
||||
|
||||
private static readonly string[] RouterTemplate =
|
||||
[
|
||||
@ -24,7 +25,6 @@ public sealed class Api : IFeature
|
||||
"from fastapi import FastAPI",
|
||||
$"from {FeatureName}.routers import hello",
|
||||
"",
|
||||
"",
|
||||
"app = FastAPI()",
|
||||
"",
|
||||
"app.include_router(prefix=\"/hello\", router=hello.router)"
|
||||
@ -36,15 +36,23 @@ public sealed class Api : IFeature
|
||||
|
||||
public string Name => FeatureName;
|
||||
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
|
||||
public Api(OptionsContainer optionsContainer)
|
||||
{
|
||||
_optionsContainer = optionsContainer;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(ProjectContext context)
|
||||
{
|
||||
var config = await context.LoadConfig();
|
||||
config.Api = new() { Port = 8000 };
|
||||
var options = _optionsContainer.Get<Options>();
|
||||
var config = await context.LoadConfig(create: true);
|
||||
config.Api = new() { Port = options.ApiPort ?? 8000 };
|
||||
await context.SaveConfig(config);
|
||||
|
||||
await context.Bash(
|
||||
"source .venv/bin/activate",
|
||||
"python3 -m pip install fastapi uvicorn[standard]",
|
||||
"python3 -m pip install fastapi",
|
||||
"python3 -m pip freeze > requirements.txt"
|
||||
);
|
||||
|
||||
|
10
MycroForge.CLI/Features/Db.Options.cs
Normal file
10
MycroForge.CLI/Features/Db.Options.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace MycroForge.CLI.Features;
|
||||
|
||||
public sealed partial class Db
|
||||
{
|
||||
public class Options
|
||||
{
|
||||
public int? DbhPort { get; set; }
|
||||
public int? DbuPort { get; set; }
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
using MycroForge.CLI.CodeGen;
|
||||
using MycroForge.CLI.Commands;
|
||||
using MycroForge.Core;
|
||||
|
||||
namespace MycroForge.CLI.Features;
|
||||
|
||||
public sealed class Db : IFeature
|
||||
public sealed partial class Db : IFeature
|
||||
{
|
||||
#region Defaults
|
||||
|
||||
@ -37,7 +38,7 @@ public sealed class Db : IFeature
|
||||
private static readonly string[] DockerCompose =
|
||||
[
|
||||
"version: '3.8'",
|
||||
"# Access the database UI at http://localhost:${DB_PORT}.",
|
||||
"# Access the database UI at http://localhost:${DBU_PORT}.",
|
||||
"# Login: username = root & password = password",
|
||||
"",
|
||||
"services:",
|
||||
@ -47,7 +48,7 @@ public sealed class Db : IFeature
|
||||
" networks:",
|
||||
" - default",
|
||||
" ports:",
|
||||
" - '${DB_PORT}:3306'",
|
||||
" - '${DBH_PORT}:3306'",
|
||||
" environment:",
|
||||
" MYSQL_ROOT_PASSWORD: 'password'",
|
||||
" MYSQL_USER: 'root'",
|
||||
@ -60,7 +61,7 @@ public sealed class Db : IFeature
|
||||
" image: phpmyadmin/phpmyadmin",
|
||||
" container_name: %app_name%_phpmyadmin",
|
||||
" ports:",
|
||||
" - '${PMA_PORT}:80'",
|
||||
" - '${DBU_PORT}:80'",
|
||||
" networks:",
|
||||
" - default",
|
||||
" environment:",
|
||||
@ -78,12 +79,24 @@ public sealed class Db : IFeature
|
||||
|
||||
public const string FeatureName = "db";
|
||||
|
||||
private readonly OptionsContainer _optionsContainer;
|
||||
|
||||
public string Name => FeatureName;
|
||||
|
||||
public Db(OptionsContainer optionsContainer)
|
||||
{
|
||||
_optionsContainer = optionsContainer;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(ProjectContext context)
|
||||
{
|
||||
var config = await context.LoadConfig();
|
||||
config.Db = new() { DbPort = 5050, PmaPort = 5051 };
|
||||
var options = _optionsContainer.Get<Options>();
|
||||
var config = await context.LoadConfig(create: true);
|
||||
config.Db = new()
|
||||
{
|
||||
DbhPort = options.DbhPort ?? 5050,
|
||||
DbuPort = options.DbuPort ?? 5051
|
||||
};
|
||||
await context.SaveConfig(config);
|
||||
|
||||
var appName = context.AppName;
|
||||
@ -101,7 +114,7 @@ public sealed class Db : IFeature
|
||||
|
||||
var settings = string.Join('\n', Settings)
|
||||
.Replace("%app_name%", appName)
|
||||
.Replace("%db_port%", config.Db.DbPort.ToString())
|
||||
.Replace("%db_port%", config.Db.DbhPort.ToString())
|
||||
;
|
||||
|
||||
await context.CreateFile($"{FeatureName}/settings.py", settings);
|
||||
|
61
MycroForge.CLI/Features/GitIgnore.cs
Normal file
61
MycroForge.CLI/Features/GitIgnore.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using MycroForge.Core;
|
||||
|
||||
namespace MycroForge.CLI.Features;
|
||||
|
||||
public class GitIgnore : IFeature
|
||||
{
|
||||
#region GitIgnore
|
||||
|
||||
private static readonly string[] GitIgnoreTemplate =
|
||||
[
|
||||
"# Byte-compiled / optimized / DLL files", "__pycache__/", "*.py[cod]", "*$py.class", "# C extensions",
|
||||
"*.so", "# Distribution / packaging", ".Python", "build/", "develop-eggs/", "dist/", "downloads/", "eggs/",
|
||||
".eggs/", "lib/", "lib64/", "parts/", "sdist/", "var/", "wheels/", "share/python-wheels/", "*.egg-info/",
|
||||
".installed.cfg", "*.egg", "MANIFEST", "# PyInstaller",
|
||||
"# Usually these files are written by a python script from a template",
|
||||
"# before PyInstaller builds the exe, so as to inject date/other infos into it.", "*.manifest", "*.spec",
|
||||
"# Installer logs", "pip-log.txt", "pip-delete-this-directory.txt", "# Unit test / coverage reports",
|
||||
"htmlcov/", ".tox/", ".nox/", ".coverage", ".coverage.*", ".cache", "nosetests.xml", "coverage.xml",
|
||||
"*.cover", "*.py,cover", ".hypothesis/", ".pytest_cache/", "cover/", "# Translations", "*.mo", "*.pot",
|
||||
"# Django stuff:", "*.log", "local_settings.py", "db.sqlite3", "db.sqlite3-journal", "# Flask stuff:",
|
||||
"instance/", ".webassets-cache", "# Scrapy stuff:", ".scrapy", "# Sphinx documentation", "docs/_build/",
|
||||
"# PyBuilder", ".pybuilder/", "target/", "# Jupyter Notebook", ".ipynb_checkpoints", "# IPython",
|
||||
"profile_default/", "ipython_config.py", "# pyenv",
|
||||
"# For a library or package, you might want to ignore these files since the code is",
|
||||
"# intended to run in multiple environments; otherwise, check them in:", "# .python-version", "# pipenv",
|
||||
"# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.",
|
||||
"# However, in case of collaboration, if having platform-specific dependencies or dependencies",
|
||||
"# having no cross-platform support, pipenv may install dependencies that don't work, or not",
|
||||
"# install all needed dependencies.", "#Pipfile.lock", "# poetry",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.",
|
||||
"# This is especially recommended for binary packages to ensure reproducibility, and is more",
|
||||
"# commonly ignored for libraries.",
|
||||
"# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control",
|
||||
"#poetry.lock", "# pdm",
|
||||
"# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.",
|
||||
"#pdm.lock",
|
||||
"# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it",
|
||||
"# in version control.", "# https://pdm.fming.dev/#use-with-ide", ".pdm.toml",
|
||||
"# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm", "__pypackages__/",
|
||||
"# Celery stuff", "celerybeat-schedule", "celerybeat.pid", "# SageMath parsed files", "*.sage.py",
|
||||
"# Environments", ".env", ".venv", "env/", "venv/", "ENV/", "env.bak/", "venv.bak/",
|
||||
"# Spyder project settings", ".spyderproject", ".spyproject", "# Rope project settings", ".ropeproject",
|
||||
"# mkdocs documentation", "/site", "# mypy", ".mypy_cache/", ".dmypy.json", "dmypy.json",
|
||||
"# Pyre type checker", ".pyre/", "# pytype static type analyzer", ".pytype/", "# Cython debug symbols",
|
||||
"cython_debug/", "# PyCharm",
|
||||
"# JetBrains specific template is maintained in a separate JetBrains.gitignore that can",
|
||||
"# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore",
|
||||
"# and can be added to the global gitignore or merged into this file. For a more nuclear",
|
||||
"# option (not recommended) you can uncomment the following to ignore the entire idea folder.", "#.idea/"
|
||||
];
|
||||
|
||||
#endregion
|
||||
|
||||
public const string FeatureName = "gitignore";
|
||||
public string Name => FeatureName;
|
||||
|
||||
public async Task ExecuteAsync(ProjectContext context)
|
||||
{
|
||||
await context.CreateFile(".gitignore", GitIgnoreTemplate);
|
||||
}
|
||||
}
|
5
MycroForge.CLI/scripts/publish-nuget.sh
Normal file
5
MycroForge.CLI/scripts/publish-nuget.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
dotnet pack -v d
|
||||
|
||||
dotnet nuget push nupkg/MycroForge.CLI.1.0.0.nupkg --source devdisciples
|
@ -5,7 +5,6 @@ namespace MycroForge.Core.Contract;
|
||||
public interface ICommandPlugin
|
||||
{
|
||||
public string? Name { get; }
|
||||
public string Command { get; }
|
||||
|
||||
public void RegisterServices(IServiceCollection services);
|
||||
}
|
@ -4,6 +4,12 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageId>MycroForge.Core</PackageId>
|
||||
<Description>The MycroForge core package</Description>
|
||||
<Version>1.0.0</Version>
|
||||
<Authors>Donné Napo</Authors>
|
||||
<Company>Dev Disciples</Company>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -4,7 +4,7 @@ public partial class ProjectConfig
|
||||
{
|
||||
public class DbConfig
|
||||
{
|
||||
public int DbPort { get; set; }
|
||||
public int PmaPort { get; set; }
|
||||
public int DbhPort { get; set; }
|
||||
public int DbuPort { get; set; }
|
||||
}
|
||||
}
|
@ -12,10 +12,13 @@ public class ProjectContext
|
||||
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
|
||||
|
||||
|
||||
public async Task<ProjectConfig> LoadConfig()
|
||||
public async Task<ProjectConfig> LoadConfig(bool create = false)
|
||||
{
|
||||
if (!File.Exists(ConfigPath))
|
||||
throw new FileNotFoundException($"File {ConfigPath} does not exist.");
|
||||
{
|
||||
if (create) await CreateFile("m4g.json", "{}");
|
||||
else throw new FileNotFoundException($"File {ConfigPath} does not exist.");
|
||||
}
|
||||
|
||||
var config = await JsonSerializer.DeserializeAsync<ProjectConfig>(
|
||||
File.OpenRead(ConfigPath),
|
||||
|
6
MycroForge.Core/README.md
Normal file
6
MycroForge.Core/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
### Publish to local nuget repo
|
||||
|
||||
```shell
|
||||
dotnet build -r Release
|
||||
dotnet nuget push --source devdisciples bin/Release/MycroForge.Core.1.0.0.nupkg
|
||||
```
|
5
MycroForge.PluginTemplate.Package/.gitignore
vendored
Normal file
5
MycroForge.PluginTemplate.Package/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bin/
|
||||
obj/
|
||||
templates/
|
||||
!templates/.gitkeep
|
||||
MycroForge.Package.PluginTemplate.sln
|
@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- The package metadata. Fill in the properties marked as TODO below -->
|
||||
<!-- Follow the instructions on https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices -->
|
||||
<PackageId>MycroForge.PluginTemplate.Package</PackageId>
|
||||
<PackageVersion>1.0</PackageVersion>
|
||||
<Title>A template for generating MycroForge plugins</Title>
|
||||
<Authors>Donné Napo</Authors>
|
||||
<Description>Template to use when creating a plugin for the MycroForge CLI.</Description>
|
||||
<PackageTags>dotnet-new;templates;</PackageTags>
|
||||
<PackageProjectUrl>https://git.devdisciples.com/devdisciples/mycroforge</PackageProjectUrl>
|
||||
|
||||
<PackageType>Template</PackageType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IncludeContentInPack>true</IncludeContentInPack>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<ContentTargetFolders>content</ContentTargetFolders>
|
||||
<NoWarn>$(NoWarn);NU5128</NoWarn>
|
||||
<NoDefaultExcludes>true</NoDefaultExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**"/>
|
||||
<Compile Remove="**\*"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
15
MycroForge.PluginTemplate.Package/README.md
Normal file
15
MycroForge.PluginTemplate.Package/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
### Used resources
|
||||
|
||||
https://www.youtube.com/watch?v=XzD-95qfWJM
|
||||
https://www.youtube.com/watch?v=rdWZo5PD9Ek
|
||||
https://learn.microsoft.com/en-us/dotnet/core/tutorials/cli-templates-create-template-package?pivots=dotnet-8-0
|
||||
|
||||
|
||||
### Build the package
|
||||
`dotnet pack`
|
||||
|
||||
### Push to local nuget
|
||||
`dotnet nuget push bin/Release/MycroForge.PluginTemplate.Package.1.0.0.nupkg --source devdisciples`
|
||||
|
||||
### Install template package from local nuget
|
||||
`dotnet new install MycroForge.PluginTemplate.Package --nuget-source devdisciples`
|
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
rm -rf templates/MycroForge.PluginTemplate
|
||||
cp -R ../MycroForge.PluginTemplate templates/MycroForge.PluginTemplate
|
||||
dotnet pack
|
21
MycroForge.PluginTemplate/.template.config/template.json
Normal file
21
MycroForge.PluginTemplate/.template.config/template.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/template",
|
||||
"author": "Donné Napo",
|
||||
"defaultName": "My.Plugin",
|
||||
"name": "MycroForge plugin template",
|
||||
"description": "Creates a basic MycroForge plugin project",
|
||||
"projectURL": "https://github.com/mdnapo/mycroforge",
|
||||
"repository": {
|
||||
"url": "https://github.com/",
|
||||
"type": "GitHub"
|
||||
},
|
||||
"classifications": ["Console","Plugin"],
|
||||
"identity": "MycroForge.PluginTemplate",
|
||||
"shortName": "m4gp",
|
||||
"sourceName": "MycroForge.PluginTemplate",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"preferNameDirectory": true
|
||||
}
|
36
MycroForge.PluginTemplate/HelloWorldCommand.cs
Normal file
36
MycroForge.PluginTemplate/HelloWorldCommand.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace MycroForge.PluginTemplate;
|
||||
|
||||
public class HelloWorldCommand : Command, ISubCommandOf<RootCommand>
|
||||
{
|
||||
private readonly Argument<string> NameArgument =
|
||||
new(name: "name", description: "The name of the person to greet");
|
||||
|
||||
private readonly Option<bool> AllCapsOption =
|
||||
new(aliases: ["-a", "--all-caps"], description: "Print the name in all caps");
|
||||
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public HelloWorldCommand(ProjectContext context) :
|
||||
base("hello", "An example command generated by dotnet new using the m4gp template")
|
||||
{
|
||||
_context = context;
|
||||
AddArgument(NameArgument);
|
||||
AddOption(AllCapsOption);
|
||||
this.SetHandler(ExecuteAsync, NameArgument, AllCapsOption);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync(string name, bool allCaps)
|
||||
{
|
||||
name = allCaps ? name.ToUpper() : name;
|
||||
|
||||
await _context.CreateFile("hello_world.txt",
|
||||
$"Hello {name}!",
|
||||
"This file was generated by your custom command!"
|
||||
);
|
||||
}
|
||||
}
|
15
MycroForge.PluginTemplate/HelloWorldCommandPlugin.cs
Normal file
15
MycroForge.PluginTemplate/HelloWorldCommandPlugin.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace MycroForge.PluginTemplate;
|
||||
|
||||
public class HelloWorldCommandPlugin : ICommandPlugin
|
||||
{
|
||||
public string Name => "MycroForge.PluginTemplate";
|
||||
|
||||
public void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ISubCommandOf<RootCommand>, HelloWorldCommand>();
|
||||
}
|
||||
}
|
@ -7,7 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MycroForge.Core\MycroForge.Core.csproj" />
|
||||
<Content Include=".template.config\template.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MycroForge.Core" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,6 +0,0 @@
|
||||
namespace MycroForge.TestPlugin;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const string MainCommandName = "mj";
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using System.CommandLine;
|
||||
using MycroForge.Core;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace MycroForge.TestPlugin;
|
||||
|
||||
public class MyJewelleryCommand : Command, ISubCommandOf<RootCommand>
|
||||
{
|
||||
private readonly ProjectContext _context;
|
||||
|
||||
public MyJewelleryCommand(ProjectContext context) :
|
||||
base(Constants.MainCommandName, "Custom command for My Jewellery specific stuff")
|
||||
{
|
||||
_context = context;
|
||||
this.SetHandler(ExecuteAsync);
|
||||
}
|
||||
|
||||
private async Task ExecuteAsync()
|
||||
{
|
||||
await _context.CreateFile("hello_world.txt",
|
||||
"My Jewellery command plugin is working!"
|
||||
);
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MycroForge.Core.Contract;
|
||||
using RootCommand = MycroForge.Core.RootCommand;
|
||||
|
||||
namespace MycroForge.TestPlugin;
|
||||
|
||||
public class MyJewelleryCommandPlugin : ICommandPlugin
|
||||
{
|
||||
public string Name => $"{nameof(MycroForge)}.{nameof(TestPlugin)}";
|
||||
public string Command => Constants.MainCommandName;
|
||||
|
||||
public void RegisterServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ISubCommandOf<RootCommand>, MyJewelleryCommand>();
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.CLI", "MycroForg
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.Core", "MycroForge.Core\MycroForge.Core.csproj", "{CFF8BD4E-520D-4319-BA80-3F49B5F493BA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.TestPlugin", "MycroForge.TestPlugin\MycroForge.TestPlugin.csproj", "{7C479E68-98FA-4FBC-B5E4-7116015774B3}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate", "MycroForge.PluginTemplate\MycroForge.PluginTemplate.csproj", "{114A2B34-D77E-42AE-ADAF-0CD68C7B8D32}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate.Package", "MycroForge.PluginTemplate.Package\MycroForge.PluginTemplate.Package.csproj", "{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -20,9 +22,13 @@ Global
|
||||
{CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CFF8BD4E-520D-4319-BA80-3F49B5F493BA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7C479E68-98FA-4FBC-B5E4-7116015774B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7C479E68-98FA-4FBC-B5E4-7116015774B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C479E68-98FA-4FBC-B5E4-7116015774B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C479E68-98FA-4FBC-B5E4-7116015774B3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{114A2B34-D77E-42AE-ADAF-0CD68C7B8D32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{114A2B34-D77E-42AE-ADAF-0CD68C7B8D32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{114A2B34-D77E-42AE-ADAF-0CD68C7B8D32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{114A2B34-D77E-42AE-ADAF-0CD68C7B8D32}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -21,3 +21,9 @@ The MycroForge CLI assumes a linux compatible environment, so on Windows you'll
|
||||
Run the install script in the same directory as the downloaded zip. See the example below for linux-x64.
|
||||
|
||||
`sudo ./install.sh m4g-<platform>.zip <platform>`
|
||||
|
||||
### Add DevDisciples NuGet source
|
||||
|
||||
```bash
|
||||
dotnet nuget add source --name devdisciples --username username --password password https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json --store-password-in-clear-text
|
||||
```
|
20
docs/.gitignore
vendored
Normal file
20
docs/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
182
docs/README.md
182
docs/README.md
@ -1,141 +1,41 @@
|
||||
# General introduction
|
||||
|
||||
## What is MycroForge?
|
||||
|
||||
MycroForge is an opinionated CLI tool that is meant to facilitate the development of FastAPI & SQLAlchemy based backends.
|
||||
It provides a command line interface that allows users to generate a skeleton project and other common items like
|
||||
database entities, migrations, routers and even basic CRUD functionality. The main purpose of this tool is to generate
|
||||
boilerplate code and to provide a unified interface for performing recurrent development activities.
|
||||
|
||||
|
||||
## Generating a project
|
||||
|
||||
To generate a project you can run the following command.
|
||||
|
||||
`m4g init <name>`
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize a new project
|
||||
|
||||
Usage:
|
||||
m4g init <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of your project
|
||||
|
||||
Options:
|
||||
--without <api|db|git> Features to exclude
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
```
|
||||
|
||||
Running this command will generate the following project structure.
|
||||
|
||||
```
|
||||
📦<project_name>
|
||||
┣ 📂.git
|
||||
┣ 📂.venv
|
||||
┣ 📂api
|
||||
┃ ┗ 📂routers
|
||||
┃ ┃ ┗ 📜hello.py
|
||||
┣ 📂db
|
||||
┃ ┣ 📂engine
|
||||
┃ ┃ ┗ 📜async_session.py
|
||||
┃ ┣ 📂entities
|
||||
┃ ┃ ┗ 📜entity_base.py
|
||||
┃ ┣ 📂versions
|
||||
┃ ┣ 📜README
|
||||
┃ ┣ 📜env.py
|
||||
┃ ┣ 📜script.py.mako
|
||||
┃ ┗ 📜settings.py
|
||||
┣ 📜.gitignore
|
||||
┣ 📜alembic.ini
|
||||
┣ 📜db.docker-compose.yml
|
||||
┣ 📜m4g.json
|
||||
┣ 📜main.py
|
||||
┗ 📜requirements.txt
|
||||
```
|
||||
|
||||
Let's go through these one by one.
|
||||
|
||||
### .git
|
||||
|
||||
The `m4g init` command will initialize new projects with git by default.
|
||||
If you don't want to use git you can pass the option `--without git` to `m4g init`.
|
||||
|
||||
### .venv
|
||||
|
||||
To promote isolation of Python dependencies, new projects are initialized with a virtual environment by default.
|
||||
TODO: This is a good section to introduce the `m4g hydrate` command.
|
||||
|
||||
### api/routers/hello.py
|
||||
|
||||
This file defines a basic example router, which is imported and mapped in `main.py`. This router is just an example and
|
||||
can be removed or modified at you discretion.
|
||||
|
||||
### db/engine/async_session.py
|
||||
|
||||
This file defines the `async_session` function, which can be used to open an asynchronous session to a database.
|
||||
|
||||
### db/entities/entity_base.py
|
||||
|
||||
This file contains an automatically generated entity base class that derives from the DeclarativeBase.
|
||||
All entities must inherit from this class, so that SQLAlchemy & alembic can track them. The entities directory is also
|
||||
where all newly generated entities will be stored.
|
||||
|
||||
### db/versions
|
||||
|
||||
This is where the generated database migrations will be stored.
|
||||
|
||||
### db/README
|
||||
|
||||
This README file is automatically generated by the alembic init command.
|
||||
|
||||
### db/env.py
|
||||
|
||||
This is the database environment file that is used by alembic to interact with the database.
|
||||
If you take a closer look at the imports, you'll see that the file has been modified to assign `EntityBase.metadata` to
|
||||
a variable called `target_metadata`, this will allow alembic to track changes in your entities. You'll also find that
|
||||
the `DbSettings` class is used to get the connectionstring. Any time you generate a new database entity, or create a
|
||||
many-to-many relation between two entities, this file will also be modified to include the generated classes.
|
||||
|
||||
### db/script.py.mako
|
||||
|
||||
This file is automatically generated by the alembic init command.
|
||||
|
||||
### db/settings.py
|
||||
|
||||
This file defines the `DbSettings` class, that is responsible for retrieving the database connectionstring.
|
||||
You will probably want to modify this class to retrieve the connectionstring from a secret manager at some point.
|
||||
|
||||
### .gitignore
|
||||
|
||||
The default .gitignore file that is generated by the `m4g init` command. Modify this file at your discretion.
|
||||
|
||||
### alembic.ini
|
||||
|
||||
This file is automatically generated by the alembic init command.
|
||||
|
||||
### db.docker-compose.yml
|
||||
|
||||
A docker compose file for running a database locally.
|
||||
|
||||
### m4g.json
|
||||
|
||||
This file contains some configs that are used by the CLI, for example the ports to map to the API and database.
|
||||
|
||||
### main.py
|
||||
|
||||
The entrypoint for the application. When generating entities, many-to-many relations or routers, this file will be
|
||||
modified to include the generated files.
|
||||
|
||||
### requirements.txt
|
||||
|
||||
The requirements file containing the Python dependencies.
|
||||
TODO: introduce the `m4g install` & `m4g uninstall` commands.
|
||||
|
||||
|
||||
## Scripting
|
||||
|
||||
TODO: Dedicate a section to scripting
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```
|
||||
$ USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
3
docs/babel.config.js
Normal file
3
docs/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
24
docs/docs/Commands/index.md
Normal file
24
docs/docs/Commands/index.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Commands
|
||||
|
||||
```
|
||||
Description:
|
||||
The MycroForge CLI tool.
|
||||
|
||||
Usage:
|
||||
m4g [command] [options]
|
||||
|
||||
Options:
|
||||
--version Show version information
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
init <name> Initialize a new project
|
||||
i, install <packages> Install packages and update the requirements.txt
|
||||
u, uninstall <packages> Uninstall packages and update the requirements.txt
|
||||
hydrate Initialize venv and install dependencies from requirements.txt
|
||||
add Add features to the project
|
||||
g, generate Generate common items
|
||||
api API related commands
|
||||
db Database related commands
|
||||
p, plugin Plugin related commands
|
||||
```
|
22
docs/docs/Commands/m4g_add/index.md
Normal file
22
docs/docs/Commands/m4g_add/index.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g add
|
||||
|
||||
```
|
||||
Description:
|
||||
Add features to the project
|
||||
|
||||
Usage:
|
||||
m4g add [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
api Add FastAPI to the project
|
||||
db Add SQLAlchemy & Alembic to the project
|
||||
git Add git to the project
|
||||
gitignore Add a default .gitignore file to the project
|
||||
```
|
17
docs/docs/Commands/m4g_add/m4g_add_api.md
Normal file
17
docs/docs/Commands/m4g_add/m4g_add_api.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g add api
|
||||
|
||||
```
|
||||
Description:
|
||||
Add FastAPI to the project
|
||||
|
||||
Usage:
|
||||
m4g add api [options]
|
||||
|
||||
Options:
|
||||
--api-port <api-port> The API port
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
18
docs/docs/Commands/m4g_add/m4g_add_db.md
Normal file
18
docs/docs/Commands/m4g_add/m4g_add_db.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g add db
|
||||
|
||||
```
|
||||
Description:
|
||||
Add SQLAlchemy & Alembic to the project
|
||||
|
||||
Usage:
|
||||
m4g add db [options]
|
||||
|
||||
Options:
|
||||
--database-host-port, --dbh-port <database-host-port> The database host port
|
||||
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_add/m4g_add_git.md
Normal file
16
docs/docs/Commands/m4g_add/m4g_add_git.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g add git
|
||||
|
||||
```
|
||||
Description:
|
||||
Add git to the project
|
||||
|
||||
Usage:
|
||||
m4g add git [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_add/m4g_add_gitignore.md
Normal file
16
docs/docs/Commands/m4g_add/m4g_add_gitignore.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g add gitignore
|
||||
|
||||
```
|
||||
Description:
|
||||
Add a default .gitignore file to the project
|
||||
|
||||
Usage:
|
||||
m4g add gitignore [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
20
docs/docs/Commands/m4g_api/index.md
Normal file
20
docs/docs/Commands/m4g_api/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g api
|
||||
|
||||
```
|
||||
Description:
|
||||
API related commands
|
||||
|
||||
Usage:
|
||||
m4g api [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
run Run your app
|
||||
g, generate Generate an API item
|
||||
```
|
20
docs/docs/Commands/m4g_api/m4g_api_generate/index.md
Normal file
20
docs/docs/Commands/m4g_api/m4g_api_generate/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g api generate
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate an API item
|
||||
|
||||
Usage:
|
||||
m4g api generate [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
r, router <name> Generate an api router
|
||||
crud <entity> Generated CRUD functionality for an entity
|
||||
```
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g api generate crud
|
||||
|
||||
```
|
||||
Description:
|
||||
Generated CRUD functionality for an entity
|
||||
|
||||
Usage:
|
||||
m4g api generate crud <entity> [options]
|
||||
|
||||
Arguments:
|
||||
<entity> The entity to target
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g api generate router
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate an api router
|
||||
|
||||
Usage:
|
||||
m4g api generate router <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of the api router
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_api/m4g_api_run.md
Normal file
16
docs/docs/Commands/m4g_api/m4g_api_run.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g api run
|
||||
|
||||
```
|
||||
Description:
|
||||
Run your app
|
||||
|
||||
Usage:
|
||||
m4g api run [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
24
docs/docs/Commands/m4g_db/index.md
Normal file
24
docs/docs/Commands/m4g_db/index.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db
|
||||
|
||||
```
|
||||
Description:
|
||||
Database related commands
|
||||
|
||||
Usage:
|
||||
m4g db [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
run Runs the services defined in db.docker-compose.yml
|
||||
stop Stops db.docker-compose.yml
|
||||
migrate Apply migrations to the database
|
||||
rollback Rollback the last migration
|
||||
g, generate Generate a database item
|
||||
link Define relationships between entities
|
||||
```
|
20
docs/docs/Commands/m4g_db/m4g_db_generate/index.md
Normal file
20
docs/docs/Commands/m4g_db/m4g_db_generate/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db generate
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate a database item
|
||||
|
||||
Usage:
|
||||
m4g db generate [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
e, entity <name> Generate and database entity
|
||||
m, migration <name> Generate a migration
|
||||
```
|
@ -0,0 +1,35 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db generate entity
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate and database entity
|
||||
|
||||
Usage:
|
||||
m4g db generate entity <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of the database entity
|
||||
|
||||
Supported formats:
|
||||
Entity
|
||||
path/relative/to/entities:Entity
|
||||
|
||||
Options:
|
||||
-c, --column <column> Specify the fields to add.
|
||||
|
||||
Format:
|
||||
<name>:<native_type>:<orm_type>
|
||||
|
||||
<name> = Name of the column
|
||||
<native_type> = The native Python type
|
||||
<orm_type> = The SQLAlchemy type
|
||||
|
||||
Example:
|
||||
first_name:str:String(255)
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
```
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db generate migration
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate a migration
|
||||
|
||||
Usage:
|
||||
m4g db generate migration <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of the migration
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
21
docs/docs/Commands/m4g_db/m4g_db_link/index.md
Normal file
21
docs/docs/Commands/m4g_db/m4g_db_link/index.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db link
|
||||
|
||||
```
|
||||
Description:
|
||||
Define relationships between entities
|
||||
|
||||
Usage:
|
||||
m4g db link [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
one <entity> Define a 1:n relation
|
||||
many <entity> Define a n:m relation
|
||||
|
||||
```
|
21
docs/docs/Commands/m4g_db/m4g_db_link/m4g_db_link_many.md
Normal file
21
docs/docs/Commands/m4g_db/m4g_db_link/m4g_db_link_many.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db link many
|
||||
|
||||
```
|
||||
Description:
|
||||
Define a n:m relation
|
||||
|
||||
Usage:
|
||||
m4g db link many <entity> [options]
|
||||
|
||||
Arguments:
|
||||
<entity> The left side of the relation
|
||||
|
||||
Options:
|
||||
--to-one <to-one> The right side of the relation
|
||||
--to-many <to-many> The right side of the relation
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
21
docs/docs/Commands/m4g_db/m4g_db_link/m4g_db_link_one.md
Normal file
21
docs/docs/Commands/m4g_db/m4g_db_link/m4g_db_link_one.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db link one
|
||||
|
||||
```
|
||||
Description:
|
||||
Define a 1:n relation
|
||||
|
||||
Usage:
|
||||
m4g db link one <entity> [options]
|
||||
|
||||
Arguments:
|
||||
<entity> The left side of the relation
|
||||
|
||||
Options:
|
||||
--to-one <to-one> The right side of the relation
|
||||
--to-many <to-many> The right side of the relation
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_db/m4g_db_migrate.md
Normal file
16
docs/docs/Commands/m4g_db/m4g_db_migrate.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db migrate
|
||||
|
||||
```
|
||||
Description:
|
||||
Apply migrations to the database
|
||||
|
||||
Usage:
|
||||
m4g db migrate [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_db/m4g_db_rollback.md
Normal file
16
docs/docs/Commands/m4g_db/m4g_db_rollback.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db rollback
|
||||
|
||||
```
|
||||
Description:
|
||||
Rollback the last migration
|
||||
|
||||
Usage:
|
||||
m4g db rollback [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_db/m4g_db_run.md
Normal file
16
docs/docs/Commands/m4g_db/m4g_db_run.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db run
|
||||
|
||||
```
|
||||
Description:
|
||||
Runs the services defined in db.docker-compose.yml
|
||||
|
||||
Usage:
|
||||
m4g db run [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_db/m4g_db_stop.md
Normal file
16
docs/docs/Commands/m4g_db/m4g_db_stop.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g db stop
|
||||
|
||||
```
|
||||
Description:
|
||||
Stops db.docker-compose.yml
|
||||
|
||||
Usage:
|
||||
m4g db stop [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
20
docs/docs/Commands/m4g_generate/index.md
Normal file
20
docs/docs/Commands/m4g_generate/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g generate
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate common items
|
||||
|
||||
Usage:
|
||||
m4g generate [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
s, service <name> Generate a service
|
||||
venv Generate a venv
|
||||
```
|
20
docs/docs/Commands/m4g_generate/m4g_generate_service.md
Normal file
20
docs/docs/Commands/m4g_generate/m4g_generate_service.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g generate service
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate a service
|
||||
|
||||
Usage:
|
||||
m4g generate service <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of the service
|
||||
|
||||
Options:
|
||||
--with-session Create a service that uses database sessions
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_generate/m4g_generate_vend.md
Normal file
16
docs/docs/Commands/m4g_generate/m4g_generate_vend.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g generate venv
|
||||
|
||||
```
|
||||
Description:
|
||||
Generate a venv
|
||||
|
||||
Usage:
|
||||
m4g generate venv [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_hydrate.md
Normal file
16
docs/docs/Commands/m4g_hydrate.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g hydrate
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize venv and install dependencies from requirements.txt
|
||||
|
||||
Usage:
|
||||
m4g hydrate [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
23
docs/docs/Commands/m4g_init.md
Normal file
23
docs/docs/Commands/m4g_init.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g init
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize a new project
|
||||
|
||||
Usage:
|
||||
m4g init <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of your project
|
||||
|
||||
Options:
|
||||
--without <api|db|git|gitignore> Features to exclude
|
||||
--api-port <api-port> The API port
|
||||
--database-host-port, --dbh-port <database-host-port> The database host port
|
||||
--database-ui-port, --dbu-port <database-ui-port> The database UI port
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
19
docs/docs/Commands/m4g_install.md
Normal file
19
docs/docs/Commands/m4g_install.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g install
|
||||
|
||||
```
|
||||
Description:
|
||||
Install packages and update the requirements.txt
|
||||
|
||||
Usage:
|
||||
m4g install [<packages>...] [options]
|
||||
|
||||
Arguments:
|
||||
<packages> The names of the packages to install
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
22
docs/docs/Commands/m4g_plugin/index.md
Normal file
22
docs/docs/Commands/m4g_plugin/index.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g plugin
|
||||
|
||||
```
|
||||
Description:
|
||||
Plugin related commands
|
||||
|
||||
Usage:
|
||||
m4g plugin [command] [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
Commands:
|
||||
init <name> Initialize a basic plugin project
|
||||
l, list, ls List all installed plugins
|
||||
i, install Install a plugin
|
||||
u, uninstall <name> Uninstall a plugin
|
||||
```
|
19
docs/docs/Commands/m4g_plugin/m4g_plugin_init.md
Normal file
19
docs/docs/Commands/m4g_plugin/m4g_plugin_init.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g plugin init
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize a basic plugin project
|
||||
|
||||
Usage:
|
||||
m4g plugin init <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of your project
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
17
docs/docs/Commands/m4g_plugin/m4g_plugin_install.md
Normal file
17
docs/docs/Commands/m4g_plugin/m4g_plugin_install.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g plugin install
|
||||
|
||||
```
|
||||
Description:
|
||||
Install a plugin
|
||||
|
||||
Usage:
|
||||
m4g plugin install [options]
|
||||
|
||||
Options:
|
||||
-p, --platform <linux_arm|linux_arm64|linux_x64|osx_arm64|osx_x64> (REQUIRED) The platform to target when building the plugin
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
16
docs/docs/Commands/m4g_plugin/m4g_plugin_list.md
Normal file
16
docs/docs/Commands/m4g_plugin/m4g_plugin_list.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g plugin list
|
||||
|
||||
```
|
||||
Description:
|
||||
List all installed plugins
|
||||
|
||||
Usage:
|
||||
m4g plugin list [options]
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
19
docs/docs/Commands/m4g_plugin/m4g_plugin_uninstall.md
Normal file
19
docs/docs/Commands/m4g_plugin/m4g_plugin_uninstall.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g plugin uninstall
|
||||
|
||||
```
|
||||
Description:
|
||||
Uninstall a plugin
|
||||
|
||||
Usage:
|
||||
m4g plugin uninstall [<name>...] [options]
|
||||
|
||||
Arguments:
|
||||
<name> The names of the plugins you want to uninstall
|
||||
|
||||
Options:
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
20
docs/docs/Commands/m4g_uninstall.md
Normal file
20
docs/docs/Commands/m4g_uninstall.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# m4g uninstall
|
||||
|
||||
```
|
||||
Description:
|
||||
Uninstall packages and update the requirements.txt
|
||||
|
||||
Usage:
|
||||
m4g uninstall [<packages>...] [options]
|
||||
|
||||
Arguments:
|
||||
<packages> The names of the packages to uninstall
|
||||
|
||||
Options:
|
||||
-y, --yes Don’t ask for confirmation of uninstall deletions
|
||||
-?, -h, --help Show help and usage information
|
||||
```
|
20
docs/docs/install.md
Normal file
20
docs/docs/install.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Install
|
||||
|
||||
## Requirements
|
||||
|
||||
MycroForge has the following dependencies.
|
||||
|
||||
- bash
|
||||
- git
|
||||
- Python3 (3.10)
|
||||
- Docker
|
||||
|
||||
### Windows
|
||||
|
||||
To simplify the implementation of this tool, it assumes that it's running in a POSIX compliant environment.
|
||||
MycroForge has been developed and tested on Windows in WSL2 Ubuntu 22.04.03.
|
||||
So when running on Windows, it's recommended to run MycroForge in the same environment or atleast in a similar WSL2 distro.
|
145
docs/docs/intro.md
Normal file
145
docs/docs/intro.md
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Intro
|
||||
|
||||
## What is MycroForge?
|
||||
|
||||
MycroForge is an opinionated CLI tool that is meant to facilitate the development of FastAPI & SQLAlchemy based backends.
|
||||
It provides a command line interface that allows users to generate a skeleton project and other common items like
|
||||
database entities, migrations, routers and even basic CRUD functionality. The main purpose of this tool is to generate
|
||||
boilerplate code and to provide a unified interface for performing recurrent development activities.
|
||||
|
||||
|
||||
## Generating a project
|
||||
|
||||
To generate a project you can run the following command.
|
||||
|
||||
`m4g init <name>`
|
||||
|
||||
```
|
||||
Description:
|
||||
Initialize a new project
|
||||
|
||||
Usage:
|
||||
m4g init <name> [options]
|
||||
|
||||
Arguments:
|
||||
<name> The name of your project
|
||||
|
||||
Options:
|
||||
--without <api|db|git> Features to exclude
|
||||
-?, -h, --help Show help and usage information
|
||||
|
||||
```
|
||||
|
||||
Running this command will generate the following project structure.
|
||||
|
||||
```
|
||||
📦<project_name>
|
||||
┣ 📂.git
|
||||
┣ 📂.venv
|
||||
┣ 📂api
|
||||
┃ ┗ 📂routers
|
||||
┃ ┃ ┗ 📜hello.py
|
||||
┣ 📂db
|
||||
┃ ┣ 📂engine
|
||||
┃ ┃ ┗ 📜async_session.py
|
||||
┃ ┣ 📂entities
|
||||
┃ ┃ ┗ 📜entity_base.py
|
||||
┃ ┣ 📂versions
|
||||
┃ ┣ 📜README
|
||||
┃ ┣ 📜env.py
|
||||
┃ ┣ 📜script.py.mako
|
||||
┃ ┗ 📜settings.py
|
||||
┣ 📜.gitignore
|
||||
┣ 📜alembic.ini
|
||||
┣ 📜db.docker-compose.yml
|
||||
┣ 📜m4g.json
|
||||
┣ 📜main.py
|
||||
┗ 📜requirements.txt
|
||||
```
|
||||
|
||||
Let's go through these one by one.
|
||||
|
||||
### .git
|
||||
|
||||
The `m4g init` command will initialize new projects with git by default.
|
||||
If you don't want to use git you can pass the option `--without git` to `m4g init`.
|
||||
|
||||
### .venv
|
||||
|
||||
To promote isolation of Python dependencies, new projects are initialized with a virtual environment by default.
|
||||
TODO: This is a good section to introduce the `m4g hydrate` command.
|
||||
|
||||
### api/routers/hello.py
|
||||
|
||||
This file defines a basic example router, which is imported and mapped in `main.py`. This router is just an example and
|
||||
can be removed or modified at you discretion.
|
||||
|
||||
### db/engine/async_session.py
|
||||
|
||||
This file defines the `async_session` function, which can be used to open an asynchronous session to a database.
|
||||
|
||||
### db/entities/entity_base.py
|
||||
|
||||
This file contains an automatically generated entity base class that derives from the DeclarativeBase.
|
||||
All entities must inherit from this class, so that SQLAlchemy & alembic can track them. The entities directory is also
|
||||
where all newly generated entities will be stored.
|
||||
|
||||
### db/versions
|
||||
|
||||
This is where the generated database migrations will be stored.
|
||||
|
||||
### db/README
|
||||
|
||||
This README file is automatically generated by the alembic init command.
|
||||
|
||||
### db/env.py
|
||||
|
||||
This is the database environment file that is used by alembic to interact with the database.
|
||||
If you take a closer look at the imports, you'll see that the file has been modified to assign `EntityBase.metadata` to
|
||||
a variable called `target_metadata`, this will allow alembic to track changes in your entities. You'll also find that
|
||||
the `DbSettings` class is used to get the connectionstring. Any time you generate a new database entity, or create a
|
||||
many-to-many relation between two entities, this file will also be modified to include the generated classes.
|
||||
|
||||
### db/script.py.mako
|
||||
|
||||
This file is automatically generated by the alembic init command.
|
||||
|
||||
### db/settings.py
|
||||
|
||||
This file defines the `DbSettings` class, that is responsible for retrieving the database connectionstring.
|
||||
You will probably want to modify this class to retrieve the connectionstring from a secret manager at some point.
|
||||
|
||||
### .gitignore
|
||||
|
||||
The default .gitignore file that is generated by the `m4g init` command. Modify this file at your discretion.
|
||||
|
||||
### alembic.ini
|
||||
|
||||
This file is automatically generated by the alembic init command.
|
||||
|
||||
### db.docker-compose.yml
|
||||
|
||||
A docker compose file for running a database locally.
|
||||
|
||||
### m4g.json
|
||||
|
||||
This file contains some configs that are used by the CLI, for example the ports to map to the API and database.
|
||||
|
||||
### main.py
|
||||
|
||||
The entrypoint for the application. When generating entities, many-to-many relations or routers, this file will be
|
||||
modified to include the generated files.
|
||||
|
||||
### requirements.txt
|
||||
|
||||
The requirements file containing the Python dependencies.
|
||||
TODO: introduce the `m4g install` & `m4g uninstall` commands.
|
||||
|
||||
|
||||
## Plugin system
|
||||
|
||||
TODO: Dedicate a section to the Plugin system
|
283
docs/docs/tutorial.md
Normal file
283
docs/docs/tutorial.md
Normal file
@ -0,0 +1,283 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Tutorial
|
||||
|
||||
We're going to build a simple todo app to demonstrate the capabilities of the MycroForge CLI.
|
||||
After this tutorial, you should have a solid foundation to start exploring and using MycroForge to develop your
|
||||
projects.
|
||||
|
||||
## General notes
|
||||
|
||||
The commands in this tutorial assume that you're running them from a MycroForge root directory.
|
||||
|
||||
## Initialize the project
|
||||
|
||||
Open a terminal and `cd` into the directory where your project should be created.
|
||||
Run `m4g init todo-app` to initialize a new project and open the newly created project by running `code todo-app`.
|
||||
Make sure you have `vscode` on you machine before running this command. If you prefer using another editor, then
|
||||
manually open the generated `todo-app` folder.
|
||||
|
||||
## Setup the database
|
||||
|
||||
Our todo app needs to keep track of todos, so it needs a storage mechanism of some sorts. A database should be one
|
||||
of the first things, if not THE first thing, that comes to mind. Luckily, MycroForge provides you with a locally hosted
|
||||
database for you project out of the box. This setup, is powered by docker compose and can be examined by opening the
|
||||
`db.docker-compose.yml` file in the project. Follow along to learn how to use the docker based database when developing
|
||||
locally.
|
||||
|
||||
### Run the database
|
||||
|
||||
The first step is to start the database, you can do this by running the following command in a terminal.
|
||||
|
||||
`m4g db run`
|
||||
|
||||
This command starts the services defined in the `db.docker-compose.yml` file.
|
||||
You can verify that the services are up by running `docker container ls`. If everything went well, then the previous
|
||||
command should output the service names defined in `db.docker-compose.yml`.
|
||||
|
||||
If you go to [PhpMyAdmin (i.e. http://localhost:5051)](http://localhost:5051), you should now be able to login with the
|
||||
following credentials.
|
||||
- user: root
|
||||
- pass: password
|
||||
|
||||
When you're done developing, you can shut down the local database by running `m4g db stop`
|
||||
|
||||
|
||||
### Create the entities
|
||||
|
||||
Now that the database is running, we can start to create our entities. Run the commands below to create the `Todo` &
|
||||
`Tag` entities.
|
||||
|
||||
`m4g db generate entity Tag --column "description:str:String(255)"`
|
||||
|
||||
`m4g db generate entity Todo --column "description:str:String(255)" -c "is_done:bool:Boolean()"`
|
||||
|
||||
After running these commands, you should find the generated entities in the `db/entities` folder of your project.
|
||||
You should also see that the `main.py` & `db/env.py` files have been modified to include the newly generated entity.
|
||||
|
||||
For more information about the `m4g db generate entity` command, you can run `m4g db generate entity -?`.
|
||||
|
||||
### Define a many-to-many relation between Todo & Tag
|
||||
|
||||
To allow for relations between `Todo` & `Tag`, we'll define a many-to-many relation between the two entities.
|
||||
This relation makes sense, because a `Todo` can have many `Tags` and a `Tag` could belong to many `Todos`.
|
||||
You can generate this relation by running the following command from you terminal.
|
||||
Creating a one-to-many relation would also make sense, but for the purpose of demonstration we're going to demonstrate
|
||||
the many-to-many relation, because this one is the most complex, since it requires an additional mapping to be included
|
||||
in the database schema.
|
||||
|
||||
`m4g db link many Todo --to-many Tag`
|
||||
|
||||
After running this command you should see that both the `Todo` and `Tag` entities now have a new field referencing the
|
||||
a `List` containing instances of the other entity.
|
||||
|
||||
For more information about the `m4g db link` command try running `m4g db link -?`. Note that you can do the same thing
|
||||
for all sub commands, so if you want to know more about `m4g db link many` you can simply run `m4g db link many -?` to
|
||||
examine the command. The same is true for all the other commands as well.
|
||||
|
||||
### Generate the migration
|
||||
|
||||
Now that we've generated our entities, it's time to generate a migration that will apply these changes in the database.
|
||||
Generate the initial migration by running the following command.
|
||||
|
||||
`m4g db generate migration initial_migration`
|
||||
|
||||
After running this command, you should see the new migration in the `db/version` directory.
|
||||
|
||||
### Apply the migration
|
||||
|
||||
The last step for the database setup is to actually apply the new migration to the database. This can be done by running
|
||||
the following command.
|
||||
|
||||
`m4g db migrate`
|
||||
|
||||
After running this command, you should now see a populated schema when visiting [PhpMyAdmin](http://localhost:5051).
|
||||
If for whatever reason you want to undo the last migration, you can simply run `m4g db rollback`.
|
||||
|
||||
## Setup the API
|
||||
|
||||
### Generate CRUD for Tag & Todo
|
||||
|
||||
Our API should provide use with basic endpoint to manage the `Todo` & `Tag` entities, i.e. CRUD functionality.
|
||||
Writing this code can be boring, since it's pretty much boilerplate with some custom additions sprinkled here and there.
|
||||
Fortunately, MycroForge can generate a good chunk of this boring code on your behalf. Run the following commands to
|
||||
generate CRUD functionality for the `Todo` & `Tag` classes.
|
||||
|
||||
`m4g api generate crud Tag`
|
||||
|
||||
`m4g api generate crud Todo`
|
||||
|
||||
After running this command you should see that the `api/requests`,`api/routers` & `api/services` now contain the
|
||||
relevant classes need to support the generated CRUD functionality. This could should be relatively straightforward, so
|
||||
we won't dive into it, but feel free to take a break and explore what the generated code actually does. Another thing to
|
||||
note, is that the generated routers are also automatically included in `main.py`.
|
||||
|
||||
### Modify the generated Todo request classes
|
||||
|
||||
Since we have a many-to-many relationship between `Todo` & `Tag`, the generated CRUD functionality isn't quite ready
|
||||
yet. We need to be able to specify which `Tags` to add to a `Todo` when creating or updating it.
|
||||
To do this, we will allow for a `tag_ids` field in both the `CreateTodoRequest` & the `UpdateTodoRequest`.
|
||||
This field will contain the ids of the `Tags` that are associated with a `Todo`.
|
||||
|
||||
Modify `CreateTodoRequest` in `api/requests/create_todo_request.py`, you might need to import `List` from `typing`.
|
||||
|
||||
```python
|
||||
# Before
|
||||
class CreateTodoRequest(BaseModel):
|
||||
description: str = None
|
||||
is_done: bool = None
|
||||
|
||||
# After
|
||||
class CreateTodoRequest(BaseModel):
|
||||
description: str = None
|
||||
is_done: bool = None
|
||||
tag_ids: Optional[List[int]] = []
|
||||
```
|
||||
|
||||
Modify `UpdateTodoRequest` in `api/requests/update_todo_request.py`, you might need to import `List` from `typing`.
|
||||
|
||||
```python
|
||||
# Before
|
||||
class UpdateTodoRequest(BaseModel):
|
||||
description: Optional[str] = None
|
||||
is_done: Optional[bool] = None
|
||||
tag_ids: Optional[List[int]] = []
|
||||
|
||||
# After
|
||||
class UpdateTodoRequest(BaseModel):
|
||||
description: Optional[str] = None
|
||||
is_done: Optional[bool] = None
|
||||
tag_ids: Optional[List[int]] = []
|
||||
```
|
||||
|
||||
### Modify generated TodoService
|
||||
|
||||
The `TodoService` will also need to be updated to accomodate the management of `tag_ids`.
|
||||
|
||||
Add the following imports in `api/services/todo_service.py`.
|
||||
|
||||
```python
|
||||
from sqlalchemy.orm import selectinload
|
||||
from db.entities.tag import Tag
|
||||
```
|
||||
|
||||
Modify `TodoService.list`
|
||||
|
||||
```python
|
||||
# Before
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo)
|
||||
results = (await session.scalars(stmt)).all()
|
||||
return results
|
||||
|
||||
# After
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo).options(selectinload(Todo.tags))
|
||||
results = (await session.scalars(stmt)).all()
|
||||
return results
|
||||
```
|
||||
|
||||
Modify `TodoService.get_by_id`
|
||||
|
||||
```python
|
||||
# Before
|
||||
async def get_by_id(self, id: int) -> Optional[Todo]:
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo).where(Todo.id == id)
|
||||
result = (await session.scalars(stmt)).first()
|
||||
return result
|
||||
|
||||
# After
|
||||
async def get_by_id(self, id: int) -> Optional[Todo]:
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
||||
result = (await session.scalars(stmt)).first()
|
||||
return result
|
||||
```
|
||||
|
||||
Modify `TodoService.create`
|
||||
|
||||
```python
|
||||
# Before
|
||||
async def create(self, data: Dict[str, Any]) -> None:
|
||||
async with async_session() as session:
|
||||
entity = Todo(**data)
|
||||
session.add(entity)
|
||||
await session.commit()
|
||||
|
||||
# After
|
||||
async def create(self, data: Dict[str, Any]) -> None:
|
||||
tag_ids = []
|
||||
|
||||
if "tag_ids" in data.keys():
|
||||
tag_ids = data["tag_ids"]
|
||||
del data["tag_ids"]
|
||||
|
||||
async with async_session() as session:
|
||||
entity = Todo(**data)
|
||||
|
||||
if len(tag_ids) > 0:
|
||||
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
||||
result = (await session.scalars(stmt)).all()
|
||||
entity.tags = list(result)
|
||||
|
||||
session.add(entity)
|
||||
await session.commit()
|
||||
```
|
||||
|
||||
Modify `TodoService.update`
|
||||
|
||||
```python
|
||||
# Before
|
||||
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
||||
tag_ids = []
|
||||
|
||||
if "tag_ids" in data.keys():
|
||||
tag_ids = data["tag_ids"]
|
||||
del data["tag_ids"]
|
||||
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo).where(Todo.id == id)
|
||||
entity = (await session.scalars(stmt)).first()
|
||||
|
||||
if entity is None:
|
||||
return False
|
||||
else:
|
||||
for key, value in data.items():
|
||||
setattr(entity, key, value)
|
||||
await session.commit()
|
||||
return True
|
||||
|
||||
# After
|
||||
async def update(self, id: int, data: Dict[str, Any]) -> bool:
|
||||
tag_ids = []
|
||||
|
||||
if "tag_ids" in data.keys():
|
||||
tag_ids = data["tag_ids"]
|
||||
del data["tag_ids"]
|
||||
|
||||
async with async_session() as session:
|
||||
stmt = select(Todo).where(Todo.id == id).options(selectinload(Todo.tags))
|
||||
entity = (await session.scalars(stmt)).first()
|
||||
|
||||
if entity is None:
|
||||
return False
|
||||
else:
|
||||
for key, value in data.items():
|
||||
setattr(entity, key, value)
|
||||
|
||||
if len(tag_ids) > 0:
|
||||
stmt = select(Tag).where(Tag.id.in_(tag_ids))
|
||||
result = (await session.scalars(stmt)).all()
|
||||
entity.tags = list(result)
|
||||
else:
|
||||
entity.tags = []
|
||||
|
||||
await session.commit()
|
||||
return True
|
||||
```
|
||||
|
||||
At this point, the app should be ready to test.
|
||||
TODO: Elaborate!
|
86
docs/docusaurus.config.ts
Normal file
86
docs/docusaurus.config.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {themes as prismThemes} from 'prism-react-renderer';
|
||||
import type {Config} from '@docusaurus/types';
|
||||
import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: 'MycroForge',
|
||||
tagline: 'Your FastAPI & SQLAlchemy assistant!',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
url: 'https://git.devdisciples.com',
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: '/',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'devdisciples', // Usually your GitHub org/user name.
|
||||
projectName: 'mycroforge', // Usually your repo name.
|
||||
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
navbar: {
|
||||
title: 'MycroForge',
|
||||
logo: {
|
||||
alt: 'MycroForge Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'tutorialSidebar',
|
||||
position: 'left',
|
||||
label: 'Docs',
|
||||
},
|
||||
{
|
||||
href: 'https://git.devdisciples.com/devdisciples/mycroforge',
|
||||
label: 'Git',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} DevDisciples`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.github,
|
||||
darkTheme: prismThemes.dracula,
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
};
|
||||
|
||||
export default config;
|
14543
docs/package-lock.json
generated
Normal file
14543
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
docs/package.json
Normal file
47
docs/package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/preset-classic": "3.4.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.4.0",
|
||||
"@docusaurus/tsconfig": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"typescript": "~5.2.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 3 chrome version",
|
||||
"last 3 firefox version",
|
||||
"last 5 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
35
docs/requirements.txt
Normal file
35
docs/requirements.txt
Normal file
@ -0,0 +1,35 @@
|
||||
asgiref==3.5.0
|
||||
blinker==1.4
|
||||
click==8.0.3
|
||||
colorama==0.4.4
|
||||
command-not-found==0.3
|
||||
cryptography==3.4.8
|
||||
dbus-python==1.2.18
|
||||
distro==1.7.0
|
||||
distro-info==1.1+ubuntu0.2
|
||||
h11==0.13.0
|
||||
httplib2==0.20.2
|
||||
importlib-metadata==4.6.4
|
||||
jeepney==0.7.1
|
||||
keyring==23.5.0
|
||||
launchpadlib==1.10.16
|
||||
lazr.restfulclient==0.14.4
|
||||
lazr.uri==1.0.6
|
||||
more-itertools==8.10.0
|
||||
netifaces==0.11.0
|
||||
oauthlib==3.2.0
|
||||
PyGObject==3.42.1
|
||||
PyJWT==2.3.0
|
||||
pyparsing==2.4.7
|
||||
python-apt==2.4.0+ubuntu3
|
||||
PyYAML==5.4.1
|
||||
SecretStorage==3.3.1
|
||||
six==1.16.0
|
||||
systemd-python==234
|
||||
ubuntu-pro-client==8001
|
||||
ufw==0.36.1
|
||||
unattended-upgrades==0.1
|
||||
uvicorn==0.15.0
|
||||
wadllib==1.3.6
|
||||
wsproto==1.0.0
|
||||
zipp==1.0.0
|
31
docs/sidebars.ts
Normal file
31
docs/sidebars.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
const sidebars: SidebarsConfig = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
'intro',
|
||||
'hello',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['tutorial-basics/create-a-document'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
export default sidebars;
|
69
docs/src/components/HomepageFeatures/index.tsx
Normal file
69
docs/src/components/HomepageFeatures/index.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import clsx from 'clsx';
|
||||
import Heading from '@theme/Heading';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
type FeatureItem = {
|
||||
title: string;
|
||||
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
|
||||
description: JSX.Element;
|
||||
};
|
||||
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: 'Initialize a skeleton project quickly',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Initialize a skeleton project that supports FastAPI and SQLAlchemy with a single command.
|
||||
Here is an example. <code>m4g init todo-app</code>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Generate common items',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
||||
description: (
|
||||
<>
|
||||
MycroForge allows you to generate boilerplate code for common items like entities, service & routers.
|
||||
It can even generate a basic CRUD setup around an entity!
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Extend MycroForge',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Extend MycroForge with your own commands by creating a plugin!
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({title, Svg, description}: FeatureItem) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
<Svg className={styles.featureSvg} role="img" />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<Heading as="h3">{title}</Heading>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures(): JSX.Element {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
11
docs/src/components/HomepageFeatures/styles.module.css
Normal file
11
docs/src/components/HomepageFeatures/styles.module.css
Normal file
@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
30
docs/src/css/custom.css
Normal file
30
docs/src/css/custom.css
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
* work well for content-centric websites.
|
||||
*/
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
23
docs/src/pages/index.module.css
Normal file
23
docs/src/pages/index.module.css
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
*/
|
||||
|
||||
.heroBanner {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
43
docs/src/pages/index.tsx
Normal file
43
docs/src/pages/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
||||
import Heading from '@theme/Heading';
|
||||
|
||||
import styles from './index.module.css';
|
||||
|
||||
function HomepageHeader() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<Heading as="h1" className="hero__title">
|
||||
{siteConfig.title}
|
||||
</Heading>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/intro">
|
||||
MycroForge Tutorial
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`Hello from ${siteConfig.title}`}
|
||||
description="Description will go into a meta tag in <head />">
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
0
docs/static/.nojekyll
vendored
Normal file
0
docs/static/.nojekyll
vendored
Normal file
BIN
docs/static/img/docusaurus-social-card.jpg
vendored
Normal file
BIN
docs/static/img/docusaurus-social-card.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user