Compare commits

46 Commits

Author SHA1 Message Date
21f4e7765b Added uvicorn to API feature
All checks were successful
Test MycroForge.CLI / test (push) Has been skipped
2024-10-24 11:52:37 +02:00
33ba944f8a Updated scripts
All checks were successful
Test MycroForge.CLI / test (push) Successful in 2m23s
2024-10-13 18:12:04 +02:00
05051878f2 Cleaning up versions and workflows
All checks were successful
Test MycroForge.CLI / test (push) Has been skipped
2024-10-13 16:03:02 +02:00
3fff9c7dd0 Renamed build job to test 2024-10-13 15:34:33 +02:00
eda7992c23 Apply version 0.0.1
All checks were successful
Build and publish MycroForge.CLI / test (push) Successful in 1m41s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-13 15:13:50 +02:00
457429f7ec Improved plugin feature
All checks were successful
Build and publish MycroForge.CLI / test (push) Has been skipped
Test MycroForge.CLI / test (push) Successful in 5m39s
2024-10-13 13:39:56 +02:00
8f82360cc7 Forget dollas symbol
All checks were successful
Build and publish MycroForge.CLI / test (push) Successful in 40s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:28:39 +02:00
8396da2e9a Cleaned up build
Some checks failed
Test MycroForge.CLI / test (push) Has been cancelled
Build and publish MycroForge.CLI / test (push) Failing after 47s
2024-10-06 14:27:10 +02:00
5c7d07afb0 Fixed syntax error
All checks were successful
Build and publish MycroForge.CLI / test (push) Successful in 41s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:20:47 +02:00
52d2507891 Listing nupkg directory
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 56s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:16:41 +02:00
7d677d27c6 Trying with full path
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 49s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:13:22 +02:00
0f85244681 Changed version variable reference
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 1m5s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:09:00 +02:00
d59bf264b0 Changed dotnet pack verbosity level
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 49s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:06:36 +02:00
9b339c738e Wrapped nuget push argument in quotes
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 42s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 14:00:31 +02:00
165d97245d Properly configured build actions
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 50s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 13:50:21 +02:00
a1ffc51a57 Moved docs to separate repo
Some checks failed
Build and publish MycroForge.CLI / test (push) Has been cancelled
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 13:27:22 +02:00
6464b9f8f3 Merged build & publish steps
Some checks failed
Test MycroForge.CLI / test (push) Has been skipped
Build and publish MycroForge.CLI / test (push) Failing after 41s
2024-10-06 12:52:12 +02:00
30d2ccba76 Fixed secret reference
Some checks failed
Build and publish MycroForge.CLI / test (push) Failing after 45s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:48:56 +02:00
7d61f76b94 Removed comment
All checks were successful
Build and publish MycroForge.CLI / test (push) Successful in 1m25s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:45:27 +02:00
38b83ac8ac Added version to .csproj
All checks were successful
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:43:45 +02:00
f8efd45076 Onelined add source command
All checks were successful
Build MycroForge.CLI / test (push) Successful in 39s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:31:49 +02:00
c1f618ad19 Merged adding source with the publishing
All checks were successful
Build MycroForge.CLI / test (push) Successful in 54s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:27:55 +02:00
5d49b9ab2d Added directory change to MycroForge.CLI
Some checks failed
Build MycroForge.CLI / test (push) Failing after 44s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:24:46 +02:00
bb3d75521e Testing build action
Some checks failed
Build MycroForge.CLI / test (push) Failing after 37s
Test MycroForge.CLI / test (push) Has been skipped
2024-10-06 12:19:20 +02:00
bc1f0fb943 Manually reference MycroForge.Core in MycroForge.PluginTemplate
All checks were successful
Test and build / test (push) Successful in 2m46s
2024-10-06 11:33:36 +02:00
e95bd759c3 Commented all steps except test
Some checks failed
Test and build / test (push) Failing after 41s
2024-10-06 11:24:53 +02:00
2918f87911 Changed line endings
Some checks failed
Test and build / test (push) Failing after 1m37s
2024-10-06 10:20:45 +02:00
33a86882ac Removed driver spec
Some checks are pending
Test and build / test (push) Waiting to run
2024-10-06 10:19:58 +02:00
d95ee39c0b Tried specifying driver too
Some checks failed
Test and build / test (push) Has been cancelled
2024-10-06 10:00:58 +02:00
9109e9a4c7 Changed back to ubuntu-latest again
Some checks are pending
Test and build / test (push) Waiting to run
2024-10-06 09:55:52 +02:00
67a693b916 Changed .yaml to .yml and changed name back to default
Some checks failed
Test and build / test (push) Has been cancelled
2024-10-06 09:53:57 +02:00
b8373f6908 Changed act runner label
Some checks failed
Test and build / test (push) Has been cancelled
2024-10-06 09:46:15 +02:00
b14a2e7117 Fixed error in yaml
Some checks failed
Test and build / test (push) Has been cancelled
2024-10-06 09:41:57 +02:00
207345b54b Moved build file to workflows folder 2024-10-06 09:40:27 +02:00
dc8327289f Testing ci pipeline 2024-10-06 09:28:13 +02:00
a88f8a1f11 Added .gitea directory 2024-10-05 16:54:45 +02:00
f676f236b1 Added CLI test project and tests for 'm4g init' command 2024-10-05 13:37:14 +02:00
4f322e56c7 Updated README.md 2024-09-22 19:40:28 +02:00
4b0d1df34f Added missing example in tutorial 2024-09-22 17:33:47 +02:00
128ae21088 Clean up 2024-07-26 16:57:28 +02:00
6301bd438a Additional clean up 2024-07-25 07:35:06 +02:00
3f33035611 Cleaned up 2024-07-25 07:34:27 +02:00
32b7a3c01c Cleaned up project and added constraints for SQLAlchemy types in generate entity 2024-07-24 07:18:42 +02:00
91431fd996 Clean up 2024-07-23 22:04:51 +02:00
5ccb40bb44 Fixed exceptions and added constraints to commands 2024-07-23 21:46:11 +02:00
d210c6ac7c Cleaned up, tested and documented --dbu-platform 2024-07-22 21:27:17 +02:00
129 changed files with 907 additions and 17647 deletions

19
.dockerignore Normal file
View File

