Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
71c8105
Add endpoint that serves Vue frontend
mslaursen Jul 24, 2025
2dba5e1
Initial Stepper component work
mslaursen Jul 24, 2025
0b8cc4d
Layout
mslaursen Jul 26, 2025
be22cd1
StepOne complete
mslaursen Jul 27, 2025
e526335
StepTwo complete
mslaursen Jul 27, 2025
4553411
Editable Nodes
mslaursen Jul 31, 2025
a544656
Rename option
mslaursen Aug 2, 2025
61f48bb
Ens
mslaursen Aug 2, 2025
df7913d
update(Frontend v2): Correction of vue-flow-viewport & step-indicator…
bigbadens Aug 2, 2025
553ab30
update(Frontend v2): Introduced previous button and corrected minor i…
bigbadens Aug 2, 2025
8f5c518
update(Frontend v2): Changed the font and minor corrections (#95)
bigbadens Aug 2, 2025
1a7e746
update(Frontend V2): Change layout of viewport (#96)
mslaursen Aug 2, 2025
f064862
update(Frontend V2): Rename store.ts > projectStore.ts (#97)
mslaursen Aug 2, 2025
6f5b1f2
feat(Frontend V2): Add global colors (#98)
mslaursen Aug 3, 2025
1b70250
feat(Frontend v2): Viewport Background (#99)
mslaursen Aug 3, 2025
8ec508c
feat(Frontend v2): EnumEditor (#100)
mslaursen Aug 4, 2025
a63fa15
update(Frontend v2): Modal stylings (#101)
mslaursen Aug 4, 2025
c9ef5d9
update(Frontend v2): EditRelationModal (#102)
mslaursen Aug 4, 2025
f1d4fb6
update(Frontend v2): Finished Enums (#103)
mslaursen Aug 7, 2025
0856793
update(Frontend v2): Set metadata on timestamp fields (#104)
mslaursen Aug 8, 2025
c601add
update(Frontend v2): Input validation + Toasts (#105)
mslaursen Aug 9, 2025
5e0c8e0
update(Frontend v2): Delete old UI (#106)
mslaursen Aug 9, 2025
1b1b0a6
feat(Frontend v2): Generation + Feature selection steps
mslaursen Aug 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions .github/workflows/cli_test.yaml
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
name: Test CLI Generated Project
# name: Test CLI Generated Project

on: [push]
# on: [push]

jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17.4-bookworm
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: postgres
ports:
- 5432
options: >-
--health-cmd "pg_isready"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: "Set up Python"
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install the project
run: uv sync --all-extras --dev
- name: Generate example project
run: uv run -m fastapi_forge start --use-example --no-ui --yes
- name: Run tests
working-directory: ./game_zone
env:
GAME_ZONE_PG_HOST: localhost
GAME_ZONE_PG_PORT: ${{ job.services.postgres.ports['5432'] }}
GAME_ZONE_PG_USER: postgres
GAME_ZONE_PG_PASSWORD: postgres
GAME_ZONE_PG_DATABASE: postgres
run: uv run pytest ./tests -v -s
# jobs:
# test:
# runs-on: ubuntu-latest
# services:
# postgres:
# image: postgres:17.4-bookworm
# env:
# POSTGRES_PASSWORD: postgres
# POSTGRES_USER: postgres
# POSTGRES_DB: postgres
# ports:
# - 5432
# options: >-
# --health-cmd "pg_isready"
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# steps:
# - uses: actions/checkout@v4
# - name: Install uv
# uses: astral-sh/setup-uv@v5
# - name: "Set up Python"
# uses: actions/setup-python@v5
# with:
# python-version-file: "pyproject.toml"
# - name: Install the project
# run: uv sync --all-extras --dev
# - name: Generate example project
# run: uv run -m fastapi_forge start --use-example --no-ui --yes
# - name: Run tests
# working-directory: ./game_zone
# env:
# GAME_ZONE_PG_HOST: localhost
# GAME_ZONE_PG_PORT: ${{ job.services.postgres.ports['5432'] }}
# GAME_ZONE_PG_USER: postgres
# GAME_ZONE_PG_PASSWORD: postgres
# GAME_ZONE_PG_DATABASE: postgres
# run: uv run pytest ./tests -v -s
132 changes: 1 addition & 131 deletions fastapi_forge/__main__.py
Original file line number Diff line number Diff line change
@@ -1,134 +1,4 @@
import sys
from pathlib import Path

import click

from fastapi_forge.frontend.main import init
from fastapi_forge.project_io import (
YamlProjectLoader,
create_postgres_project_loader,
)


def confirm_uv_installed() -> bool:
"""Show UV requirement warning and get confirmation."""
click.secho(
"\n⚠️ Important Requirement (use the '--yes' option to skip)",
fg="yellow",
bold=True,
)
click.echo("Generated projects require UV to be installed.")
click.secho(
"GitHub: https://docs.astral.sh/uv/getting-started/installation",
fg="blue",
underline=True,
)

if not click.confirm(
"\nDo you have UV installed and ready to use?",
default=True,
):
click.secho(
"\n❌ Please install UV first and restart this command.",
fg="red",
)
click.echo("Verify with: uv --version")
return False
return True


@click.group(
help="FastAPI Forge CLI - A tool for generating FastAPI projects.",
context_settings={"help_option_names": ["-h", "--help"]},
)
@click.version_option(package_name="fastapi-forge")
@click.option("-v", "--verbose", count=True, help="Increase verbosity level.")
@click.pass_context
def main(ctx: click.Context, verbose: int) -> None:
"""FastAPI Forge CLI entry point."""
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose


@main.command(
help="Start FastAPI Forge - Generate a new FastAPI project.",
)
@click.option(
"--use-example",
is_flag=True,
help="Generate a new project using a prebuilt example provided by FastAPI Forge.",
)
@click.option(
"--no-ui",
is_flag=True,
help="Generate the project directly in the terminal without launching the UI (default: False).",
)
@click.option(
"--dry-run",
is_flag=True,
help="Perform a dry run without generating any files (requires --no-ui).",
)
@click.option(
"--from-yaml",
type=click.Path(
exists=True,
dir_okay=False,
readable=True,
path_type=Path,
),
help="Generate a project using a custom configuration from a YAML file.",
)
@click.option(
"--yes",
is_flag=True,
help="Automatically confirm all prompts (use with caution).",
)
@click.option(
"--conn-string",
help="Generate a project from a PostgreSQL connection string "
"(e.g., postgresql://user:password@host:port/dbname)",
)
@click.pass_context
def start(
_: click.Context,
use_example: bool = False,
no_ui: bool = False,
dry_run: bool = False,
yes: bool = False,
from_yaml: Path | None = None,
conn_string: str | None = None,
) -> None:
"""Start FastAPI Forge."""
if not yes and not confirm_uv_installed():
sys.exit(1)

option_count = sum([use_example, bool(from_yaml), bool(conn_string)])
if option_count > 1:
raise click.UsageError(
"Only one of '--use-example', '--from-yaml', or '--conn-string' can be used."
)

if no_ui and option_count < 1:
raise click.UsageError(
"Option '--no-ui' requires one of '--use-example', '--from-yaml', or '--conn-string'."
)

if dry_run and not no_ui:
raise click.UsageError("Option '--dry-run' requires '--no-ui' to be set.")

project_spec = None

if from_yaml:
project_spec = YamlProjectLoader(project_path=from_yaml).load()
elif conn_string:
project_spec = create_postgres_project_loader(conn_string).load()
elif use_example:
base_path = Path(__file__).parent / "example-projects"
path = base_path / "game_zone.yaml"
project_spec = YamlProjectLoader(project_path=path).load()

init(project_spec=project_spec, no_ui=no_ui, dry_run=dry_run)

from fastapi_forge.cli import main

if __name__ in {"__main__", "__mp_main__"}:
main()
3 changes: 3 additions & 0 deletions fastapi_forge/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = ["start_forge_api"]

from .main import start_forge_api
37 changes: 37 additions & 0 deletions fastapi_forge/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import webbrowser
from pathlib import Path

import uvicorn
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

from fastapi_forge.core.build import build_fastapi_project
from fastapi_forge.schemas import ProjectSpec

app = FastAPI()


def start_forge_api() -> None:
app.mount(
"/",
StaticFiles(
directory=Path(__file__).parent.parent / "static",
html=True,
),
name="frontend",
)

webbrowser.open("http://localhost:8000")
uvicorn.run(app, host="localhost", port=8000)


@app.post("/generate")
async def generate_project(project_spec: ProjectSpec) -> None:
try:
await build_fastapi_project(project_spec, dry_run=False)
except Exception as e:
print(e)


if __name__ == "__main__":
start_forge_api()
3 changes: 3 additions & 0 deletions fastapi_forge/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = ["main"]

from .main import main
130 changes: 130 additions & 0 deletions fastapi_forge/cli/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import click

from fastapi_forge.api import start_forge_api


def confirm_uv_installed() -> bool:
"""Show UV requirement warning and get confirmation."""
click.secho(
"\n⚠️ Important Requirement (use the '--yes' option to skip)",
fg="yellow",
bold=True,
)
click.echo("Generated projects require UV to be installed.")
click.secho(
"GitHub: https://docs.astral.sh/uv/getting-started/installation",
fg="blue",
underline=True,
)

if not click.confirm(
"\nDo you have UV installed and ready to use?",
default=True,
):
click.secho(
"\n❌ Please install UV first and restart this command.",
fg="red",
)
click.echo("Verify with: uv --version")
return False
return True


@click.group(
help="FastAPI Forge CLI - A tool for generating FastAPI projects.",
context_settings={"help_option_names": ["-h", "--help"]},
)
@click.version_option(package_name="fastapi-forge")
@click.option("-v", "--verbose", count=True, help="Increase verbosity level.")
@click.pass_context
def main(ctx: click.Context, verbose: int) -> None:
"""FastAPI Forge CLI entry point."""
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose


@main.command()
def start() -> None:
start_forge_api()


# @main.command(
# help="Start FastAPI Forge - Generate a new FastAPI project.",
# )
# @click.option(
# "--use-example",
# is_flag=True,
# help="Generate a new project using a prebuilt example provided by FastAPI Forge.",
# )
# @click.option(
# "--no-ui",
# is_flag=True,
# help="Generate the project directly in the terminal without launching the UI (default: False).",
# )
# @click.option(
# "--dry-run",
# is_flag=True,
# help="Perform a dry run without generating any files (requires --no-ui).",
# )
# @click.option(
# "--from-yaml",
# type=click.Path(
# exists=True,
# dir_okay=False,
# readable=True,
# path_type=Path,
# ),
# help="Generate a project using a custom configuration from a YAML file.",
# )
# @click.option(
# "--yes",
# is_flag=True,
# help="Automatically confirm all prompts (use with caution).",
# )
# @click.option(
# "--conn-string",
# help="Generate a project from a PostgreSQL connection string "
# "(e.g., postgresql://user:password@host:port/dbname)",
# )
# @click.pass_context
# def start_v1(
# _: click.Context,
# use_example: bool = False,
# no_ui: bool = False,
# dry_run: bool = False,
# yes: bool = False,
# from_yaml: Path | None = None,
# conn_string: str | None = None,
# ) -> None:
# """Start FastAPI Forge."""
# if not yes and not confirm_uv_installed():
# sys.exit(1)

# option_count = sum([use_example, bool(from_yaml), bool(conn_string)])
# if option_count > 1:
# raise click.UsageError(
# "Only one of '--use-example', '--from-yaml', or '--conn-string' can be used."
# )

# if no_ui and option_count < 1:
# raise click.UsageError(
# "Option '--no-ui' requires one of '--use-example', '--from-yaml', or '--conn-string'."
# )

# if dry_run and not no_ui:
# raise click.UsageError("Option '--dry-run' requires '--no-ui' to be set.")

# project_spec = None

# if from_yaml:
# project_spec = YamlProjectLoader(project_path=from_yaml).load()
# elif conn_string:
# project_spec = create_postgres_project_loader(conn_string).load()
# elif use_example:
# base_path = Path(__file__).parent / "example-projects"
# path = base_path / "game_zone.yaml"
# project_spec = YamlProjectLoader(project_path=path).load()


if __name__ in {"__main__", "__mp_main__"}:
main()
Empty file.
Loading