From 84ce94b713b08cb8b236142445ef95b1943669ff Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 4 Mar 2026 17:22:10 +0000 Subject: [PATCH] feat: add core-php binary, fix imports to use core/cli - Create cmd/core-php/main.go standalone binary entry point - Add FrankenPHP serve:embedded and exec commands (CGO build-tagged) - Replace all cobra imports with core/cli re-exports - Replace core/go/pkg/cli with core/cli/pkg/cli - Replace core/go/pkg/i18n with core/go-i18n/pkg/i18n - Add core/go and core/go-i18n as go.mod dependencies Co-Authored-By: Virgil --- cmd.go | 11 ++-- cmd/core-php/main.go | 15 +++++ cmd_build.go | 25 ++++--- cmd_ci.go | 11 ++-- cmd_commands.go | 4 +- cmd_deploy.go | 31 +++++---- cmd_dev.go | 35 +++++----- cmd_packages.go | 37 +++++------ cmd_qa_runner.go | 4 +- cmd_quality.go | 59 +++++++++-------- cmd_serve_frankenphp.go | 141 ++++++++++++++++++++++++++++++++++++++++ container.go | 2 +- coolify.go | 2 +- deploy.go | 2 +- dockerfile.go | 2 +- go.mod | 2 + i18n.go | 2 +- packages.go | 2 +- php.go | 2 +- quality.go | 4 +- services.go | 2 +- ssl.go | 2 +- testing.go | 2 +- 23 files changed, 275 insertions(+), 124 deletions(-) create mode 100644 cmd/core-php/main.go create mode 100644 cmd_serve_frankenphp.go diff --git a/cmd.go b/cmd.go index c0fa098..fc96e9b 100644 --- a/cmd.go +++ b/cmd.go @@ -4,10 +4,9 @@ import ( "os" "path/filepath" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" "forge.lthn.ai/core/go/pkg/io" - "github.com/spf13/cobra" ) // DefaultMedium is the default filesystem medium used by the php package. @@ -67,12 +66,12 @@ var ( ) // AddPHPCommands adds PHP/Laravel development commands. -func AddPHPCommands(root *cobra.Command) { - phpCmd := &cobra.Command{ +func AddPHPCommands(root *cli.Command) { + phpCmd := &cli.Command{ Use: "php", Short: i18n.T("cmd.php.short"), Long: i18n.T("cmd.php.long"), - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + PersistentPreRunE: func(cmd *cli.Command, args []string) error { // Check if we are in a workspace root wsRoot, err := findWorkspaceRoot() if err != nil { diff --git a/cmd/core-php/main.go b/cmd/core-php/main.go new file mode 100644 index 0000000..14aea3b --- /dev/null +++ b/cmd/core-php/main.go @@ -0,0 +1,15 @@ +// Package main provides the core-php binary — a standalone PHP/Laravel +// development tool with FrankenPHP embedding support. +package main + +import ( + php "forge.lthn.ai/core/go-php" + + "forge.lthn.ai/core/cli/pkg/cli" +) + +func main() { + cli.Main( + cli.WithCommands("php", php.AddPHPCommands), + ) +} diff --git a/cmd_build.go b/cmd_build.go index b8b7583..11052f1 100644 --- a/cmd_build.go +++ b/cmd_build.go @@ -6,9 +6,8 @@ import ( "os" "strings" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) var ( @@ -23,12 +22,12 @@ var ( buildNoCache bool ) -func addPHPBuildCommand(parent *cobra.Command) { - buildCmd := &cobra.Command{ +func addPHPBuildCommand(parent *cli.Command) { + buildCmd := &cli.Command{ Use: "build", Short: i18n.T("cmd.php.build.short"), Long: i18n.T("cmd.php.build.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -190,12 +189,12 @@ var ( serveEnvFile string ) -func addPHPServeCommand(parent *cobra.Command) { - serveCmd := &cobra.Command{ +func addPHPServeCommand(parent *cli.Command) { + serveCmd := &cli.Command{ Use: "serve", Short: i18n.T("cmd.php.serve.short"), Long: i18n.T("cmd.php.serve.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { imageName := serveImageName if imageName == "" { // Try to detect from current directory @@ -268,13 +267,13 @@ func addPHPServeCommand(parent *cobra.Command) { parent.AddCommand(serveCmd) } -func addPHPShellCommand(parent *cobra.Command) { - shellCmd := &cobra.Command{ +func addPHPShellCommand(parent *cli.Command) { + shellCmd := &cli.Command{ Use: "shell [container]", Short: i18n.T("cmd.php.shell.short"), Long: i18n.T("cmd.php.shell.long"), - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + Args: cli.ExactArgs(1), + RunE: func(cmd *cli.Command, args []string) error { ctx := context.Background() cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]})) diff --git a/cmd_ci.go b/cmd_ci.go index 1c4344f..1c52400 100644 --- a/cmd_ci.go +++ b/cmd_ci.go @@ -21,9 +21,8 @@ import ( "strings" "time" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) // CI command flags @@ -66,12 +65,12 @@ type CISummary struct { Skipped int `json:"skipped"` } -func addPHPCICommand(parent *cobra.Command) { - ciCmd := &cobra.Command{ +func addPHPCICommand(parent *cli.Command) { + ciCmd := &cli.Command{ Use: "ci", Short: i18n.T("cmd.php.ci.short"), Long: i18n.T("cmd.php.ci.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPCI() }, } diff --git a/cmd_commands.go b/cmd_commands.go index c0a2444..e3f0b28 100644 --- a/cmd_commands.go +++ b/cmd_commands.go @@ -33,9 +33,9 @@ // - deploy:list: List recent deployments package php -import "github.com/spf13/cobra" +import "forge.lthn.ai/core/cli/pkg/cli" // AddCommands registers the 'php' command and all subcommands. -func AddCommands(root *cobra.Command) { +func AddCommands(root *cli.Command) { AddPHPCommands(root) } diff --git a/cmd_deploy.go b/cmd_deploy.go index 2298a43..1a3e71b 100644 --- a/cmd_deploy.go +++ b/cmd_deploy.go @@ -5,9 +5,8 @@ import ( "os" "time" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) // Deploy command styles (aliases to shared) @@ -17,7 +16,7 @@ var ( phpDeployFailedStyle = cli.ErrorStyle ) -func addPHPDeployCommands(parent *cobra.Command) { +func addPHPDeployCommands(parent *cli.Command) { // Main deploy command addPHPDeployCommand(parent) @@ -37,12 +36,12 @@ var ( deployWait bool ) -func addPHPDeployCommand(parent *cobra.Command) { - deployCmd := &cobra.Command{ +func addPHPDeployCommand(parent *cli.Command) { + deployCmd := &cli.Command{ Use: "deploy", Short: i18n.T("cmd.php.deploy.short"), Long: i18n.T("cmd.php.deploy.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -97,12 +96,12 @@ var ( deployStatusDeploymentID string ) -func addPHPDeployStatusCommand(parent *cobra.Command) { - statusCmd := &cobra.Command{ +func addPHPDeployStatusCommand(parent *cli.Command) { + statusCmd := &cli.Command{ Use: "deploy:status", Short: i18n.T("cmd.php.deploy_status.short"), Long: i18n.T("cmd.php.deploy_status.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -146,12 +145,12 @@ var ( rollbackWait bool ) -func addPHPDeployRollbackCommand(parent *cobra.Command) { - rollbackCmd := &cobra.Command{ +func addPHPDeployRollbackCommand(parent *cli.Command) { + rollbackCmd := &cli.Command{ Use: "deploy:rollback", Short: i18n.T("cmd.php.deploy_rollback.short"), Long: i18n.T("cmd.php.deploy_rollback.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -206,12 +205,12 @@ var ( deployListLimit int ) -func addPHPDeployListCommand(parent *cobra.Command) { - listCmd := &cobra.Command{ +func addPHPDeployListCommand(parent *cli.Command) { + listCmd := &cli.Command{ Use: "deploy:list", Short: i18n.T("cmd.php.deploy_list.short"), Long: i18n.T("cmd.php.deploy_list.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) diff --git a/cmd_dev.go b/cmd_dev.go index d2d8de0..f0be425 100644 --- a/cmd_dev.go +++ b/cmd_dev.go @@ -10,9 +10,8 @@ import ( "syscall" "time" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) var ( @@ -25,12 +24,12 @@ var ( devPort int ) -func addPHPDevCommand(parent *cobra.Command) { - devCmd := &cobra.Command{ +func addPHPDevCommand(parent *cli.Command) { + devCmd := &cli.Command{ Use: "dev", Short: i18n.T("cmd.php.dev.short"), Long: i18n.T("cmd.php.dev.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPDev(phpDevOptions{ NoVite: devNoVite, NoHorizon: devNoHorizon, @@ -185,12 +184,12 @@ var ( logsService string ) -func addPHPLogsCommand(parent *cobra.Command) { - logsCmd := &cobra.Command{ +func addPHPLogsCommand(parent *cli.Command) { + logsCmd := &cli.Command{ Use: "logs", Short: i18n.T("cmd.php.logs.short"), Long: i18n.T("cmd.php.logs.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPLogs(logsService, logsFollow) }, } @@ -245,11 +244,11 @@ func runPHPLogs(service string, follow bool) error { return scanner.Err() } -func addPHPStopCommand(parent *cobra.Command) { - stopCmd := &cobra.Command{ +func addPHPStopCommand(parent *cli.Command) { + stopCmd := &cli.Command{ Use: "stop", Short: i18n.T("cmd.php.stop.short"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPStop() }, } @@ -276,11 +275,11 @@ func runPHPStop() error { return nil } -func addPHPStatusCommand(parent *cobra.Command) { - statusCmd := &cobra.Command{ +func addPHPStatusCommand(parent *cli.Command) { + statusCmd := &cli.Command{ Use: "status", Short: i18n.T("cmd.php.status.short"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPStatus() }, } @@ -339,11 +338,11 @@ func runPHPStatus() error { var sslDomain string -func addPHPSSLCommand(parent *cobra.Command) { - sslCmd := &cobra.Command{ +func addPHPSSLCommand(parent *cli.Command) { + sslCmd := &cli.Command{ Use: "ssl", Short: i18n.T("cmd.php.ssl.short"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { return runPHPSSL(sslDomain) }, } diff --git a/cmd_packages.go b/cmd_packages.go index fa1172b..ff89358 100644 --- a/cmd_packages.go +++ b/cmd_packages.go @@ -3,13 +3,12 @@ package php import ( "os" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) -func addPHPPackagesCommands(parent *cobra.Command) { - packagesCmd := &cobra.Command{ +func addPHPPackagesCommands(parent *cli.Command) { + packagesCmd := &cli.Command{ Use: "packages", Short: i18n.T("cmd.php.packages.short"), Long: i18n.T("cmd.php.packages.long"), @@ -22,13 +21,13 @@ func addPHPPackagesCommands(parent *cobra.Command) { addPHPPackagesListCommand(packagesCmd) } -func addPHPPackagesLinkCommand(parent *cobra.Command) { - linkCmd := &cobra.Command{ +func addPHPPackagesLinkCommand(parent *cli.Command) { + linkCmd := &cli.Command{ Use: "link [paths...]", Short: i18n.T("cmd.php.packages.link.short"), Long: i18n.T("cmd.php.packages.link.long"), - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + Args: cli.MinimumNArgs(1), + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -48,13 +47,13 @@ func addPHPPackagesLinkCommand(parent *cobra.Command) { parent.AddCommand(linkCmd) } -func addPHPPackagesUnlinkCommand(parent *cobra.Command) { - unlinkCmd := &cobra.Command{ +func addPHPPackagesUnlinkCommand(parent *cli.Command) { + unlinkCmd := &cli.Command{ Use: "unlink [packages...]", Short: i18n.T("cmd.php.packages.unlink.short"), Long: i18n.T("cmd.php.packages.unlink.long"), - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + Args: cli.MinimumNArgs(1), + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -74,12 +73,12 @@ func addPHPPackagesUnlinkCommand(parent *cobra.Command) { parent.AddCommand(unlinkCmd) } -func addPHPPackagesUpdateCommand(parent *cobra.Command) { - updateCmd := &cobra.Command{ +func addPHPPackagesUpdateCommand(parent *cli.Command) { + updateCmd := &cli.Command{ Use: "update [packages...]", Short: i18n.T("cmd.php.packages.update.short"), Long: i18n.T("cmd.php.packages.update.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -99,12 +98,12 @@ func addPHPPackagesUpdateCommand(parent *cobra.Command) { parent.AddCommand(updateCmd) } -func addPHPPackagesListCommand(parent *cobra.Command) { - listCmd := &cobra.Command{ +func addPHPPackagesListCommand(parent *cli.Command) { + listCmd := &cli.Command{ Use: "list", Short: i18n.T("cmd.php.packages.list.short"), Long: i18n.T("cmd.php.packages.list.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) diff --git a/cmd_qa_runner.go b/cmd_qa_runner.go index 7e9d7ae..47b2c0c 100644 --- a/cmd_qa_runner.go +++ b/cmd_qa_runner.go @@ -6,9 +6,9 @@ import ( "strings" "sync" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go/pkg/framework" - "forge.lthn.ai/core/go/pkg/i18n" + "forge.lthn.ai/core/go-i18n/pkg/i18n" "forge.lthn.ai/core/go/pkg/process" ) diff --git a/cmd_quality.go b/cmd_quality.go index e76363e..7f42025 100644 --- a/cmd_quality.go +++ b/cmd_quality.go @@ -7,9 +7,8 @@ import ( "os" "strings" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" - "github.com/spf13/cobra" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) var ( @@ -20,12 +19,12 @@ var ( testJSON bool ) -func addPHPTestCommand(parent *cobra.Command) { - testCmd := &cobra.Command{ +func addPHPTestCommand(parent *cli.Command) { + testCmd := &cli.Command{ Use: "test", Short: i18n.T("cmd.php.test.short"), Long: i18n.T("cmd.php.test.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -77,12 +76,12 @@ var ( fmtJSON bool ) -func addPHPFmtCommand(parent *cobra.Command) { - fmtCmd := &cobra.Command{ +func addPHPFmtCommand(parent *cli.Command) { + fmtCmd := &cli.Command{ Use: "fmt [paths...]", Short: i18n.T("cmd.php.fmt.short"), Long: i18n.T("cmd.php.fmt.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -156,12 +155,12 @@ var ( stanSARIF bool ) -func addPHPStanCommand(parent *cobra.Command) { - stanCmd := &cobra.Command{ +func addPHPStanCommand(parent *cli.Command) { + stanCmd := &cli.Command{ Use: "stan [paths...]", Short: i18n.T("cmd.php.analyse.short"), Long: i18n.T("cmd.php.analyse.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -233,12 +232,12 @@ var ( psalmSARIF bool ) -func addPHPPsalmCommand(parent *cobra.Command) { - psalmCmd := &cobra.Command{ +func addPHPPsalmCommand(parent *cli.Command) { + psalmCmd := &cli.Command{ Use: "psalm", Short: i18n.T("cmd.php.psalm.short"), Long: i18n.T("cmd.php.psalm.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -310,12 +309,12 @@ var ( auditFix bool ) -func addPHPAuditCommand(parent *cobra.Command) { - auditCmd := &cobra.Command{ +func addPHPAuditCommand(parent *cli.Command) { + auditCmd := &cli.Command{ Use: "audit", Short: i18n.T("cmd.php.audit.short"), Long: i18n.T("cmd.php.audit.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -403,12 +402,12 @@ var ( securityURL string ) -func addPHPSecurityCommand(parent *cobra.Command) { - securityCmd := &cobra.Command{ +func addPHPSecurityCommand(parent *cli.Command) { + securityCmd := &cli.Command{ Use: "security", Short: i18n.T("cmd.php.security.short"), Long: i18n.T("cmd.php.security.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -502,12 +501,12 @@ var ( qaJSON bool ) -func addPHPQACommand(parent *cobra.Command) { - qaCmd := &cobra.Command{ +func addPHPQACommand(parent *cli.Command) { + qaCmd := &cli.Command{ Use: "qa", Short: i18n.T("cmd.php.qa.short"), Long: i18n.T("cmd.php.qa.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -670,12 +669,12 @@ var ( rectorClearCache bool ) -func addPHPRectorCommand(parent *cobra.Command) { - rectorCmd := &cobra.Command{ +func addPHPRectorCommand(parent *cli.Command) { + rectorCmd := &cli.Command{ Use: "rector", Short: i18n.T("cmd.php.rector.short"), Long: i18n.T("cmd.php.rector.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) @@ -744,12 +743,12 @@ var ( infectionOnlyCovered bool ) -func addPHPInfectionCommand(parent *cobra.Command) { - infectionCmd := &cobra.Command{ +func addPHPInfectionCommand(parent *cli.Command) { + infectionCmd := &cli.Command{ Use: "infection", Short: i18n.T("cmd.php.infection.short"), Long: i18n.T("cmd.php.infection.long"), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cli.Command, args []string) error { cwd, err := os.Getwd() if err != nil { return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err) diff --git a/cmd_serve_frankenphp.go b/cmd_serve_frankenphp.go new file mode 100644 index 0000000..e49292e --- /dev/null +++ b/cmd_serve_frankenphp.go @@ -0,0 +1,141 @@ +//go:build cgo + +package php + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "forge.lthn.ai/core/cli/pkg/cli" +) + +var ( + serveFPPort int + serveFPPath string + serveFPWorkers int + serveFPThreads int +) + +func init() { + cli.RegisterCommands(addFrankenPHPCommands) +} + +func addFrankenPHPCommands(root *cli.Command) { + // Find the php parent command, or create one + phpCmd := findOrCreatePHPCmd(root) + + serveCmd := &cli.Command{ + Use: "serve:embedded", + Short: "Serve Laravel via embedded FrankenPHP runtime", + Long: "Start an HTTP server using the embedded FrankenPHP runtime with Octane worker mode support.", + RunE: runFrankenPHPServe, + } + serveCmd.Flags().IntVar(&serveFPPort, "port", 8000, "HTTP listen port") + serveCmd.Flags().StringVar(&serveFPPath, "path", ".", "Laravel application root") + serveCmd.Flags().IntVar(&serveFPWorkers, "workers", 2, "Octane worker count") + serveCmd.Flags().IntVar(&serveFPThreads, "threads", 4, "PHP thread count") + phpCmd.AddCommand(serveCmd) + + execCmd := &cli.Command{ + Use: "exec [command...]", + Short: "Execute a PHP artisan command via FrankenPHP", + Long: "Boot FrankenPHP, run an artisan command, then exit. Stdin/stdout pass-through.", + Args: cli.MinimumNArgs(1), + RunE: runFrankenPHPExec, + } + execCmd.Flags().StringVar(&serveFPPath, "path", ".", "Laravel application root") + phpCmd.AddCommand(execCmd) +} + +func runFrankenPHPServe(cmd *cli.Command, args []string) error { + handler, cleanup, err := NewHandler(serveFPPath, HandlerConfig{ + NumThreads: serveFPThreads, + NumWorkers: serveFPWorkers, + }) + if err != nil { + return fmt.Errorf("init FrankenPHP: %w", err) + } + defer cleanup() + + addr := fmt.Sprintf(":%d", serveFPPort) + srv := &http.Server{ + Addr: addr, + Handler: handler, + } + + // Graceful shutdown + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + go func() { + log.Printf("core-php: serving on http://localhost%s (doc root: %s)", addr, handler.DocRoot()) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("core-php: server error: %v", err) + } + }() + + <-ctx.Done() + log.Println("core-php: shutting down...") + return srv.Shutdown(context.Background()) +} + +func runFrankenPHPExec(cmd *cli.Command, args []string) error { + handler, cleanup, err := NewHandler(serveFPPath, HandlerConfig{ + NumThreads: 1, + NumWorkers: 0, + }) + if err != nil { + return fmt.Errorf("init FrankenPHP: %w", err) + } + defer cleanup() + + // Build an artisan request + artisanArgs := "artisan" + for _, a := range args { + artisanArgs += " " + a + } + + log.Printf("core-php: exec %s (root: %s)", artisanArgs, handler.LaravelRoot()) + + // Execute via internal HTTP request to FrankenPHP + // This routes through the PHP runtime as if it were a CLI call + req, err := http.NewRequest("GET", "/artisan-exec?cmd="+artisanArgs, nil) + if err != nil { + return err + } + + // For now, use the handler directly + w := &execResponseWriter{os.Stdout} + handler.ServeHTTP(w, req) + + return nil +} + +// findOrCreatePHPCmd finds an existing "php" command or creates one. +func findOrCreatePHPCmd(root *cli.Command) *cli.Command { + for _, c := range root.Commands() { + if c.Use == "php" { + return c + } + } + phpCmd := &cli.Command{ + Use: "php", + Short: "PHP/Laravel development tools", + } + root.AddCommand(phpCmd) + return phpCmd +} + +// execResponseWriter writes HTTP response body directly to stdout. +type execResponseWriter struct { + out *os.File +} + +func (w *execResponseWriter) Header() http.Header { return http.Header{} } +func (w *execResponseWriter) WriteHeader(statusCode int) {} +func (w *execResponseWriter) Write(b []byte) (int, error) { return w.out.Write(b) } diff --git a/container.go b/container.go index 1df5dea..f996e20 100644 --- a/container.go +++ b/container.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // DockerBuildOptions configures Docker image building for PHP projects. diff --git a/coolify.go b/coolify.go index fd08a06..bf75a95 100644 --- a/coolify.go +++ b/coolify.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // CoolifyClient is an HTTP client for the Coolify API. diff --git a/deploy.go b/deploy.go index 9717ae7..ce33633 100644 --- a/deploy.go +++ b/deploy.go @@ -4,7 +4,7 @@ import ( "context" "time" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // Environment represents a deployment environment. diff --git a/dockerfile.go b/dockerfile.go index be7afd1..bf1e66e 100644 --- a/dockerfile.go +++ b/dockerfile.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // DockerfileConfig holds configuration for generating a Dockerfile. diff --git a/go.mod b/go.mod index 413b757..125a63d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.26.0 require ( forge.lthn.ai/core/cli v0.1.0 + forge.lthn.ai/core/go v0.1.0 + forge.lthn.ai/core/go-i18n v0.1.0 github.com/dunglas/frankenphp v1.5.0 github.com/stretchr/testify v1.11.1 ) diff --git a/i18n.go b/i18n.go index 96a60a9..9cb9517 100644 --- a/i18n.go +++ b/i18n.go @@ -4,7 +4,7 @@ package php import ( "embed" - "forge.lthn.ai/core/go/pkg/i18n" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) //go:embed locales/*.json diff --git a/packages.go b/packages.go index 03645d6..d98c4a1 100644 --- a/packages.go +++ b/packages.go @@ -6,7 +6,7 @@ import ( "os/exec" "path/filepath" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // LinkedPackage represents a linked local package. diff --git a/php.go b/php.go index 96393eb..c6b84a6 100644 --- a/php.go +++ b/php.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // Options configures the development server. diff --git a/quality.go b/quality.go index a7f9638..7f67012 100644 --- a/quality.go +++ b/quality.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "forge.lthn.ai/core/go/pkg/cli" - "forge.lthn.ai/core/go/pkg/i18n" + "forge.lthn.ai/core/cli/pkg/cli" + "forge.lthn.ai/core/go-i18n/pkg/i18n" ) // FormatOptions configures PHP code formatting. diff --git a/services.go b/services.go index 9282ece..4df44f0 100644 --- a/services.go +++ b/services.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // Service represents a managed development service. diff --git a/ssl.go b/ssl.go index 46b0cf8..7955523 100644 --- a/ssl.go +++ b/ssl.go @@ -5,7 +5,7 @@ import ( "os/exec" "path/filepath" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) const ( diff --git a/testing.go b/testing.go index 9851d9e..f2af466 100644 --- a/testing.go +++ b/testing.go @@ -7,7 +7,7 @@ import ( "os/exec" "path/filepath" - "forge.lthn.ai/core/go/pkg/cli" + "forge.lthn.ai/core/cli/pkg/cli" ) // TestOptions configures PHP test execution.