diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..3f72290 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,367 @@ +--- +title: Architecture +description: Internal design of core/php -- FrankenPHP handler, service orchestration, project detection, CI pipeline, deployment, and native bridge. +--- + +# Architecture + +This document explains how the Go code in `forge.lthn.ai/core/php` is structured +and how the major subsystems interact. + + +## Command Registration + +The module exposes two entry points for command registration: + +- **`AddPHPCommands(root)`** -- adds commands under a `php` parent (for the + multi-purpose `core` binary where PHP is one of many command groups). +- **`AddPHPRootCommands(root)`** -- adds commands directly to the root (for the + standalone `core-php` binary where `dev`, `build`, etc. are top-level). + +Both paths register the same set of commands and share workspace-aware +`PersistentPreRunE` logic that detects `.core/workspace.yaml` and `cd`s into the +active package directory before execution. + +The standalone binary is minimal: + +```go +// cmd/core-php/main.go +func main() { + cli.Main( + cli.WithCommands("php", php.AddPHPRootCommands), + ) +} +``` + +### Command Tree + +``` +core-php (or core php) + dev Start all detected services (FrankenPHP, Vite, Horizon, Reverb, Redis) + logs Stream unified or per-service logs + stop Stop all running services + status Show project info and service detection + ssl Setup mkcert SSL certificates + build Build Docker or LinuxKit image + serve Run a production Docker container + shell Open a shell in a running container + ci Run full QA pipeline (test, stan, psalm, fmt, audit, security) + packages + link Add Composer path repositories for local development + unlink Remove path repositories + update [pkgs] Run composer update + list List linked packages + deploy Trigger Coolify deployment + deploy:status Check deployment status + deploy:rollback Rollback to a previous deployment + deploy:list List recent deployments + serve:embedded (CGO only) Serve via embedded FrankenPHP runtime + exec (CGO only) Execute artisan via FrankenPHP +``` + + +## FrankenPHP Handler (CGO) + +Files: `handler.go`, `cmd_serve_frankenphp.go`, `env.go`, `extract.go` + +Build tag: `//go:build cgo` + +The `Handler` struct implements `http.Handler` and delegates all PHP processing +to the FrankenPHP C library. It supports two modes: + +1. **Octane worker mode** -- Laravel stays booted in memory across requests. + Workers are persistent PHP processes that handle requests without + re-bootstrapping. This yields sub-millisecond response times. +2. **Standard mode** -- each request boots the PHP application from scratch. + Used as a fallback when Octane is not installed. + +### Request Routing + +`Handler.ServeHTTP` implements a try-files pattern similar to Caddy/Nginx: + +1. If the URL maps to a directory, rewrite to `{dir}/index.php`. +2. If the file does not exist and the URL does not end in `.php`, rewrite to + `/index.php` (front controller). +3. Non-PHP files that exist on disc are served directly via `http.ServeFile`. +4. Everything else is passed to `frankenphp.ServeHTTP`. + +### Initialisation + +```go +handler, cleanup, err := php.NewHandler(laravelRoot, php.HandlerConfig{ + NumThreads: 4, + NumWorkers: 2, + PHPIni: map[string]string{ + "display_errors": "Off", + "opcache.enable": "1", + }, +}) +defer cleanup() +``` + +`NewHandler` tries to initialise FrankenPHP with workers first. If +`vendor/laravel/octane/bin/frankenphp-worker.php` exists, it passes the worker +script to `frankenphp.Init`. If that fails, it falls back to standard mode. + +### Embedded Applications + +`Extract()` copies an `embed.FS`-packaged Laravel application to a temporary +directory so that FrankenPHP can access real filesystem paths. +`PrepareRuntimeEnvironment()` then creates persistent data directories +(`~/Library/Application Support/{app}` on macOS, `~/.local/share/{app}` on +Linux), generates a `.env` file with an auto-generated `APP_KEY`, symlinks +`storage/` to the persistent location, and creates an empty SQLite database. + + +## Native Bridge + +File: `bridge.go` + +The bridge is a localhost-only HTTP server that allows PHP code to call back into +Go. This is needed because Livewire renders server-side in PHP and cannot call +Wails bindings (`window.go.*`) directly. + +```go +bridge, err := php.NewBridge(myHandler) +// PHP can now POST to http://127.0.0.1:{bridge.Port()}/bridge/call +``` + +The bridge exposes two endpoints: + +| Method | Path | Purpose | +|---|---|---| +| GET | `/bridge/health` | Health check (returns `{"status":"ok"}`) | +| POST | `/bridge/call` | Invoke a named method with JSON arguments | + +The host application implements `BridgeHandler`: + +```go +type BridgeHandler interface { + HandleBridgeCall(method string, args json.RawMessage) (any, error) +} +``` + +The bridge port is injected into Laravel's `.env` as `NATIVE_BRIDGE_URL`. + + +## Service Orchestration + +Files: `php.go`, `services.go`, `services_unix.go`, `services_windows.go` + +### DevServer + +`DevServer` manages the lifecycle of all development services. It: + +1. Detects which services are needed (via `DetectServices`). +2. Filters out services disabled by flags (`--no-vite`, `--no-horizon`, etc.). +3. Creates concrete service instances. +4. Starts all services, rolling back if any fail. +5. Provides unified log streaming (round-robin multiplexing from all service log files). +6. Stops services in reverse order on shutdown. + +### Service Interface + +All managed services implement: + +```go +type Service interface { + Name() string + Start(ctx context.Context) error + Stop() error + Logs(follow bool) (io.ReadCloser, error) + Status() ServiceStatus +} +``` + +### Concrete Services + +| Service | Binary | Default Port | Notes | +|---|---|---|---| +| `FrankenPHPService` | `php artisan octane:start --server=frankenphp` | 8000 | HTTPS via mkcert certificates | +| `ViteService` | `npm/pnpm/yarn/bun run dev` | 5173 | Auto-detects package manager | +| `HorizonService` | `php artisan horizon` | -- | Uses `horizon:terminate` for graceful stop | +| `ReverbService` | `php artisan reverb:start` | 8080 | WebSocket server | +| `RedisService` | `redis-server` | 6379 | Optional config file support | + +All services inherit from `baseService`, which handles: + +- Process creation with platform-specific `SysProcAttr` for clean shutdown. +- Log file creation under `.core/logs/`. +- Background process monitoring. +- Graceful stop with SIGTERM, then SIGKILL after 5 seconds. + + +## Project Detection + +File: `detect.go` + +The detection system inspects the filesystem to determine project capabilities: + +| Function | Checks | +|---|---| +| `IsLaravelProject(dir)` | `artisan` exists and `composer.json` requires `laravel/framework` | +| `IsFrankenPHPProject(dir)` | `laravel/octane` in `composer.json`, `config/octane.php` mentions frankenphp | +| `IsPHPProject(dir)` | `composer.json` exists | +| `DetectServices(dir)` | Checks for Vite configs, Horizon config, Reverb config, Redis in `.env` | +| `DetectPackageManager(dir)` | Inspects lock files: `bun.lockb`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json` | +| `GetLaravelAppName(dir)` | Reads `APP_NAME` from `.env` | +| `GetLaravelAppURL(dir)` | Reads `APP_URL` from `.env` | + + +## Dockerfile Generation + +File: `dockerfile.go` + +`GenerateDockerfile(dir)` produces a multi-stage Dockerfile by analysing +`composer.json`: + +1. **PHP version** -- extracted from `composer.json`'s `require.php` constraint. +2. **Extensions** -- inferred from package dependencies (e.g., `laravel/horizon` + implies `redis` and `pcntl`; `intervention/image` implies `gd`). +3. **Frontend assets** -- if `package.json` has a `build` script, a Node.js + build stage is prepended. +4. **Base image** -- `dunglas/frankenphp` with Alpine variant by default. + +The generated Dockerfile includes: + +- Multi-stage build for frontend assets (Node 20 Alpine). +- Composer dependency installation with layer caching. +- Laravel config/route/view caching. +- Correct permissions for `storage/` and `bootstrap/cache/`. +- Health check via `curl -f http://localhost/up`. +- Octane start command if `laravel/octane` is detected. + + +## CI Pipeline + +File: `cmd_ci.go`, `quality.go`, `testing.go` + +The `ci` command runs six checks in sequence: + +| Check | Tool | SARIF Support | +|---|---|---| +| `test` | Pest or PHPUnit (auto-detected) | No | +| `stan` | PHPStan or Larastan (auto-detected) | Yes | +| `psalm` | Psalm (skipped if not configured) | Yes | +| `fmt` | Laravel Pint (check-only mode) | No | +| `audit` | `composer audit` + `npm audit` | No | +| `security` | `.env` and filesystem security checks | No | + +### Output Formats + +- **Default** -- coloured terminal table with per-check status icons. +- **`--json`** -- structured `CIResult` JSON with per-check details. +- **`--summary`** -- Markdown table suitable for PR comments. +- **`--sarif`** -- SARIF files for stan/psalm, uploadable to GitHub Security. +- **`--upload-sarif`** -- uploads SARIF files via `gh api`. + +### Failure Threshold + +The `--fail-on` flag controls when the pipeline returns a non-zero exit code: + +| Value | Fails On | +|---|---| +| `critical` | Only if issues with `Issues > 0` | +| `high` / `error` (default) | Any check with status `failed` | +| `warning` | Any check with status `failed` or `warning` | + +### QA Pipeline Stages + +The `quality.go` file also defines a broader QA pipeline (`QAOptions`) with +three stages: + +- **Quick** -- `audit`, `fmt`, `stan` +- **Standard** -- `psalm` (if configured), `test` +- **Full** -- `rector` (if configured), `infection` (if configured) + + +## Deployment (Coolify) + +Files: `deploy.go`, `coolify.go` + +### Configuration + +Coolify credentials are loaded from environment variables or `.env`: + +``` +COOLIFY_URL=https://coolify.example.com +COOLIFY_TOKEN=your-api-token +COOLIFY_APP_ID=app-uuid +COOLIFY_STAGING_APP_ID=staging-app-uuid (optional) +``` + +Environment variables take precedence over `.env` values. + +### CoolifyClient + +The `CoolifyClient` wraps the Coolify REST API: + +```go +client := php.NewCoolifyClient(baseURL, token) +deployment, err := client.TriggerDeploy(ctx, appID, force) +deployment, err := client.GetDeployment(ctx, appID, deploymentID) +deployments, err := client.ListDeployments(ctx, appID, limit) +deployment, err := client.Rollback(ctx, appID, deploymentID) +app, err := client.GetApp(ctx, appID) +``` + +### Deployment Flow + +1. Load config from `.env` or environment. +2. Resolve the app ID for the target environment (production or staging). +3. Trigger deployment via the Coolify API. +4. If `--wait` is set, poll every 5 seconds (up to 10 minutes) until the + deployment reaches a terminal state. +5. Print deployment status with coloured output. + +### Rollback + +If no specific deployment ID is provided, `Rollback()` fetches the 10 most +recent deployments, skips the current one, and rolls back to the last +successful deployment. + + +## Workspace Support + +File: `workspace.go` + +For multi-package repositories, a `.core/workspace.yaml` file at the workspace +root can set an active package: + +```yaml +version: 1 +active: core-tenant +packages_dir: ./packages +``` + +When present, the `PersistentPreRunE` hook automatically changes the working +directory to the active package before command execution. The workspace root is +found by walking up the directory tree. + + +## SSL Certificates + +File: `ssl.go` + +The `ssl` command and `--https` flag use [mkcert](https://github.com/FiloSottile/mkcert) +to generate locally-trusted SSL certificates. Certificates are stored in +`~/.core/ssl/` by default. + +The `SetupSSLIfNeeded()` function is idempotent: it checks for existing +certificates before generating new ones. Generated certificates cover the +domain, `localhost`, `127.0.0.1`, and `::1`. + + +## Filesystem Abstraction + +The module uses `io.Medium` from `forge.lthn.ai/core/go-io` for all filesystem +operations. The default medium is `io.Local` (real filesystem), but tests can +inject a mock medium via `SetMedium()`: + +```go +php.SetMedium(myMockMedium) +defer php.SetMedium(io.Local) +``` + +This allows the detection, Dockerfile generation, package management, and +security check code to be tested without touching the real filesystem. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..1b929f4 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,284 @@ +--- +title: Development +description: How to build, test, and contribute to core/php. +--- + +# Development + +This guide covers building the `core-php` binary, running the test suite, and +contributing to the project. + + +## Prerequisites + +- **Go 1.26+** (the module uses Go 1.26 features) +- **CGO toolchain** (optional, required only for FrankenPHP embedding) +- **Docker** (for container build/serve commands) +- **mkcert** (optional, for local SSL certificates) +- **PHP 8.3+** with Composer (for the PHP side of the project) +- **Node.js 20+** (optional, for frontend asset building) + + +## Building + +### Standard build (no CGO) + +The default build produces a binary without FrankenPHP embedding. The embedded +FrankenPHP commands (`serve:embedded`, `exec`) are excluded. + +```bash +# Using the core CLI +core build + +# Using go directly +go build -trimpath -ldflags="-s -w" -o bin/core-php ./cmd/core-php +``` + +Build configuration lives in `.core/build.yaml`: + +```yaml +project: + name: core-php + main: ./cmd/core-php + binary: core-php + +build: + cgo: false + flags: + - -trimpath + ldflags: + - -s + - -w + +targets: + - os: linux + arch: amd64 + - os: linux + arch: arm64 + - os: darwin + arch: arm64 + - os: windows + arch: amd64 +``` + +### CGO build (with FrankenPHP) + +To include the embedded FrankenPHP handler, enable CGO: + +```bash +CGO_ENABLED=1 go build -trimpath -o bin/core-php ./cmd/core-php +``` + +This pulls in `github.com/dunglas/frankenphp` and links against the PHP C +library. The resulting binary can serve Laravel applications without a separate +PHP installation. + + +## Running Tests + +```bash +# All Go tests +core go test +# -- or -- +go test ./... + +# Single test +core go test --run TestDetectServices +# -- or -- +go test -run TestDetectServices ./... + +# With race detector +go test -race ./... + +# Coverage +core go cov +core go cov --open # Opens HTML report +``` + +### Test Conventions + +Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern from the Core +framework: + +- **`_Good`** -- happy path, expected to succeed. +- **`_Bad`** -- expected error conditions, verifying error handling. +- **`_Ugly`** -- edge cases, panics, unusual inputs. + +### Mock Filesystem + +Tests that exercise detection, Dockerfile generation, or package management use +a mock `io.Medium` to avoid filesystem side effects: + +```go +func TestDetectServices_Good(t *testing.T) { + mock := io.NewMockMedium() + mock.WriteFile("artisan", "") + mock.WriteFile("composer.json", `{"require":{"laravel/framework":"^11.0"}}`) + mock.WriteFile("vite.config.js", "") + + php.SetMedium(mock) + defer php.SetMedium(io.Local) + + services := php.DetectServices(".") + assert.Contains(t, services, php.ServiceFrankenPHP) + assert.Contains(t, services, php.ServiceVite) +} +``` + +### Test Files + +| File | Covers | +|---|---| +| `php_test.go` | DevServer lifecycle, service filtering, options | +| `container_test.go` | Docker build, LinuxKit build, serve options | +| `detect_test.go` | Project detection, service detection, package manager detection | +| `dockerfile_test.go` | Dockerfile generation, PHP extension detection, version extraction | +| `deploy_test.go` | Deployment flow, rollback, status checking | +| `deploy_internal_test.go` | Internal deployment helpers | +| `coolify_test.go` | Coolify API client (HTTP mocking) | +| `packages_test.go` | Package linking, unlinking, listing | +| `services_test.go` | Service interface, base service, start/stop | +| `services_extended_test.go` | Extended service scenarios | +| `ssl_test.go` | SSL certificate paths, existence checking | +| `ssl_extended_test.go` | Extended SSL scenarios | + + +## Code Quality + +```bash +# Format Go code +core go fmt + +# Vet +core go vet + +# Lint +core go lint + +# Full QA (fmt + vet + lint + test) +core go qa + +# Full QA with race detection, vulnerability scan, security checks +core go qa full +``` + + +## Project Structure + +``` +forge.lthn.ai/core/php/ + cmd/ + core-php/ + main.go # Binary entry point + locales/ + *.json # Internationalised CLI strings + docker/ + docker-compose.prod.yml + stubs/ # Template stubs + config/ # PHP configuration templates + src/ # PHP framework source (separate from Go code) + tests/ # PHP tests + docs/ # Documentation (this directory) + .core/ + build.yaml # Build configuration + *.go # Go source (flat layout, single package) +``` + +The Go code uses a flat package layout -- all `.go` files are in the root +`php` package. This keeps imports simple: `import php "forge.lthn.ai/core/php"`. + + +## Adding a New Command + +1. Create a new file `cmd_mycommand.go`. +2. Define the registration function: + +```go +func addPHPMyCommand(parent *cli.Command) { + cmd := &cli.Command{ + Use: "mycommand", + Short: i18n.T("cmd.php.mycommand.short"), + RunE: func(cmd *cli.Command, args []string) error { + // Implementation + return nil + }, + } + parent.AddCommand(cmd) +} +``` + +3. Register it in `cmd.go` inside both `AddPHPCommands` and + `AddPHPRootCommands`: + +```go +addPHPMyCommand(phpCmd) // or root, for standalone binary +``` + +4. Add the i18n key to `locales/en.json`. + + +## Adding a New Service + +1. Define the service struct in `services.go`, embedding `baseService`: + +```go +type MyService struct { + baseService +} + +func NewMyService(dir string) *MyService { + return &MyService{ + baseService: baseService{ + name: "MyService", + port: 9999, + dir: dir, + }, + } +} + +func (s *MyService) Start(ctx context.Context) error { + return s.startProcess(ctx, "my-binary", []string{"--flag"}, nil) +} + +func (s *MyService) Stop() error { + return s.stopProcess() +} +``` + +2. Add a `DetectedService` constant in `detect.go`: + +```go +const ServiceMyService DetectedService = "myservice" +``` + +3. Add detection logic in `DetectServices()`. + +4. Add a case in `DevServer.Start()` in `php.go`. + + +## Internationalisation + +All user-facing strings use `i18n.T()` keys rather than hardcoded English. +Locale files live in `locales/` and are embedded via `//go:embed`: + +```go +//go:embed locales/*.json +var localeFS embed.FS + +func init() { + i18n.RegisterLocales(localeFS, "locales") +} +``` + +When adding new commands or messages, add the corresponding keys to the locale +files. + + +## Contributing + +- Follow UK English conventions: colour, organisation, centre. +- All code is licenced under EUPL-1.2. +- Run `core go qa` before submitting changes. +- Use conventional commits: `type(scope): description`. +- Include `Co-Authored-By: Virgil ` if pair-programming with + the AI agent. diff --git a/docs/index.md b/docs/index.md index 76908c5..401cabe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,126 +1,96 @@ --- -layout: home - -hero: - name: Core PHP Framework - text: Modular Monolith for Laravel - tagline: Event-driven architecture with lazy module loading and built-in multi-tenancy - actions: - - theme: brand - text: Get Started - link: /guide/getting-started - - theme: alt - text: View on GitHub - link: https://github.com/host-uk/core-php - -features: - - icon: ⚡️ - title: Event-Driven Modules - details: Modules declare interest in lifecycle events and are only loaded when needed, reducing overhead for unused features. - - - icon: 🔒 - title: Multi-Tenant Isolation - details: Automatic workspace scoping for Eloquent models with strict mode enforcement prevents data leakage. - - - icon: 🎯 - title: Actions Pattern - details: Extract business logic into testable, reusable classes with automatic dependency injection. - - - icon: 📝 - title: Activity Logging - details: Built-in audit trails for model changes with minimal setup using Spatie Activity Log. - - - icon: 🌱 - title: Seeder Auto-Discovery - details: Automatic seeder ordering via priority and dependency attributes eliminates manual registration. - - - icon: 🎨 - title: HLCRF Layouts - details: Data-driven composable layouts with infinite nesting for flexible UI structures. - - - icon: 🔐 - title: Security First - details: Bouncer action gates, request whitelisting, and comprehensive input sanitization. - - - icon: 🚀 - title: Production Ready - details: Battle-tested in production with comprehensive test coverage and security audits. +title: core/php +description: Go-powered PHP/Laravel development toolkit with FrankenPHP embedding, service orchestration, CI pipelines, and Coolify deployment. --- +# core/php + +`forge.lthn.ai/core/php` is a Go module that provides a comprehensive CLI toolkit +for PHP and Laravel development. It covers the full lifecycle: local development +with service orchestration, code quality assurance, Docker/LinuxKit image building, +and production deployment via the Coolify API. + +The module also embeds FrankenPHP, allowing Laravel applications to be served +from a single Go binary with Octane worker mode for sub-millisecond response +times. + + ## Quick Start +### As a standalone binary + ```bash -# Install via Composer -composer require host-uk/core +# Build the core-php binary +core build +# -- or -- +go build -o bin/core-php ./cmd/core-php -# Create a module -php artisan make:mod Commerce +# Start the Laravel development environment +core-php dev -# Register lifecycle events -class Boot -{ - public static array $listens = [ - WebRoutesRegistering::class => 'onWebRoutes', - ]; - - public function onWebRoutes(WebRoutesRegistering $event): void - { - $event->routes(fn () => require __DIR__.'/Routes/web.php'); - } -} +# Run the CI pipeline +core-php ci ``` -## Why Core PHP? +### As a library in a Go application -Traditional Laravel applications grow into monoliths with tight coupling and unclear boundaries. Microservices add complexity you may not need. **Core PHP provides a middle ground**: a structured monolith with clear module boundaries, lazy loading, and the ability to extract services later if needed. +```go +import php "forge.lthn.ai/core/php" -### Key Benefits +// Register commands under a "php" parent command +cli.Main( + cli.WithCommands("php", php.AddPHPRootCommands), +) +``` -- **Reduced Complexity** - No network overhead, distributed tracing, or service mesh -- **Clear Boundaries** - Modules have explicit dependencies via lifecycle events -- **Performance** - Lazy loading means unused modules aren't loaded -- **Flexibility** - Start monolithic, extract services when it makes sense -- **Type Safety** - Full IDE support with no RPC serialization -## Packages +## Package Layout -
+| File / Directory | Purpose | +|---|---| +| `cmd/core-php/main.go` | Binary entry point -- registers all commands and calls `cli.Main()` | +| `cmd.go` | Top-level command registration (`AddPHPCommands`, `AddPHPRootCommands`) | +| `cmd_dev.go` | `dev`, `logs`, `stop`, `status`, `ssl` commands | +| `cmd_build.go` | `build` (Docker/LinuxKit) and `serve` (production container) commands | +| `cmd_ci.go` | `ci` command -- full QA pipeline with JSON/Markdown/SARIF output | +| `cmd_deploy.go` | `deploy`, `deploy:status`, `deploy:rollback`, `deploy:list` commands | +| `cmd_packages.go` | `packages link/unlink/update/list` commands | +| `cmd_serve_frankenphp.go` | `serve:embedded` and `exec` commands (CGO only) | +| `cmd_commands.go` | `AddCommands()` convenience wrapper | +| `handler.go` | FrankenPHP HTTP handler (`Handler`) -- CGO build tag | +| `bridge.go` | Native bridge -- localhost HTTP API for PHP-to-Go calls | +| `php.go` | `DevServer` -- multi-service orchestration (start, stop, logs, status) | +| `services.go` | `Service` interface and concrete implementations (FrankenPHP, Vite, Horizon, Reverb, Redis) | +| `detect.go` | Project detection: Laravel, FrankenPHP, Vite, Horizon, Reverb, Redis, package managers | +| `dockerfile.go` | Auto-generated Dockerfiles from `composer.json` analysis | +| `container.go` | `DockerBuildOptions`, `LinuxKitBuildOptions`, `ServeOptions`, and build/serve functions | +| `deploy.go` | Deployment orchestration -- `Deploy()`, `Rollback()`, `DeployStatus()` | +| `coolify.go` | Coolify API client (`CoolifyClient`) with deploy, rollback, status, and list operations | +| `quality.go` | QA tools: Pint, PHPStan/Larastan, Psalm, Rector, Infection, security checks, audit | +| `testing.go` | Test runner detection (Pest/PHPUnit) and execution | +| `ssl.go` | SSL certificate management via mkcert | +| `packages.go` | Composer path repository management (link/unlink local packages) | +| `env.go` | Runtime environment setup for embedded apps (CGO only) | +| `extract.go` | `Extract()` -- copies an `embed.FS` Laravel app to a temporary directory | +| `workspace.go` | Workspace configuration (`.core/workspace.yaml`) for multi-package repos | +| `i18n.go` | Locale registration for internationalised CLI strings | +| `services_unix.go` | Unix process group management (SIGTERM/SIGKILL) | +| `services_windows.go` | Windows process termination | +| `.core/build.yaml` | Build configuration for `core build` | -### [Core](/packages/core) -Event-driven architecture, module system, actions pattern, and multi-tenancy. -### [Admin](/packages/admin) -Livewire-powered admin panel with global search and service management. +## Dependencies -### [API](/packages/api) -REST API with OpenAPI docs, rate limiting, webhook signing, and secure keys. +| Module | Role | +|---|---| +| `forge.lthn.ai/core/cli` | CLI framework (Cobra wrapper, TUI styles, output helpers) | +| `forge.lthn.ai/core/go-i18n` | Internationalisation for command descriptions and messages | +| `forge.lthn.ai/core/go-io` | Filesystem abstraction (`Medium` interface) for testability | +| `forge.lthn.ai/core/go-process` | Process management utilities | +| `github.com/dunglas/frankenphp` | FrankenPHP embedding (CGO, optional) | +| `gopkg.in/yaml.v3` | YAML parsing for workspace configuration | -### [MCP](/packages/mcp) -Model Context Protocol tools for AI integrations with analytics and security. -
+## Licence -## Community - -- **GitHub Discussions** - Ask questions and share ideas -- **Issue Tracker** - Report bugs and request features -- **Contributing** - See our [contributing guide](/contributing) - - +EUPL-1.2