feat: add core-php binary, fix imports to use core/cli
Some checks failed
Security Scan / security (push) Successful in 9s
Test / test (push) Failing after 25s

- 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 <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-04 17:22:10 +00:00
parent d2081d91e7
commit 84ce94b713
23 changed files with 275 additions and 124 deletions

11
cmd.go
View file

@ -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 {

15
cmd/core-php/main.go Normal file
View file

@ -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),
)
}

View file

@ -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]}))

View file

@ -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()
},
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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)
},
}

View file

@ -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)

View file

@ -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"
)

View file

@ -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)

141
cmd_serve_frankenphp.go Normal file
View file

@ -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) }

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

2
go.mod
View file

@ -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
)

View file

@ -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

View file

@ -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.

2
php.go
View file

@ -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.

View file

@ -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.

View file

@ -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.

2
ssl.go
View file

@ -5,7 +5,7 @@ import (
"os/exec"
"path/filepath"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/cli/pkg/cli"
)
const (

View file

@ -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.