@@ -0,0 +1,19 @@
# directories
**/bin/
**/obj/
**/out/
.git
.idea
docs
.gitignore
.dockerignore
MycroForge.sln.DotSettings.user
nuget.docker-compose.yml
README.md
# files
Dockerfile*
**/*.md
#MycroForge.PluginTemplate
#MycroForge.PluginTemplate.Package

View File

@@ -0,0 +1,31 @@
name: Build and publish MycroForge.CLI
run-name: ${{ gitea.actor }} triggered a build for the MycroForge.CLI package
on: [ workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
if: gitea.ref == 'refs/heads/main'
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: "Build and publish NuGet package"
run: |
# Build the NuGet package
cd MycroForge.CLI
dotnet pack -v m
# Add the NuGet source
dotnet nuget add source --name devdisciples \
--username ${{ secrets.NUGET_USER }} \
--password ${{ secrets.NUGET_PASS }} \
--store-password-in-clear-text \
https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json
# Get the path to the package
PACKAGE="nupkg/$(ls nupkg)"
# Push the package
dotnet nuget push "$PACKAGE" --source devdisciples

View File

@@ -0,0 +1,33 @@
name: Build and publish MycroForge.Core
run-name: ${{ gitea.actor }} triggered a build for the MycroForge.Core package
on: [ workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
if: gitea.ref == 'refs/heads/main'
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: "Build and publish NuGet package"
run: |
# Build the NuGet package
cd MycroForge.Core
dotnet publish
dotnet pack -v m
# Add the NuGet source
dotnet nuget add source --name devdisciples \
--username ${{ secrets.NUGET_USER }} \
--password ${{ secrets.NUGET_PASS }} \
--store-password-in-clear-text \
https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json
# Set the path to the package
VERSION=$(grep '<Version>' < MycroForge.Core.csproj | sed 's/.*<Version>\(.*\)<\/Version>/\1/' | xargs)
PACKAGE="bin/Release/MycroForge.Core.$VERSION.nupkg"
# Push the package
dotnet nuget push "$PACKAGE" --source devdisciples

View File

@@ -0,0 +1,33 @@
name: Build and publish MycroForge.PluginTemplate package
run-name: ${{ gitea.actor }} triggered a build for the MycroForge.PluginTemplate package
on: [ workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
if: gitea.ref == 'refs/heads/main'
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: "Build and publish NuGet package"
run: |
# Build the NuGet package
cd MycroForge.PluginTemplate.Package
dotnet publish
dotnet pack -v m
# Add the NuGet source
dotnet nuget add source --name devdisciples \
--username ${{ secrets.NUGET_USER }} \
--password ${{ secrets.NUGET_PASS }} \
--store-password-in-clear-text \
https://git.devdisciples.com/api/packages/devdisciples/nuget/index.json
# Set the path to the package
VERSION=$(grep '<PackageVersion>' < MycroForge.PluginTemplate.Package.csproj | sed 's/.*<PackageVersion>\(.*\)<\/PackageVersion>/\1/' | xargs)
PACKAGE="bin/Release/MycroForge.PluginTemplate.Package.$VERSION.nupkg"
# Push the package
dotnet nuget push "$PACKAGE" --source devdisciples

20
.gitea/workflows/test.yml Normal file
View File

@@ -0,0 +1,20 @@
name: Test MycroForge.CLI
run-name: ${{ gitea.actor }} triggered a test for the MycroForge.CLI
on: [ push ]
jobs:
test:
runs-on: ubuntu-latest
if: gitea.ref == 'refs/heads/develop'
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: "Reference MycroForge.Core in MycroForge.PluginTemplate"
# The MycroForge.PluginTemplate project references MycroForge.Core as a package and not as a reference.
# This allows the 'm4g plugin init' command to pull in the core package from a package repository.
# To prevent the test command from trying to pull from the package repository, we reference the local project.
run: dotnet add MycroForge.PluginTemplate reference MycroForge.Core
- name: "Run MycroForge.CLI.Tests"
run: dotnet test

30
Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
# Copy the files to the /tool directory.
WORKDIR /tool
COPY . .
# Manually add a reference to MycroForge.Core in the MycroForge.PluginTemplate project,
# otherwise it will try to fetch it from a nuget repository.
RUN dotnet add MycroForge.PluginTemplate reference MycroForge.Core
# Install the tools required for testing
RUN apt update -y && \
apt upgrade -y && \
apt install -y git && \
apt install -y bash && \
apt install -y python3 && \
apt install -y python3-pip && \
apt install -y python3-venv
# Publish the CLI as a global tool and add the .dotnet/tools folder the the PATH variable.
WORKDIR /tool/MycroForge.CLI
RUN ./scripts/publish-tool.sh
ENV PATH="$PATH:/root/.dotnet/tools"
WORKDIR /test
SHELL ["/bin/bash", "-c"]
ENV PATH="$PATH:/root/.dotnet/tools"
CMD ["sleep", "infinity"]

View File

@@ -0,0 +1,11 @@
using DotNet.Testcontainers.Containers;
namespace MycroForge.CLI.Tests.Extensions;
internal static class ContainerInterfaceExtensions
{
public static Task<byte[]> ReadFileFromRootAsync(this IContainer container, string file)
{
return container.ReadFileAsync($"/test/todo/{file}");
}
}

View File

@@ -0,0 +1 @@
global using NUnit.Framework;

View File

@@ -0,0 +1,222 @@
using System.Text;
using System.Text.Json;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using MycroForge.CLI.Tests.Extensions;
using MycroForge.Core;
namespace MycroForge.CLI.Tests;
public class InitCommandTests
{
private IFutureDockerImage Image = null!;
private IContainer Container = null!;
private async Task CreateImage()
{
Image = new ImageFromDockerfileBuilder()
.WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), string.Empty)
.WithDockerfile("Dockerfile")
.WithCleanUp(true)
.Build();
await Image.CreateAsync();
}
private async Task CreateContainer()
{
Container = new ContainerBuilder()
.WithImage(Image)
.WithCleanUp(true)
.Build();
await Container.StartAsync();
}
[OneTimeSetUp]
public async Task SetupOnce()
{
await CreateImage();
await CreateContainer();
await Container.ExecAsync(["m4g", "init", "todo"]);
}
[Test]
public async Task Creates_m4g_json()
{
var bytes = await Container.ReadFileFromRootAsync("m4g.json");
using var stream = new MemoryStream(bytes);
var config = await JsonSerializer.DeserializeAsync<ProjectConfig>(stream, DefaultJsonSerializerOptions.Default);
Assert.That(config, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(config!.Api, Is.Not.Null);
Assert.That(config.Api.Port, Is.EqualTo(8000));
Assert.That(config.Db, Is.Not.Null);
Assert.That(config.Db.DbhPort, Is.EqualTo(5050));
Assert.That(config.Db.DbuPort, Is.EqualTo(5051));
});
}
[Test]
public async Task Creates_main_py()
{
var bytes = await Container.ReadFileFromRootAsync("main.py");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("from fastapi import FastAPI"));
Assert.That(lines, Does.Contain("from api.routers import hello"));
Assert.That(lines, Does.Contain("app = FastAPI()"));
Assert.That(lines, Does.Contain("app.include_router(prefix=\"/hello\", router=hello.router)"));
});
}
[Test]
public async Task Creates_gitignore()
{
var bytes = await Container.ReadFileFromRootAsync(".gitignore");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain(".venv"));
Assert.That(lines, Does.Contain("__pycache__/"));
});
}
[Test]
public async Task Creates_alembic_ini()
{
var bytes = await Container.ReadFileFromRootAsync("alembic.ini");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("[alembic]"));
Assert.That(lines, Does.Contain("sqlalchemy.url = driver://user:pass@localhost/dbname"));
});
}
[Test]
public async Task Creates_db_docker_compose_yml()
{
var bytes = await Container.ReadFileFromRootAsync("db.docker-compose.yml");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain(" todo_mariadb:"));
Assert.That(lines, Does.Contain(" container_name: 'todo_mariadb'"));
Assert.That(lines, Does.Contain(" - '${DBH_PORT}:3306'"));
Assert.That(lines, Does.Contain(" MYSQL_DATABASE: 'todo'"));
Assert.That(lines, Does.Contain(" - 'todo_mariadb:/var/lib/mysql'"));
Assert.That(lines, Does.Contain(" todo_phpmyadmin:"));
Assert.That(lines, Does.Contain(" container_name: todo_phpmyadmin"));
Assert.That(lines, Does.Contain(" PMA_HOST: todo_mariadb"));
Assert.That(lines, Does.Contain(" - todo_mariadb"));
Assert.That(lines, Does.Contain(" todo_mariadb:"));
});
}
[Test]
public async Task Creates_api__router_hello_py()
{
var bytes = await Container.ReadFileFromRootAsync("api/routers/hello.py");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("from fastapi import APIRouter"));
Assert.That(lines, Does.Contain("from fastapi.responses import JSONResponse"));
Assert.That(lines, Does.Contain("from fastapi.encoders import jsonable_encoder"));
Assert.That(lines, Does.Contain("router = APIRouter()"));
Assert.That(lines, Does.Contain("@router.get(\"/{name}\")"));
Assert.That(lines, Does.Contain("async def hello(name: str):"));
Assert.That(lines, Does.Contain("\treturn JSONResponse(status_code=200, content=jsonable_encoder({'greeting': f\"Hello, {name}!\"}))"));
});
}
[Test]
public async Task Creates_db__engine__async_session_py()
{
var bytes = await Container.ReadFileFromRootAsync("db/engine/async_session.py");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession"));
Assert.That(lines, Does.Contain("from db.settings import DbSettings"));
Assert.That(lines, Does.Contain("async_engine: AsyncEngine = create_async_engine(DbSettings.get_connectionstring())"));
Assert.That(lines, Does.Contain("def async_session() -> AsyncSession:"));
Assert.That(lines, Does.Contain("\treturn AsyncSession(async_engine, expire_on_commit=False)"));
});
}
[Test]
public async Task Creates_db__entities__entity_base_py()
{
var bytes = await Container.ReadFileFromRootAsync("db/entities/entity_base.py");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("from sqlalchemy.orm import DeclarativeBase"));
Assert.That(lines, Does.Contain("class EntityBase(DeclarativeBase):"));
Assert.That(lines, Does.Contain("\tpass"));
});
}
[Test]
public async Task Creates_db__settings_py()
{
var bytes = await Container.ReadFileFromRootAsync("db/settings.py");
var text = Encoding.UTF8.GetString(bytes);
var lines = text.Split('\n');
// TestContext.WriteLine(text);
Assert.Multiple(() =>
{
Assert.That(text, Is.Not.Empty);
Assert.That(lines, Does.Contain("class DbSettings:"));
Assert.That(lines, Does.Contain("\tdef get_connectionstring() -> str:"));
Assert.That(lines, Does.Contain("\t\tconnectionstring = \"mysql+asyncmy://root:password@localhost:5050/todo\""));
Assert.That(lines, Does.Contain("\t\treturn connectionstring"));
});
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="Testcontainers" Version="3.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MycroForge.Core\MycroForge.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -97,14 +97,14 @@ public class CrudRouterGenerator
var entityRoutePrefix = fqn.PascalizedName.Kebaberize().Pluralize().ToLower();
var serviceFilePath = Path.Join(
Features.Api.FeatureName, "services", fqn.FolderPath, $"{fqn.SnakeCasedName}_service"
Features.Api.FeatureName, "services", fqn.Namespace, $"{fqn.SnakeCasedName}_service"
);
var serviceImportPath = serviceFilePath.SlashesToDots();
var routerFolderPath = Path.Join(Features.Api.FeatureName, "routers", fqn.FolderPath);
var routerFolderPath = Path.Join(Features.Api.FeatureName, "routers", fqn.Namespace);
var routerFilePath = Path.Join(routerFolderPath, $"{fqn.SnakeCasedName}");
var routerImportPath = routerFolderPath.SlashesToDots();
var requestsFolderPath = Path.Join(Features.Api.FeatureName, "requests", fqn.FolderPath);
var requestsFolderPath = Path.Join(Features.Api.FeatureName, "requests", fqn.Namespace);
var createRequestImportPath = Path.Join(requestsFolderPath, $"Create{fqn.PascalizedName}Request")
.SlashesToDots()

View File

@@ -96,14 +96,18 @@ public partial class EntityLinker
var left = await LoadEntity(_left);
var right = await LoadEntity(_right);
var associationTable = string.Join('\n', AssociationTable);
associationTable = associationTable
var associationTable = string.Join('\n', AssociationTable)
.Replace("%left_entity%", left.ClassName.Underscore().ToLower())
.Replace("%right_entity%", right.ClassName.Underscore().ToLower())
.Replace("%left_table%", left.TableName)
.Replace("%right_table%", right.TableName);
var associationTablePath =
$"{Features.Db.FeatureName}/entities/associations/{left.TableName.Singularize()}_{right.TableName.Singularize()}_mapping.py";
var associationTablePath = Path.Join(
Features.Db.FeatureName,
"entities",
"associations",
$"{left.TableName.Singularize()}_{right.TableName.Singularize()}_mapping.py"
);
await _context.CreateFile(associationTablePath, associationTable);
@@ -136,21 +140,25 @@ public partial class EntityLinker
var env = await _context.ReadFile($"{Features.Db.FeatureName}/env.py");
env = new DbEnvModifier(env, associationTableImportPath, associationTableImportName).Rewrite();
await _context.WriteFile($"{Features.Db.FeatureName}/env.py", env);
var main = await _context.ReadFile("main.py");
main = new MainModifier(main).Initialize().Import(associationTableImportPath, associationTableImportName).Rewrite();
main = new MainModifier(main)
.Initialize()
.Import(associationTableImportPath, associationTableImportName)
.Rewrite();
await _context.WriteFile("main.py", main);
}
private async Task<EntityModel> LoadEntity(string name)
{
var fqn = new FullyQualifiedName(name);
var path = $"{Features.Db.FeatureName}/entities";
var path = Path.Join(Features.Db.FeatureName, "entities");
if (fqn.HasPath)
path = Path.Combine(path, fqn.FolderPath);
if (fqn.HasNamespace)
path = Path.Join(path, fqn.Namespace);
path = Path.Combine(path, $"{fqn.SnakeCasedName}.py");
path = Path.Join(path, $"{fqn.SnakeCasedName}.py");
var entity = new EntityModel(fqn.PascalizedName, path, await _context.ReadFile(path));
entity.Initialize();
return entity;

View File

@@ -7,14 +7,16 @@ namespace MycroForge.CLI.CodeGen;
public class RequestClassGenerator
{
public record Import(string Name, List<string> Types)
private static readonly List<string> PythonTypingImports = ["Any", "Dict", "List", "Optional"];
private record Import(string Name, List<string> Types)
{
public bool Match(string type) => Types.Any(t => type == t || type.StartsWith(t));
public string FindType(string type) => Types.First(t => type == t || type.StartsWith(t));
};
public record Field(string Name, string Type);
private record Field(string Name, string Type);
public enum Type
{
@@ -47,12 +49,11 @@ public class RequestClassGenerator
var entitySource = await _context.ReadFile(entityFilePath);
var fieldInfo = ReadFields(entitySource);
var fields = string.Join('\n', fieldInfo.Select(x => ToFieldString(x, type)));
var requestFilePath = Path.Join(
Features.Api.FeatureName,
"requests",
fqn.FolderPath,
// requestsFolderPath,
fqn.Namespace,
$"{type.ToString().ToLower()}_{fqn.SnakeCasedName}_request.py"
);
@@ -60,7 +61,6 @@ public class RequestClassGenerator
.Replace("%imports%", GetImportString(entitySource, fieldInfo, type))
.Replace("%request_type%", type.ToString().Pascalize())
.Replace("%entity_class_name%", fqn.PascalizedName)
// .Replace("%entity_class_name%", entityClassName)
.Replace("%fields%", fields)
;
@@ -102,10 +102,11 @@ public class RequestClassGenerator
.Replace(" ", "");
Console.WriteLine(str); // = "List,Dict,str,Any"
*/
var dissectedTypes = field.Type.Replace("[", ",")
var dissectedTypes = field.Type
.Replace("[", ",")
.Replace("]", "")
.Replace(" ", "")
.Split();
.Split(',');
foreach (var dissectedType in dissectedTypes)
{
@@ -166,16 +167,16 @@ public class RequestClassGenerator
.Split(',')
.Select(s => s.Trim())
.ToArray();
imports.Add(new Import(name, [..types]));
imports.Add(new Import(name, new List<string>(types)));
}
if (imports.FirstOrDefault(i => i.Name == "typing") is Import typingImport)
{
typingImport.Types.AddRange(["Any", "Dict", "List", "Optional"]);
typingImport.Types.AddRange(PythonTypingImports);
}
else
{
imports.Add(new("typing", ["Any", "Dict", "List", "Optional"]));
imports.Add(new Import("typing", PythonTypingImports));
}
return imports;

View File

@@ -0,0 +1,17 @@
namespace MycroForge.CLI.Commands.Attributes;
public class RequiresFeatureAttribute : Attribute
{
public string FeatureName { get; }
public RequiresFeatureAttribute(string featureName)
{
FeatureName = featureName;
}
public void RequireFeature(string command)
{
if (!Directory.Exists(Path.Join(Environment.CurrentDirectory, FeatureName)))
throw new($"Command '{command}' requires feature {FeatureName}");
}
}

View File

@@ -0,0 +1,17 @@
namespace MycroForge.CLI.Commands.Attributes;
public class RequiresFileAttribute : Attribute
{
public string FilePath { get; }
public RequiresFileAttribute(string filePath)
{
FilePath = filePath;
}
public void RequireFile(string command)
{
if (!File.Exists(Path.Join(Environment.CurrentDirectory, FilePath)))
throw new($"Command '{command}' requires file {FilePath}");
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
namespace MycroForge.CLI.Commands.Attributes;
public class RequiresPluginAttribute : Attribute
{
public void RequirePluginProject(string command)
{
var currentDirectoryInfo = new DirectoryInfo(Environment.CurrentDirectory);
var matcher = new Matcher()
.AddInclude("*.csproj")
.Execute(new DirectoryInfoWrapper(currentDirectoryInfo));
if (!matcher.HasMatches)
throw new($"Command '{command}' must be run in a command plugin project.");
var csprojFileName = $"{new DirectoryInfo(Environment.CurrentDirectory).Name}.csproj";
bool IsCsprojFile(FilePatternMatch file)
{
return Path.GetFileName(file.Path) == csprojFileName;
}
var hasCsprojFile = matcher.Files.Any(IsCsprojFile);
if (!hasCsprojFile)
throw new($"File '{csprojFileName}' was not found, make sure you're in a command plugin project.");
}
}

View File

@@ -0,0 +1,12 @@
namespace MycroForge.CLI.Commands.Attributes;
public class RequiresVenvAttribute : Attribute
{
public string Path => System.IO.Path.Join(Environment.CurrentDirectory, ".venv");
public void RequireVenv(string command)
{
if (!File.Exists(System.IO.Path.Join(Environment.CurrentDirectory, Path)))
throw new($"Command '{command}' requires directory {Path}");
}
}

View File

@@ -5,16 +5,16 @@ namespace MycroForge.CLI.Commands;
public class FullyQualifiedName
{
public string FolderPath { get; }
public string Namespace { get; }
public string PascalizedName { get; }
public string SnakeCasedName { get; }
public string FilePath =>
string.IsNullOrEmpty(FolderPath.Trim())
string.IsNullOrEmpty(Namespace.Trim())
? SnakeCasedName
: Path.Join(FolderPath, SnakeCasedName);
: Path.Join(Namespace, SnakeCasedName);
public bool HasPath => FolderPath.Length > 0;
public bool HasNamespace => Namespace.Length > 0;
public FullyQualifiedName(string name)
@@ -27,7 +27,7 @@ public class FullyQualifiedName
name = fullName[1];
}
FolderPath = path;
Namespace = path;
PascalizedName = name.Pascalize();
SnakeCasedName = SnakeCase(name);
}

View File

@@ -2,7 +2,6 @@ using System.CommandLine;
using Humanizer;
using MycroForge.CLI.CodeGen;
using MycroForge.Core.Contract;
using MycroForge.CLI.Extensions;
using MycroForge.Core;
namespace MycroForge.CLI.Commands;
@@ -44,19 +43,17 @@ public partial class MycroForge
private async Task ExecuteAsync(string name)
{
var fqn = new FullyQualifiedName(name);
var folderPath = $"{Features.Api.FeatureName}/routers";
var routersFolderPath = Path.Join(Features.Api.FeatureName, "routers");
_context.AssertDirectoryExists(folderPath);
if (fqn.HasNamespace)
routersFolderPath = Path.Join(routersFolderPath, fqn.Namespace);
if (fqn.HasPath)
folderPath = Path.Combine(folderPath, fqn.FolderPath);
var fileName = $"{fqn.SnakeCasedName}.py";
var filePath = Path.Combine(folderPath, fileName);
var filePath = Path.Join(routersFolderPath, fileName);
await _context.CreateFile(filePath, Template);
var moduleImportPath = folderPath
var moduleImportPath = routersFolderPath
.Replace('\\', '.')
.Replace('/', '.');

View File

@@ -1,13 +1,15 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core.Contract;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
[RequiresFeature(Features.Api.FeatureName)]
public partial class Api : Command, ISubCommandOf<MycroForge>
{
public Api(IEnumerable<ISubCommandOf<Api>> commands) :
public Api(IEnumerable<ISubCommandOf<Api>> commands) :
base("api", "API related commands")
{
foreach (var command in commands)

View File

@@ -1,8 +1,8 @@
using System.CommandLine;
using System.Text.RegularExpressions;
using Humanizer;
using MycroForge.CLI.CodeGen;
using MycroForge.Core.Contract;
using MycroForge.CLI.Extensions;
using MycroForge.Core;
namespace MycroForge.CLI.Commands;
@@ -15,11 +15,65 @@ public partial class MycroForge
{
public class Entity : Command, ISubCommandOf<Generate>
{
private record ColumnDefinition(string Name, string NativeType, string OrmType);
#region Hidden region
private static string[] SqlAlchemyTypes =
[
"BigInteger",
"Boolean",
"Date",
"DateTime",
"Enum",
"Double",
"Float",
"Integer",
"Interval",
"LargeBinary",
"MatchType",
"Numeric",
"PickleType",
"SchemaType",
"SmallInteger",
"String",
"Text",
"Time",
"Unicode",
"UnicodeText",
"Uuid",
"ARRAY",
"BIGINT",
"BINARY",
"BLOB",
"BOOLEAN",
"CHAR",
"CLOB",
"DATE",
"DATETIME",
"DECIMAL",
"DOUBLE",
"DOUBLE_PRECISION",
"FLOAT",
"INT",
"JSON",
"INTEGER",
"NCHAR",
"NVARCHAR",
"NUMERIC",
"REAL",
"SMALLINT",
"TEXT",
"TIME",
"TIMESTAMP",
"UUID",
"VARBINARY",
"VARCHAR"
];
private static readonly Regex SqlAlchemyTypeRegex = new(@".*\(.*\)");
private static readonly string[] Template =
[
"from sqlalchemy import %type_imports%",
"from sqlalchemy import %sqlalchemy_imports%",
"from sqlalchemy.orm import Mapped, mapped_column",
$"from {Features.Db.FeatureName}.entities.entity_base import EntityBase",
"",
@@ -56,6 +110,10 @@ public partial class MycroForge
"\tfirst_name:str:String(255)",
])) { AllowMultipleArgumentsPerToken = true };
#endregion
private record ColumnDefinition(string Name, string NativeType, string SqlAlchemyType);
private readonly ProjectContext _context;
public Entity(ProjectContext context) : base("entity", "Generate and database entity")
@@ -70,25 +128,27 @@ public partial class MycroForge
private async Task ExecuteAsync(string name, IEnumerable<string> columns)
{
var fqn = new FullyQualifiedName(name);
var folderPath = $"{Features.Db.FeatureName}/entities";
var folderPath = Path.Join(Features.Db.FeatureName, "entities");
_context.AssertDirectoryExists(Features.Db.FeatureName);
if (fqn.HasNamespace)
folderPath = Path.Join(folderPath, fqn.Namespace);
var sqlAlchemyColumn = GetColumnDefinitions(columns.ToArray());
var distinctSqlAlchemyColumnTypes = sqlAlchemyColumn
.Select(c => c.SqlAlchemyType.Split('(').First())
.Distinct();
if (fqn.HasPath)
folderPath = Path.Combine(folderPath, fqn.FolderPath);
var _columns = GetColumnDefinitions(columns.ToArray());
var typeImports = string.Join(", ", _columns.Select(c => c.OrmType.Split('(').First()).Distinct());
var columnDefinitions = string.Join("\n\t", _columns.Select(ColumnToString));
var sqlAlchemyImport = string.Join(", ", distinctSqlAlchemyColumnTypes);
var columnDefinitions = string.Join("\n ", sqlAlchemyColumn.Select(ColumnToString));
var code = string.Join('\n', Template);
code = code.Replace("%type_imports%", typeImports);
code = code.Replace("%sqlalchemy_imports%", sqlAlchemyImport);
code = code.Replace("%class_name%", fqn.PascalizedName);
code = code.Replace("%table_name%", fqn.SnakeCasedName.Pluralize());
code = code.Replace("%column_definitions%", columnDefinitions);
var fileName = $"{fqn.SnakeCasedName}.py";
var filePath = Path.Combine(folderPath, fileName);
var filePath = Path.Join(folderPath, fileName);
await _context.CreateFile(filePath, code);
var importPathParts = new[] { folderPath, fileName.Replace(".py", "") }
@@ -109,25 +169,58 @@ public partial class MycroForge
await _context.WriteFile("main.py", main);
}
private List<ColumnDefinition> GetColumnDefinitions(string[] fields)
private List<ColumnDefinition> GetColumnDefinitions(string[] columns)
{
var definitions = new List<ColumnDefinition>();
foreach (var field in fields)
foreach (var column in columns)
{
if (field.Split(':') is not { Length: 3 } definition)
throw new Exception($"Field definition {field} is invalid.");
if (column.Split(':') is not { Length: 3 } definition)
throw new Exception($"Column definition {column} is invalid.");
definitions.Add(new ColumnDefinition(definition[0], definition[1], definition[2]));
}
ValidateSqlAlchemyColumnTypes(definitions);
return definitions;
}
private static string ColumnToString(ColumnDefinition definition)
private static void ValidateSqlAlchemyColumnTypes(List<ColumnDefinition> definitions)
{
return $"{definition.Name}: Mapped[{definition.NativeType}] = mapped_column({definition.OrmType})";
foreach (var column in definitions)
{
if (!SqlAlchemyTypeRegex.IsMatch(column.SqlAlchemyType))
{
var message = new[]
{
$"SQLAlchemy column definition {column.SqlAlchemyType} was not properly defined.",
"Add parentheses and specify parameters if required, an example is provided below.",
" String(255)",
"",
"Available options are:",
string.Join(Environment.NewLine, SqlAlchemyTypes.Select(type => $" - {type}"))
};
throw new(string.Join(Environment.NewLine, message));
}
var type = column.SqlAlchemyType.Split('(').First();
if (!SqlAlchemyTypes.Contains(type))
{
var message = string.Join(Environment.NewLine, [
$"SQLAlchemy column type '{column.SqlAlchemyType}' is not valid, available options are:",
string.Join(Environment.NewLine, SqlAlchemyTypes.Select(type => $" - {type}"))
]);
throw new(message);
}
}
}
private static string ColumnToString(ColumnDefinition definition) =>
$"{definition.Name}: Mapped[{definition.NativeType}] = mapped_column({definition.SqlAlchemyType})";
}
}
}

View File

@@ -27,8 +27,6 @@ public partial class MycroForge
private async Task ExecuteAsync(string name)
{
_context.AssertDirectoryExists($"{Features.Db.FeatureName}/versions");
await _context.Bash(
"source .venv/bin/activate",
$"alembic revision --autogenerate -m \"{name}\" --rev-id $(date -u +\"%Y%m%d%H%M%S\")"

View File

@@ -1,10 +1,12 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core.Contract;
namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
[RequiresFeature(Features.Db.FeatureName)]
public partial class Db : Command, ISubCommandOf<MycroForge>
{
public Db(IEnumerable<ISubCommandOf<Db>> commands)

View File

@@ -1,7 +1,5 @@
using System.CommandLine;
using Humanizer;
using MycroForge.Core.Contract;
using MycroForge.CLI.Extensions;
using MycroForge.Core;
namespace MycroForge.CLI.Commands;
@@ -60,10 +58,10 @@ public partial class MycroForge
var fqn = new FullyQualifiedName(name);
var folderPath = string.Empty;
if (fqn.HasPath)
folderPath = Path.Combine(folderPath, fqn.FolderPath);
if (fqn.HasNamespace)
folderPath = Path.Join(folderPath, fqn.Namespace);
var filePath = Path.Combine(folderPath, $"{fqn.SnakeCasedName}.py");
var filePath = Path.Join(folderPath, $"{fqn.SnakeCasedName}.py");
var template = withSession ? WithSessionTemplate : DefaultTemplate;
var code = string.Join('\n', template)
.Replace("%class_name%", fqn.PascalizedName);

View File

@@ -1,4 +1,5 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core;
using MycroForge.Core.Contract;
@@ -6,11 +7,12 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
[RequiresFile("requirements.txt")]
public class Hydrate : Command, ISubCommandOf<MycroForge>
{
private readonly ProjectContext _context;
public Hydrate(ProjectContext context)
public Hydrate(ProjectContext context)
: base("hydrate", "Initialize venv and install dependencies from requirements.txt")
{
_context = context;

View File

@@ -81,7 +81,7 @@ public partial class MycroForge
await _context.CreateFile("main.py");
// Create the venv
await _context.Bash($"python3 -m venv {Path.Combine(projectRoot, ".venv")}");
await _context.Bash($"python3 -m venv {Path.Join(projectRoot, ".venv")}");
// Pass feature arguments to the ArgsContainer
_optionsContainer.Set(options.ApiOptions);
@@ -106,7 +106,7 @@ public partial class MycroForge
private async Task<string> CreateDirectory(string name)
{
var directory = Path.Combine(Directory.GetCurrentDirectory(), name);
var directory = Path.Join(Directory.GetCurrentDirectory(), name);
if (Directory.Exists(directory))
throw new Exception($"Directory {directory} already exists.");

View File

@@ -1,4 +1,5 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core;
using MycroForge.Core.Contract;
@@ -6,6 +7,7 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
[RequiresVenv]
public class Install : Command, ISubCommandOf<MycroForge>
{
private static readonly Argument<IEnumerable<string>> PackagesArgument =
@@ -28,7 +30,7 @@ public partial class MycroForge
if (packs.Length == 0)
{
Console.WriteLine("m4g install requires at least one package.");
Console.WriteLine("'m4g install' requires at least one package.");
return;
}

View File

@@ -10,21 +10,29 @@ public partial class MycroForge
{
public class Init : Command, ISubCommandOf<Plugin>
{
private static readonly Argument<string> NameArgument =
new(name: "name", description: "The name of your project");
private static readonly Argument<string> NamespaceArgument =
new(name: "namespace", description: "The namespace of your project");
private static readonly Argument<string> ClassArgument =
new(name: "class", description: "The class name of the generated command");
private static readonly Argument<string> CommandArgument =
new(name: "command", description: "The command name that will be added to 'm4g'");
private readonly ProjectContext _context;
public Init(ProjectContext context) : base("init", "Initialize a basic plugin project")
{
_context = context;
AddArgument(NameArgument);
this.SetHandler(ExecuteAsync, NameArgument);
AddArgument(NamespaceArgument);
AddArgument(ClassArgument);
AddArgument(CommandArgument);
this.SetHandler(ExecuteAsync, NamespaceArgument, ClassArgument, CommandArgument);
}
private async Task ExecuteAsync(string name)
private async Task ExecuteAsync(string @namespace, string @class, string command)
{
await _context.Bash($"dotnet new m4gp -n {name}");
await _context.Bash($"dotnet new m4gp -n {@namespace} --class {@class} --command {command}");
}
}
}

View File

@@ -2,6 +2,7 @@
using Humanizer;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core;
using MycroForge.Core.Contract;
@@ -11,6 +12,7 @@ public partial class MycroForge
{
public partial class Plugin
{
[RequiresPlugin]
public class Install : Command, ISubCommandOf<Plugin>
{
public enum TargetPlatform
@@ -42,9 +44,13 @@ public partial class MycroForge
var assemblyName = GetAssemblyName();
var pluginInstallPath = Path.Join(Plugins.RootDirectory, assemblyName);
var platform = target.ToString().Dasherize();
await _context.Bash($"dotnet publish -c Release -r {platform} --output {pluginInstallPath}");
Console.WriteLine($"Successfully installed plugin {assemblyName}");
var exitCode = await _context.Bash(
$"dotnet publish -c Release -r {platform} --output {pluginInstallPath}"
);
Console.WriteLine(exitCode == 0
? $"Successfully installed plugin {assemblyName}"
: $"Could not install {assemblyName}, process exited with code {exitCode}.");
}
private string GetAssemblyName()

View File

@@ -1,4 +1,5 @@
using System.CommandLine;
using MycroForge.CLI.Commands.Attributes;
using MycroForge.Core;
using MycroForge.Core.Contract;
@@ -6,6 +7,7 @@ namespace MycroForge.CLI.Commands;
public partial class MycroForge
{
[RequiresVenv]
public class Uninstall : Command, ISubCommandOf<MycroForge>
{
private static readonly Argument<IEnumerable<string>> PackagesArgument =
@@ -27,9 +29,17 @@ public partial class MycroForge
private async Task ExecuteAsync(IEnumerable<string> packages, bool yes)
{
var packs = packages.ToArray();
if (packs.Length == 0)
{
Console.WriteLine("'m4g uninstall' requires at least one package.");
return;
}
await _context.Bash(
"source .venv/bin/activate",
$"pip uninstall{(yes ? " --yes " : " ")}{string.Join(' ', packages)}",
$"pip uninstall{(yes ? " --yes " : " ")}{string.Join(' ', packs)}",
"pip freeze > requirements.txt"
);
}

View File

@@ -0,0 +1,96 @@
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Reflection;
using MycroForge.CLI.Commands.Attributes;
namespace MycroForge.CLI.Extensions;
public static class CommandExtensions
{
public static async Task ExecuteAsync(this Commands.MycroForge rootCommand, string[] args)
{
var parser = new CommandLineBuilder(rootCommand)
.AddMiddleware()
.UseDefaults()
.UseExceptionHandler((ex, ctx) =>
{
/*
* Use a custom ExceptionHandler to prevent the System.CommandLine library from printing the StackTrace by default.
*/
Console.WriteLine(ex.Message);
/*
* Set the exit code to a non-zero value to indicate to the shell that the process has failed.
*/
Environment.ExitCode = -1;
})
.Build();
await parser.InvokeAsync(args);
}
private static CommandLineBuilder AddMiddleware(this CommandLineBuilder builder)
{
builder.AddMiddleware(async (context, next) =>
{
var commandChain = context.GetCommandChain();
var commandText = string.Join(' ', commandChain.Select(cmd => cmd.Name));
foreach (var _command in commandChain)
{
if (_command.GetRequiresFeatureAttribute() is RequiresFeatureAttribute requiresFeatureAttribute)
requiresFeatureAttribute.RequireFeature(commandText);
else if (_command.GetRequiresFileAttribute() is RequiresFileAttribute requiresFileAttribute)
requiresFileAttribute.RequireFile(commandText);
else if (_command.GetRequiresPluginAttribute() is RequiresPluginAttribute requiresPluginAttribute)
requiresPluginAttribute.RequirePluginProject(commandText);
else if (_command.GetRequiresVenvAttribute() is RequiresVenvAttribute requiresVenvAttribute)
requiresVenvAttribute.RequireVenv(commandText);
}
await next(context);
});
return builder;
}
private static List<Command> GetCommandChain(this InvocationContext context)
{
var chain = new List<Command>();
/*
* The CommandResult property refers to the last command in the chain.
* So if the command is 'm4g api run' the CommandResult will refer to 'run'.
*/
SymbolResult? cmd = context.ParseResult.CommandResult;
while (cmd is CommandResult result)
{
chain.Add(result.Command);
cmd = cmd.Parent;
}
/*
* Reverse the chain to reflect the actual order of the commands.
*/
chain.Reverse();
return chain;
}
private static RequiresFeatureAttribute? GetRequiresFeatureAttribute(this Command command) =>
command.GetType().GetCustomAttribute<RequiresFeatureAttribute>();
private static RequiresFileAttribute? GetRequiresFileAttribute(this Command command) =>
command.GetType().GetCustomAttribute<RequiresFileAttribute>();
private static RequiresPluginAttribute? GetRequiresPluginAttribute(this Command command) =>
command.GetType().GetCustomAttribute<RequiresPluginAttribute>();
private static RequiresVenvAttribute? GetRequiresVenvAttribute(this Command command) =>
command.GetType().GetCustomAttribute<RequiresVenvAttribute>();
}

View File

@@ -8,7 +8,7 @@ namespace MycroForge.CLI.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterCommandDefaults(this IServiceCollection services)
public static IServiceCollection RegisterDefaultCommands(this IServiceCollection services)
{
// Register ProjectContext, OptionsContainer & features
services.AddScoped<ProjectContext>();

View File

@@ -52,7 +52,7 @@ public sealed partial class Api : IFeature
await context.Bash(
"source .venv/bin/activate",
"python3 -m pip install fastapi",
"python3 -m pip install fastapi uvicorn",
"python3 -m pip freeze > requirements.txt"
);

View File

@@ -1,7 +1,6 @@
using MycroForge.CLI.CodeGen;
using MycroForge.CLI.Commands;
using MycroForge.Core;
using MycroForge.Core.Extensions;
namespace MycroForge.CLI.Features;
@@ -58,8 +57,7 @@ public sealed partial class Db : IFeature
" - '%app_name%_mariadb:/var/lib/mysql'",
"",
" %app_name%_phpmyadmin:",
" image: phpmyadmin/phpmyadmin",
" platform: %dbu_platform%",
" image: %dbu_platform%/phpmyadmin",
" container_name: %app_name%_phpmyadmin",
" ports:",
" - '${DBU_PORT}:80'",
@@ -127,7 +125,7 @@ public sealed partial class Db : IFeature
var dockerCompose = string.Join('\n', DockerCompose)
.Replace("%app_name%", appName)
.Replace("%dbu_platform%", options.DbuPlatform.ToDockerPlatformString())
.Replace("%dbu_platform%", options.DbuPlatform.ToString())
;
await context.CreateFile($"{FeatureName}.docker-compose.yml", dockerCompose);

View File

@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.0.1</Version>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,25 +1,18 @@
using System.CommandLine;
using MycroForge.CLI.Extensions;
using MycroForge.CLI.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using RootCommand = MycroForge.CLI.Commands.MycroForge;
using var host = Host
.CreateDefaultBuilder()
.ConfigureServices((_, services) =>
{
services
.RegisterCommandDefaults()
.RegisterDefaultCommands()
.RegisterCommandPlugins()
;
})
.Build();
try
{
await host.Services.GetRequiredService<MycroForge.CLI.Commands.MycroForge>()
.InvokeAsync(args.Length == 0 ? ["--help"] : args);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
var command = host.Services.GetRequiredService<RootCommand>();
await command.ExecuteAsync(args.Length == 0 ? ["--help"] : args);

View File

@@ -1,11 +0,0 @@
#!/usr/bin/bash
TARGET=$1
if [ -z "$TARGET" ]; then
echo "The target platform was not provided."
exit 1
fi
dotnet publish --self-contained -r "$TARGET"
zip -vr "releases/m4g-$TARGET.zip" "bin/Release/net8.0/$TARGET/"

View File

@@ -1,7 +0,0 @@
#!/usr/bin/bash
./scripts/build-executable.sh linux-x64
./scripts/build-executable.sh linux-arm
./scripts/build-executable.sh linux-arm64
./scripts/build-executable.sh osx-x64
./scripts/build-executable.sh osx-arm64

View File

@@ -1,21 +0,0 @@
#!/usr/bin/bash
ZIP=$1
if [ -z "$ZIP" ]; then
echo "The zip file was not provided."
exit 1
fi
TARGET=${ZIP//"m4g-"/}
TARGET=${TARGET//".zip"/}
DIR="/tmp/m4g"
rm -rf "$DIR"
unzip "$ZIP" -d "$DIR"
sudo rm -rf /usr/share/m4g
sudo cp -r "$DIR/bin/Release/net8.0/$TARGET" /usr/share/m4g
sudo unlink /usr/local/bin/m4g 2> /dev/null
sudo ln -s /usr/share/m4g/MycroForge.CLI /usr/local/bin/m4g

View File

@@ -1,19 +0,0 @@
#!/usr/bin/bash
ZIP=$1
if [ -z "$ZIP" ]; then
echo "The zip file was not provided."
exit 1
fi
TARGET=${ZIP//"m4g-"/}
TARGET=${TARGET//".zip"/}
DIR="/tmp/m4g"
rm -rf "$DIR"
unzip "$ZIP" -d "$DIR"
sudo rm -rf /usr/local/bin/m4g
sudo cp -r "$DIR/bin/Release/net8.0/$TARGET" /usr/local/bin/m4g
mv /usr/share/m4g/MycroForge.CLI /usr/share/m4g/m4g

View File

@@ -1,11 +0,0 @@
#!/usr/bin/bash
# Make sure to run this script from the MycroForge.CLI directory and prefixed with sudo!
# Example:
# sudo ./scripts/publish-linux.sh
dotnet publish --self-contained -r linux-x64
sudo rm -rf /usr/share/m4g
sudo cp -r bin/Release/net8.0/linux-x64 /usr/share/m4g
sudo unlink /usr/local/bin/m4g
sudo ln -s /usr/share/m4g/MycroForge.CLI /usr/local/bin/m4g

View File

@@ -2,4 +2,6 @@
dotnet pack -v d
dotnet nuget push nupkg/MycroForge.CLI.1.0.0.nupkg --source devdisciples
VERSION="1.0.0"
dotnet nuget push nupkg/MycroForge.CLI.$VERSION.nupkg --source devdisciples

View File

@@ -1,4 +1,4 @@
#!/usr/bin/bash
#!/bin/bash
dotnet pack -v d

View File

@@ -1,6 +0,0 @@
namespace MycroForge.Core.Attributes;
public class DockerPlatformAttribute : Attribute
{
public string Platform { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,17 @@
using System.Text.Json;
namespace MycroForge.Core;
public static class DefaultJsonSerializerOptions
{
public static readonly JsonSerializerOptions Default = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static readonly JsonSerializerOptions CamelCasePrettyPrint = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}

View File

@@ -1,19 +0,0 @@
using System.Reflection;
using MycroForge.Core.Attributes;
namespace MycroForge.Core.Extensions;
public static class EnumExtensions
{
/// <summary>
/// A generic extension method that aids in reflecting
/// and retrieving any attribute that is applied to an `Enum`.
/// </summary>
public static string ToDockerPlatformString(this ProjectConfig.DbConfig.DbuPlatformOptions value)
{
return value.GetType()
.GetMember(value.ToString())
.FirstOrDefault()!
.GetCustomAttribute<DockerPlatformAttribute>()!.Platform;
}
}

View File

@@ -4,15 +4,12 @@ namespace MycroForge.Core.Extensions;
public static class ObjectStreamExtensions
{
public static async Task<string> SerializeAsync(
this object @object,
JsonSerializerOptions? jsonSerializerOptions = null
)
public static async Task<string> SerializeAsync(this object @object, JsonSerializerOptions? options = null)
{
using var stream = new MemoryStream();
using var reader = new StreamReader(stream);
var options = jsonSerializerOptions ?? Serialization.DefaultJsonSerializerOptions.Default;
options ??= DefaultJsonSerializerOptions.Default;
await JsonSerializer.SerializeAsync(stream, @object, options);
stream.Position = 0;
return await reader.ReadToEndAsync();

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<PackageId>MycroForge.Core</PackageId>
<Description>The MycroForge core package</Description>
<Version>1.0.0</Version>
<Version>0.0.1</Version>
<Authors>Donné Napo</Authors>
<Company>Dev Disciples</Company>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

View File

@@ -1,6 +1,4 @@
using MycroForge.Core.Attributes;
namespace MycroForge.Core;
namespace MycroForge.Core;
public partial class ProjectConfig
{
@@ -8,20 +6,11 @@ public partial class ProjectConfig
{
public enum DbuPlatformOptions
{
[DockerPlatform(Platform = "linux/amd64")]
linux_amd64,
[DockerPlatform(Platform = "linux/arm32/v5")]
linux_arm32v5,
[DockerPlatform(Platform = "linux/arm32/v6")]
linux_arm32v6,
[DockerPlatform(Platform = "linux/arm32/v7")]
linux_arm32v7,
[DockerPlatform(Platform = "linux/arm64/v8")]
linux_arm64v8
amd64,
arm32v5,
arm32v6,
arm32v7,
arm64v8
}
}
}

View File

@@ -9,7 +9,7 @@ public class ProjectContext
{
public string RootDirectory { get; private set; } = Environment.CurrentDirectory;
public string AppName => Path.GetFileNameWithoutExtension(RootDirectory).Underscore().ToLower();
private string ConfigPath => Path.Combine(RootDirectory, "m4g.json");
private string ConfigPath => Path.Join(RootDirectory, "m4g.json");
public async Task<ProjectConfig> LoadConfig(bool create = false)
@@ -21,8 +21,8 @@ public class ProjectContext
}
var config = await JsonSerializer.DeserializeAsync<ProjectConfig>(
File.OpenRead(ConfigPath),
Serialization.DefaultJsonSerializerOptions.CamelCasePrettyPrint
File.OpenRead(ConfigPath),
DefaultJsonSerializerOptions.CamelCasePrettyPrint
);
if (config is null)
@@ -37,21 +37,9 @@ public class ProjectContext
RootDirectory = path;
}
public void AssertDirectoryExists(string path)
{
var fullPath = Path.Combine(RootDirectory, path);
if (!Directory.Exists(fullPath))
{
throw new(string.Join('\n',
$"{fullPath} does not exist, make sure you're in the correct directory."
));
}
}
public async Task CreateFile(string path, params string[] content)
{
var fullPath = Path.Combine(RootDirectory, path);
var fullPath = Path.Join(RootDirectory, path);
var fileInfo = new FileInfo(fullPath);
if (fileInfo.Exists) return;
@@ -59,11 +47,12 @@ public class ProjectContext
Directory.CreateDirectory(fileInfo.Directory!.FullName);
await File.WriteAllTextAsync(fullPath, string.Join("\n", content));
await Bash($"chmod 777 {fullPath}");
Console.WriteLine($"Created file {path}");
}
public async Task<string> ReadFile(string path)
{
var fullPath = Path.Combine(RootDirectory, path);
var fullPath = Path.Join(RootDirectory, path);
var fileInfo = new FileInfo(fullPath);
if (!fileInfo.Exists)
@@ -74,13 +63,14 @@ public class ProjectContext
public async Task WriteFile(string path, params string[] content)
{
var fullPath = Path.Combine(RootDirectory, path);
var fullPath = Path.Join(RootDirectory, path);
var fileInfo = new FileInfo(fullPath);
Directory.CreateDirectory(fileInfo.Directory!.FullName);
await File.WriteAllTextAsync(fullPath, string.Join("\n", content));
Console.WriteLine($"Modified file {path}");
}
public async Task Bash(params string[] script)
public async Task<int> Bash(params string[] script)
{
var info = new ProcessStartInfo
{
@@ -114,21 +104,21 @@ public class ProjectContext
process.BeginErrorReadLine();
await using var input = process.StandardInput;
foreach (var line in script)
await input.WriteLineAsync(line);
// Concat with '&&' operator to make sure that script does not continue on failure.
await input.WriteAsync(string.Join(" && ", script));
await input.FlushAsync();
input.Close();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
Console.WriteLine($"Process finished with exit code {process.ExitCode}.");
Environment.ExitCode = process.ExitCode;
return process.ExitCode;
}
public async Task SaveConfig(ProjectConfig config)
{
var json = await config.SerializeAsync(Serialization.DefaultJsonSerializerOptions.CamelCasePrettyPrint);
var json = await config.SerializeAsync(DefaultJsonSerializerOptions.CamelCasePrettyPrint);
await File.WriteAllTextAsync(ConfigPath, json);
}
}

View File

@@ -1,20 +0,0 @@
using System.Text.Json;
namespace MycroForge.Core;
public static class Serialization
{
public static class DefaultJsonSerializerOptions
{
public static readonly JsonSerializerOptions Default = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static readonly JsonSerializerOptions CamelCasePrettyPrint = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
}

View File

@@ -0,0 +1,5 @@
#!/bin/bash
VERSION=$(grep '<Version>' < MycroForge.Core.csproj | sed 's/.*<Version>\(.*\)<\/Version>/\1/' | tr -d '[:space:]')
dotnet build -r Releasedo
dotnet nuget push --source devdisciples "bin/Release/MycroForge.Core.$VERSION.nupkg"

View File

@@ -4,7 +4,7 @@
<!-- 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>
<PackageVersion>0.0.1</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>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/env bash
rm -rf templates/MycroForge.PluginTemplate
cp -R ../MycroForge.PluginTemplate templates/MycroForge.PluginTemplate

View File

@@ -0,0 +1,12 @@
#!/bin/env bash
rm -rf templates/MycroForge.PluginTemplate
cp -R ../MycroForge.PluginTemplate templates/MycroForge.PluginTemplate
dotnet pack
# Set the path to the package
VERSION=$(grep '<PackageVersion>' < MycroForge.PluginTemplate.Package.csproj | sed 's/.*<PackageVersion>\(.*\)<\/PackageVersion>/\1/' | tr -d '[:space:]')
PACKAGE="bin/Release/MycroForge.PluginTemplate.Package.$VERSION.nupkg"
# Push the package
dotnet nuget push "$PACKAGE" --source devdisciples

View File

@@ -17,5 +17,16 @@
"language": "C#",
"type": "project"
},
"preferNameDirectory": true
"preferNameDirectory": true,
"symbols": {
"class": {
"type": "parameter",
"replaces": "ExampleCommand",
"fileRename": "ExampleCommand"
},
"command": {
"type": "parameter",
"replaces": "example"
}
}
}

View File

@@ -5,7 +5,7 @@ using RootCommand = MycroForge.Core.RootCommand;
namespace MycroForge.PluginTemplate;
public class HelloWorldCommand : Command, ISubCommandOf<RootCommand>
public class ExampleCommand : Command, ISubCommandOf<RootCommand>
{
private readonly Argument<string> NameArgument =
new(name: "name", description: "The name of the person to greet");
@@ -15,8 +15,8 @@ public class HelloWorldCommand : Command, ISubCommandOf<RootCommand>
private readonly ProjectContext _context;
public HelloWorldCommand(ProjectContext context) :
base("hello", "An example command generated by dotnet new using the m4gp template")
public ExampleCommand(ProjectContext context) :
base("example", "A basic command plugin generated by the 'm4g plugin init' command")
{
_context = context;
AddArgument(NameArgument);
@@ -28,9 +28,9 @@ public class HelloWorldCommand : Command, ISubCommandOf<RootCommand>
{
name = allCaps ? name.ToUpper() : name;
await _context.CreateFile("hello_world.txt",
await _context.CreateFile("example.txt",
$"Hello {name}!",
"This file was generated by your custom command!"
"This file was generated by the 'm4g example' command!"
);
}
}

View File

@@ -4,12 +4,12 @@ using RootCommand = MycroForge.Core.RootCommand;
namespace MycroForge.PluginTemplate;
public class HelloWorldCommandPlugin : ICommandPlugin
public class ExampleCommandPlugin : ICommandPlugin
{
public string Name => "MycroForge.PluginTemplate";
public void RegisterServices(IServiceCollection services)
{
services.AddScoped<ISubCommandOf<RootCommand>, HelloWorldCommand>();
services.AddScoped<ISubCommandOf<RootCommand>, ExampleCommand>();
}
}

View File

@@ -11,7 +11,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MycroForge.Core" Version="1.0.0" />
<PackageReference Include="MycroForge.Core" Version="0.0.1" />
</ItemGroup>
</Project>

View File

@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.PluginTemplate.Package", "MycroForge.PluginTemplate.Package\MycroForge.PluginTemplate.Package.csproj", "{1C5C5B9A-3C90-4FE7-A1AC-2F46C3CD0D69}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MycroForge.CLI.Tests", "MycroForge.CLI.Tests\MycroForge.CLI.Tests.csproj", "{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +32,9 @@ Global
{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
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71A7EA9D-3C12-4FDE-BA4F-BDD1961DDA1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,38 +1,3 @@
### Dependencies
### Documentation
- git
- Docker
- bash (/bin/bash)
- Python 3.10.2 (/usr/bin/python3)
- python3-pip
- python3-venv
#### Note
The MycroForge CLI assumes a linux compatible environment, so on Windows you'll have to use WSL.
`Ubuntu-22.04` is the recommended WSL version to use.
### TODO
- Figure out why BashException cannot be caught, can it be due to the differences in scoping?
Because the `Bash` class is static and the services calling `Bash.ExecuteAsync` are in the container.
Maybe this in combination with the async nature of the whole thing?
### Install
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
```
### TODO
- Fix `-c` option for `m4g db generate entity`
- Mention `--dbu-platform` option for `m4g init`
- Research if System.CommandLine middleware can be used to safeguard commands like `m4g add` or `m4g api`.
- Fix up exception handling
- Clean up README files
-
Go to https://m4g.devdisciples.com for the docs.

View File

@@ -1,3 +0,0 @@
.docusaurus/
node_modules/
.k8s/

23
docs/.gitignore vendored
View File

@@ -1,23 +0,0 @@
# 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*
.k8s/remote*.yml
!.k8s/remote.example.yml

View File

@@ -1,61 +0,0 @@
# =========================================
# App manifest
# =========================================
---
# App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: m4g-docs-deployment
spec:
replicas: 1
selector:
matchLabels:
app: m4g-docs
template:
metadata:
labels:
app: m4g-docs
spec:
containers:
- name: m4g-docs
image: m4gdocs:latest
imagePullPolicy: Never
ports:
- containerPort: 80
---
# App Service
apiVersion: v1
kind: Service
metadata:
name: m4g-docs-service
spec:
selector:
app: m4g-docs
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort
---
# App Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: m4g-docs-ingress
spec:
ingressClassName: caddy
rules:
- host: m4g.docs.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: m4g-docs-service
port:
number: 80

View File

@@ -1,67 +0,0 @@
# =========================================
# App manifest
# =========================================
---
# App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: m4g-docs-deployment
spec:
replicas: 1
selector:
matchLabels:
app: m4g-docs
template:
metadata:
labels:
app: m4g-docs
spec:
containers:
- name: m4g-docs
image: git.devdisciples.com/devdisciples/m4gdocs:latest
imagePullPolicy: Always
ports:
- containerPort: 80
---
# App Service
apiVersion: v1
kind: Service
metadata:
name: m4g-docs-service
spec:
selector:
app: m4g-docs
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort
---
# App Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: m4g-docs-ingress
annotations:
cert-manager.io/cluster-issuer: lets-encrypt
spec:
ingressClassName: public
tls:
- hosts:
- m4g.example.com
secretName: example-tls-secret
rules:
- host: m4g.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: m4g-docs-service
port:
number: 80

View File

@@ -1,27 +0,0 @@
# syntax=docker/dockerfile:1
# Stage 1: Base image.
## Start with a base image containing NodeJS so we can build Docusaurus.
FROM node:lts AS base
## Disable colour output from yarn to make logs easier to read.
ENV FORCE_COLOR=0
## Enable corepack.
RUN corepack enable
## Set the working directory to `/opt/docusaurus`.
WORKDIR /opt/docusaurus
# Stage 2b: Production build mode.
FROM base AS prod
## Set the working directory to `/opt/docusaurus`.
WORKDIR /opt/docusaurus
## Copy over the source code.
COPY . /opt/docusaurus/
## Install dependencies with `--immutable` to ensure reproducibility.
RUN npm ci
## Build the static site.
RUN npm run build
## Use a stable nginx image
FROM nginx:stable-alpine AS deploy
WORKDIR /home/node/app
COPY --chown=node:node --from=prod /opt/docusaurus/build/ /usr/share/nginx/html/

View File

@@ -1,47 +0,0 @@
# 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.
### Custom
kubectl --kubeconfig ~/.kube/main.k8s.config apply -f .k8s/remote.yml

View File

@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTest.Plugin", "MyTest.Plugin\MyTest.Plugin.csproj", "{C93CD889-7228-4DA2-B0E2-5273F2FAAFE6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C93CD889-7228-4DA2-B0E2-5273F2FAAFE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C93CD889-7228-4DA2-B0E2-5273F2FAAFE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C93CD889-7228-4DA2-B0E2-5273F2FAAFE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C93CD889-7228-4DA2-B0E2-5273F2FAAFE6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {241D0F32-CE9B-40CA-BEA2-A2554CA22824}
EndGlobalSection
EndGlobal

View File

@@ -1,28 +0,0 @@
---
sidebar_position: 5
---
# 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
```

View File

@@ -1,22 +0,0 @@
---
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
```

View File

@@ -1,17 +0,0 @@
---
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
```

View File

@@ -1,19 +0,0 @@
---
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
--database-ui-platform, --dbu-platform <linux_amd64|linux_arm32v5|linux_arm32v6|linux_arm32v7|linux_arm64v8> The docker platform for the PhpMyAdmin image
-?, -h, --help Show help and usage information
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,20 +0,0 @@
---
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
```

View File

@@ -1,20 +0,0 @@
---
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
```

View File

@@ -1,19 +0,0 @@
---
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
```

View File

@@ -1,19 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
sidebar_position: 1
---
# m4g api run
```
Description:
Run your app
Usage:
m4g api run [options]
Options:
-?, -h, --help Show help and usage information
```

View File

@@ -1,24 +0,0 @@
---
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
```

View File

@@ -1,20 +0,0 @@
---
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
```

View File

@@ -1,35 +0,0 @@
---
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
```

View File

@@ -1,19 +0,0 @@
---
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
```

View File

@@ -1,21 +0,0 @@
---
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
```

View File

@@ -1,21 +0,0 @@
---
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
```

View File

@@ -1,21 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
sidebar_position: 1
---
# m4g db rollback
```
Description:
Rollback the last migration
Usage:
m4g db rollback [options]
Options:
-?, -h, --help Show help and usage information
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,20 +0,0 @@
---
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
```

View File

@@ -1,20 +0,0 @@
---
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
```

View File

@@ -1,16 +0,0 @@
---
sidebar_position: 1
---
# m4g generate venv
```
Description:
Generate a venv
Usage:
m4g generate venv [options]
Options:
-?, -h, --help Show help and usage information
```

View File

@@ -1,16 +0,0 @@
---
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
```

View File

@@ -1,24 +0,0 @@
---
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
--database-ui-platform, --dbu-platform <linux_amd64|linux_arm32v5|linux_arm32v6|linux_arm32v7|linux_arm64v8> The docker platform for the PhpMyAdmin image
-?, -h, --help Show help and usage information
```

View File

@@ -1,19 +0,0 @@
---
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
```

View File

@@ -1,22 +0,0 @@
---
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
```

View File

@@ -1,19 +0,0 @@
---
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
```

View File

@@ -1,17 +0,0 @@
---
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
```

Some files were not shown because too many files have changed in this diff Show More