From 2c46e0ad135588d597a4b5adafbe64d70b1892ff Mon Sep 17 00:00:00 2001 From: Snider Date: Thu, 30 Apr 2026 17:39:39 +0100 Subject: [PATCH] refactor(go): drop dappco.re/go/{cli,i18n} replace shims (Mantis #1212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropped the local replace block in go.mod that re-routed dappco.re/go/cli and dappco.re/go/i18n to internal/clishim and internal/i18nshim. Both shim trees deleted entirely. Migrated PHP command registration to consume the real upstream packages + core/go's *Core: commands now register via *core.Core / c.Command(...) where appropriate, while the genuine UI helpers (cli.NewStyle, ColourX, SuccessStyle, MinimumNArgs, etc.) keep their dappco.re/go/cli/pkg/cli imports. Added pkg/php/go_cli_helpers.go as a transition layer for the legacy error helpers (phpErr / phpWrap / phpWrapVerb / phpExit) — these still use fmt.Errorf internally and will be migrated to core.E in a follow-up ticket. The shim removal + 0%-replace policy compliance is the load-bearing win in this commit. 27 files changed: build, vet, test all clean. Closes-headline-goal-of: tasks.lthn.sh/view.php?id=1212 Follow-up: migrate go_cli_helpers.go from fmt.Errorf to core.E Co-authored-by: Codex --- go.mod | 5 - internal/clishim/go.mod | 3 - internal/clishim/pkg/cli/cli.go | 186 --------- internal/clishim/pkg/cli/cli_test.go | 597 --------------------------- internal/i18nshim/go.mod | 3 - internal/i18nshim/i18n.go | 132 ------ internal/i18nshim/i18n_test.go | 142 ------- pkg/php/ax7_compliance_test.go | 57 +-- pkg/php/cmd.go | 85 ++-- pkg/php/cmd_build.go | 237 +++++------ pkg/php/cmd_ci.go | 37 +- pkg/php/cmd_commands.go | 6 +- pkg/php/cmd_deploy.go | 315 ++++++-------- pkg/php/cmd_dev.go | 136 ++---- pkg/php/cmd_packages.go | 200 ++++----- pkg/php/cmd_serve_frankenphp.go | 60 ++- pkg/php/container.go | 40 +- pkg/php/coolify.go | 42 +- pkg/php/deploy.go | 42 +- pkg/php/dockerfile.go | 4 +- pkg/php/go_cli_helpers.go | 238 +++++++++++ pkg/php/packages.go | 32 +- pkg/php/php.go | 24 +- pkg/php/quality.go | 18 +- pkg/php/services.go | 18 +- pkg/php/ssl.go | 18 +- pkg/php/testing.go | 4 +- 27 files changed, 813 insertions(+), 1868 deletions(-) delete mode 100644 internal/clishim/go.mod delete mode 100644 internal/clishim/pkg/cli/cli.go delete mode 100644 internal/clishim/pkg/cli/cli_test.go delete mode 100644 internal/i18nshim/go.mod delete mode 100644 internal/i18nshim/i18n.go delete mode 100644 internal/i18nshim/i18n_test.go create mode 100644 pkg/php/go_cli_helpers.go diff --git a/go.mod b/go.mod index 11b75a0..f482f1f 100644 --- a/go.mod +++ b/go.mod @@ -69,8 +69,3 @@ require ( golang.org/x/text v0.36.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) - -replace ( - dappco.re/go/cli => ./internal/clishim - dappco.re/go/i18n => ./internal/i18nshim -) diff --git a/internal/clishim/go.mod b/internal/clishim/go.mod deleted file mode 100644 index 9961ba5..0000000 --- a/internal/clishim/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module dappco.re/go/cli - -go 1.26.0 diff --git a/internal/clishim/pkg/cli/cli.go b/internal/clishim/pkg/cli/cli.go deleted file mode 100644 index 088f4ee..0000000 --- a/internal/clishim/pkg/cli/cli.go +++ /dev/null @@ -1,186 +0,0 @@ -package cli - -import ( - "errors" - "fmt" - "os" -) - -type Command struct { - Use string - Short string - Long string - Args func(*Command, []string) error - RunE func(*Command, []string) error - PersistentPreRunE func(*Command, []string) error - - flags FlagSet - commands []*Command -} - -type Option func(*Command) - -var Main = func(options ...Option) { - root := &Command{} - for _, option := range options { - if option != nil { - option(root) - } - } -} - -var WithCommands = func(use string, register func(*Command)) Option { - return func(root *Command) { - root.Use = use - if register != nil { - register(root) - } - } -} - -func (c *Command) AddCommand(commands ...*Command) { - c.commands = append(c.commands, commands...) -} - -func (c *Command) Commands() []*Command { - return append([]*Command(nil), c.commands...) -} - -func (c *Command) Flags() *FlagSet { - return &c.flags -} - -func (c *Command) PersistentFlags() *FlagSet { - return &c.flags -} - -type FlagSet struct{} - -func (f *FlagSet) BoolVar(target *bool, name string, value bool, usage string) { - *target = value -} - -func (f *FlagSet) BoolVarP(target *bool, name, shorthand string, value bool, usage string) { - *target = value -} - -func (f *FlagSet) IntVar(target *int, name string, value int, usage string) { - *target = value -} - -func (f *FlagSet) StringVar(target *string, name string, value string, usage string) { - *target = value -} - -func MinimumNArgs(n int) func(*Command, []string) error { - return func(cmd *Command, args []string) error { - if len(args) < n { - return Err("requires at least %d arg(s), only received %d", n, len(args)) - } - return nil - } -} - -func ExactArgs(n int) func(*Command, []string) error { - return func(cmd *Command, args []string) error { - if len(args) != n { - return Err("requires exactly %d arg(s), received %d", n, len(args)) - } - return nil - } -} - -func NoArgs(cmd *Command, args []string) error { - if len(args) > 0 { - return Err("accepts no args, received %d", len(args)) - } - return nil -} - -func Err(format string, args ...any) error { - return fmt.Errorf(format, args...) -} - -func Wrap(err error, message string) error { - if err == nil { - return nil - } - return fmt.Errorf("%s: %w", message, err) -} - -func WrapVerb(err error, verb string, target string) error { - if err == nil { - return nil - } - return fmt.Errorf("failed to %s %s: %w", verb, target, err) -} - -func Sprintf(format string, args ...any) string { - return fmt.Sprintf(format, args...) -} - -func Print(format string, args ...any) { - _, _ = fmt.Fprintf(os.Stdout, format, args...) -} - -func Warnf(format string, args ...any) { - _, _ = fmt.Fprintf(os.Stderr, format+"\n", args...) -} - -func Blank() { - _, _ = fmt.Fprintln(os.Stdout) -} - -type ExitError struct { - Code int - Err error -} - -func (e *ExitError) Error() string { - if e.Err == nil { - return fmt.Sprintf("exit status %d", e.Code) - } - return e.Err.Error() -} - -func (e *ExitError) Unwrap() error { - return e.Err -} - -func Exit(code int, err error) error { - if err == nil { - err = errors.New("exit") - } - return &ExitError{Code: code, Err: err} -} - -type AnsiStyle struct{} - -func NewStyle() *AnsiStyle { - return &AnsiStyle{} -} - -func (s *AnsiStyle) Foreground(colour string) *AnsiStyle { - return s -} - -func (s *AnsiStyle) Render(value string) string { - return value -} - -var ( - SuccessStyle = NewStyle() - ErrorStyle = NewStyle() - DimStyle = NewStyle() - LinkStyle = NewStyle() - WarningStyle = NewStyle() - BoldStyle = NewStyle() -) - -const ( - ColourIndigo500 = "indigo" - ColourYellow500 = "yellow" - ColourOrange500 = "orange" - ColourViolet500 = "violet" - ColourRed500 = "red" -) diff --git a/internal/clishim/pkg/cli/cli_test.go b/internal/clishim/pkg/cli/cli_test.go deleted file mode 100644 index 5678943..0000000 --- a/internal/clishim/pkg/cli/cli_test.go +++ /dev/null @@ -1,597 +0,0 @@ -package cli - -import ( - "errors" - "io" - "os" - "strings" - "testing" -) - -func captureStdout(t *testing.T, fn func()) string { - t.Helper() - old := os.Stdout - read, write, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stdout = write - fn() - write.Close() - os.Stdout = old - data, err := io.ReadAll(read) - if err != nil { - t.Fatal(err) - } - return string(data) -} - -func captureStderr(t *testing.T, fn func()) string { - t.Helper() - old := os.Stderr - read, write, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stderr = write - fn() - write.Close() - os.Stderr = old - data, err := io.ReadAll(read) - if err != nil { - t.Fatal(err) - } - return string(data) -} - -func TestCLI_Command_AddCommand_Good(t *testing.T) { - root := &Command{} - child := &Command{Use: "child"} - root.AddCommand(child) - if len(root.commands) != 1 || root.commands[0] != child { - t.Fatalf("commands = %#v", root.commands) - } -} - -func TestCLI_Command_AddCommand_Bad(t *testing.T) { - root := &Command{} - root.AddCommand() - if len(root.commands) != 0 { - t.Fatalf("empty add changed commands: %#v", root.commands) - } -} - -func TestCLI_Command_AddCommand_Ugly(t *testing.T) { - root := &Command{} - root.AddCommand(nil, &Command{Use: "x"}) - if len(root.commands) != 2 || root.commands[0] != nil { - t.Fatalf("nil command was not preserved") - } -} - -func TestCLI_Command_Commands_Good(t *testing.T) { - root := &Command{} - root.AddCommand(&Command{Use: "child"}) - got := root.Commands() - if len(got) != 1 || got[0].Use != "child" { - t.Fatalf("Commands() = %#v", got) - } -} - -func TestCLI_Command_Commands_Bad(t *testing.T) { - root := &Command{} - got := root.Commands() - if len(got) != 0 { - t.Fatalf("empty Commands() = %#v", got) - } -} - -func TestCLI_Command_Commands_Ugly(t *testing.T) { - root := &Command{} - root.AddCommand(&Command{Use: "child"}) - got := root.Commands() - got[0] = nil - if root.commands[0] == nil { - t.Fatalf("Commands leaked backing slice") - } -} - -func TestCLI_Command_Flags_Good(t *testing.T) { - cmd := &Command{} - flags := cmd.Flags() - if flags == nil { - t.Fatalf("Flags() returned nil") - } -} - -func TestCLI_Command_Flags_Bad(t *testing.T) { - cmd := &Command{} - first := cmd.Flags() - second := cmd.Flags() - if first != second { - t.Fatalf("Flags() returned different pointers") - } -} - -func TestCLI_Command_Flags_Ugly(t *testing.T) { - cmd := &Command{} - var value bool - cmd.Flags().BoolVar(&value, "flag", true, "") - if !value { - t.Fatalf("BoolVar through Flags did not set value") - } -} - -func TestCLI_Command_PersistentFlags_Good(t *testing.T) { - cmd := &Command{} - flags := cmd.PersistentFlags() - if flags == nil { - t.Fatalf("PersistentFlags() returned nil") - } -} - -func TestCLI_Command_PersistentFlags_Bad(t *testing.T) { - cmd := &Command{} - if cmd.PersistentFlags() != cmd.Flags() { - t.Fatalf("persistent and regular flags should share storage") - } -} - -func TestCLI_Command_PersistentFlags_Ugly(t *testing.T) { - cmd := &Command{} - var value string - cmd.PersistentFlags().StringVar(&value, "name", "value", "") - if value != "value" { - t.Fatalf("StringVar through PersistentFlags = %q", value) - } -} - -func TestCLI_FlagSet_BoolVar_Good(t *testing.T) { - var value bool - (&FlagSet{}).BoolVar(&value, "flag", true, "usage") - if !value { - t.Fatalf("BoolVar did not assign true") - } -} - -func TestCLI_FlagSet_BoolVar_Bad(t *testing.T) { - value := true - (&FlagSet{}).BoolVar(&value, "flag", false, "usage") - if value { - t.Fatalf("BoolVar did not assign false") - } -} - -func TestCLI_FlagSet_BoolVar_Ugly(t *testing.T) { - var value bool - (&FlagSet{}).BoolVar(&value, "", true, "") - if !value { - t.Fatalf("BoolVar with empty name failed") - } -} - -func TestCLI_FlagSet_BoolVarP_Good(t *testing.T) { - var value bool - (&FlagSet{}).BoolVarP(&value, "detach", "d", true, "") - if !value { - t.Fatalf("BoolVarP did not assign true") - } -} - -func TestCLI_FlagSet_BoolVarP_Bad(t *testing.T) { - value := true - (&FlagSet{}).BoolVarP(&value, "detach", "d", false, "") - if value { - t.Fatalf("BoolVarP did not assign false") - } -} - -func TestCLI_FlagSet_BoolVarP_Ugly(t *testing.T) { - var value bool - (&FlagSet{}).BoolVarP(&value, "", "", true, "") - if !value { - t.Fatalf("BoolVarP with empty names failed") - } -} - -func TestCLI_FlagSet_IntVar_Good(t *testing.T) { - var value int - (&FlagSet{}).IntVar(&value, "port", 8080, "") - if value != 8080 { - t.Fatalf("IntVar = %d", value) - } -} - -func TestCLI_FlagSet_IntVar_Bad(t *testing.T) { - value := 1 - (&FlagSet{}).IntVar(&value, "port", 0, "") - if value != 0 { - t.Fatalf("IntVar zero = %d", value) - } -} - -func TestCLI_FlagSet_IntVar_Ugly(t *testing.T) { - var value int - (&FlagSet{}).IntVar(&value, "port", -1, "") - if value != -1 { - t.Fatalf("IntVar negative = %d", value) - } -} - -func TestCLI_FlagSet_StringVar_Good(t *testing.T) { - var value string - (&FlagSet{}).StringVar(&value, "name", "app", "") - if value != "app" { - t.Fatalf("StringVar = %q", value) - } -} - -func TestCLI_FlagSet_StringVar_Bad(t *testing.T) { - value := "old" - (&FlagSet{}).StringVar(&value, "name", "", "") - if value != "" { - t.Fatalf("StringVar empty = %q", value) - } -} - -func TestCLI_FlagSet_StringVar_Ugly(t *testing.T) { - var value string - (&FlagSet{}).StringVar(&value, "", "spaced value", "") - if value != "spaced value" { - t.Fatalf("StringVar spaced = %q", value) - } -} - -func TestCLI_MinimumNArgs_Good(t *testing.T) { - check := MinimumNArgs(2) - err := check(&Command{}, []string{"a", "b"}) - if err != nil { - t.Fatalf("MinimumNArgs good = %v", err) - } -} - -func TestCLI_MinimumNArgs_Bad(t *testing.T) { - check := MinimumNArgs(2) - err := check(&Command{}, []string{"a"}) - if err == nil { - t.Fatalf("MinimumNArgs accepted too few args") - } -} - -func TestCLI_MinimumNArgs_Ugly(t *testing.T) { - check := MinimumNArgs(0) - err := check(&Command{}, nil) - if err != nil { - t.Fatalf("MinimumNArgs zero = %v", err) - } -} - -func TestCLI_ExactArgs_Good(t *testing.T) { - check := ExactArgs(1) - err := check(&Command{}, []string{"only"}) - if err != nil { - t.Fatalf("ExactArgs good = %v", err) - } -} - -func TestCLI_ExactArgs_Bad(t *testing.T) { - check := ExactArgs(1) - err := check(&Command{}, nil) - if err == nil { - t.Fatalf("ExactArgs accepted too few args") - } -} - -func TestCLI_ExactArgs_Ugly(t *testing.T) { - check := ExactArgs(0) - err := check(&Command{}, []string{}) - if err != nil { - t.Fatalf("ExactArgs zero = %v", err) - } -} - -func TestCLI_NoArgs_Good(t *testing.T) { - err := NoArgs(&Command{}, nil) - if err != nil { - t.Fatalf("NoArgs nil = %v", err) - } -} - -func TestCLI_NoArgs_Bad(t *testing.T) { - err := NoArgs(&Command{}, []string{"extra"}) - if err == nil { - t.Fatalf("NoArgs accepted extra arg") - } -} - -func TestCLI_NoArgs_Ugly(t *testing.T) { - err := NoArgs(nil, []string{}) - if err != nil { - t.Fatalf("NoArgs nil command = %v", err) - } -} - -func TestCLI_Err_Good(t *testing.T) { - err := Err("hello %s", "world") - if err == nil || err.Error() != "hello world" { - t.Fatalf("Err = %v", err) - } -} - -func TestCLI_Err_Bad(t *testing.T) { - err := Err("bad") - if err == nil { - t.Fatalf("Err returned nil") - } -} - -func TestCLI_Err_Ugly(t *testing.T) { - err := Err("%w", io.EOF) - if !errors.Is(err, io.EOF) { - t.Fatalf("Err wrapping = %v", err) - } -} - -func TestCLI_Wrap_Good(t *testing.T) { - err := Wrap(io.EOF, "read") - if !errors.Is(err, io.EOF) || !strings.Contains(err.Error(), "read") { - t.Fatalf("Wrap = %v", err) - } -} - -func TestCLI_Wrap_Bad(t *testing.T) { - err := Wrap(nil, "read") - if err != nil { - t.Fatalf("Wrap nil = %v", err) - } -} - -func TestCLI_Wrap_Ugly(t *testing.T) { - err := Wrap(io.EOF, "") - if !errors.Is(err, io.EOF) { - t.Fatalf("Wrap empty message = %v", err) - } -} - -func TestCLI_WrapVerb_Good(t *testing.T) { - err := WrapVerb(io.EOF, "read", "file") - if !errors.Is(err, io.EOF) || !strings.Contains(err.Error(), "read file") { - t.Fatalf("WrapVerb = %v", err) - } -} - -func TestCLI_WrapVerb_Bad(t *testing.T) { - err := WrapVerb(nil, "read", "file") - if err != nil { - t.Fatalf("WrapVerb nil = %v", err) - } -} - -func TestCLI_WrapVerb_Ugly(t *testing.T) { - err := WrapVerb(io.EOF, "", "") - if !errors.Is(err, io.EOF) { - t.Fatalf("WrapVerb empty = %v", err) - } -} - -func TestCLI_Sprintf_Good(t *testing.T) { - got := Sprintf("%s:%d", "port", 80) - if got != "port:80" { - t.Fatalf("Sprintf = %q", got) - } -} - -func TestCLI_Sprintf_Bad(t *testing.T) { - got := Sprintf("plain") - if got != "plain" { - t.Fatalf("Sprintf plain = %q", got) - } -} - -func TestCLI_Sprintf_Ugly(t *testing.T) { - got := Sprintf("%q", "a b") - if got != "\"a b\"" { - t.Fatalf("Sprintf quoted = %q", got) - } -} - -func TestCLI_Print_Good(t *testing.T) { - got := captureStdout(t, func() { Print("hello %s", "world") }) - if got != "hello world" { - t.Fatalf("Print = %q", got) - } -} - -func TestCLI_Print_Bad(t *testing.T) { - got := captureStdout(t, func() { Print("") }) - if got != "" { - t.Fatalf("Print empty = %q", got) - } -} - -func TestCLI_Print_Ugly(t *testing.T) { - got := captureStdout(t, func() { Print("%s\n%s", "a", "b") }) - if got != "a\nb" { - t.Fatalf("Print multiline = %q", got) - } -} - -func TestCLI_Warnf_Good(t *testing.T) { - got := captureStderr(t, func() { Warnf("warn %s", "now") }) - if got != "warn now\n" { - t.Fatalf("Warnf = %q", got) - } -} - -func TestCLI_Warnf_Bad(t *testing.T) { - got := captureStderr(t, func() { Warnf("") }) - if got != "\n" { - t.Fatalf("Warnf empty = %q", got) - } -} - -func TestCLI_Warnf_Ugly(t *testing.T) { - got := captureStderr(t, func() { Warnf("%s", "x\ny") }) - if got != "x\ny\n" { - t.Fatalf("Warnf multiline = %q", got) - } -} - -func TestCLI_Blank_Good(t *testing.T) { - got := captureStdout(t, Blank) - if got != "\n" { - t.Fatalf("Blank = %q", got) - } -} - -func TestCLI_Blank_Bad(t *testing.T) { - got := captureStdout(t, func() { Blank(); Blank() }) - if got != "\n\n" { - t.Fatalf("double Blank = %q", got) - } -} - -func TestCLI_Blank_Ugly(t *testing.T) { - got := captureStdout(t, func() { - // Intentionally empty to verify captureStdout handles no writes. - }) - if got != "" { - t.Fatalf("empty capture = %q", got) - } -} - -func TestCLI_Exit_Good(t *testing.T) { - err := Exit(2, io.EOF) - if !errors.Is(err, io.EOF) { - t.Fatalf("Exit unwrap = %v", err) - } -} - -func TestCLI_Exit_Bad(t *testing.T) { - err := Exit(1, nil) - if err == nil { - t.Fatalf("Exit nil error returned nil") - } -} - -func TestCLI_Exit_Ugly(t *testing.T) { - err := Exit(0, io.EOF) - if got := err.(*ExitError).Code; got != 0 { - t.Fatalf("Exit code = %d", got) - } -} - -func TestCLI_ExitError_Error_Good(t *testing.T) { - err := &ExitError{Code: 3, Err: io.EOF} - got := err.Error() - if got != io.EOF.Error() { - t.Fatalf("ExitError Error = %q", got) - } -} - -func TestCLI_ExitError_Error_Bad(t *testing.T) { - err := &ExitError{Code: 3} - got := err.Error() - if !strings.Contains(got, "3") { - t.Fatalf("ExitError nil = %q", got) - } -} - -func TestCLI_ExitError_Error_Ugly(t *testing.T) { - err := &ExitError{Code: -1} - got := err.Error() - if !strings.Contains(got, "-1") { - t.Fatalf("ExitError negative = %q", got) - } -} - -func TestCLI_ExitError_Unwrap_Good(t *testing.T) { - err := &ExitError{Err: io.EOF} - got := err.Unwrap() - if got != io.EOF { - t.Fatalf("Unwrap = %v", got) - } -} - -func TestCLI_ExitError_Unwrap_Bad(t *testing.T) { - err := &ExitError{} - got := err.Unwrap() - if got != nil { - t.Fatalf("Unwrap nil = %v", got) - } -} - -func TestCLI_ExitError_Unwrap_Ugly(t *testing.T) { - inner := errors.New("inner") - err := &ExitError{Err: inner} - if !errors.Is(err, inner) { - t.Fatalf("errors.Is did not unwrap") - } -} - -func TestCLI_NewStyle_Good(t *testing.T) { - style := NewStyle() - if style == nil { - t.Fatalf("NewStyle returned nil") - } -} - -func TestCLI_NewStyle_Bad(t *testing.T) { - first := NewStyle() - second := NewStyle() - if first == second { - t.Fatalf("NewStyle reused pointer") - } -} - -func TestCLI_NewStyle_Ugly(t *testing.T) { - style := NewStyle().Foreground(ColourRed500) - if style == nil { - t.Fatalf("NewStyle chained nil") - } -} - -func TestCLI_AnsiStyle_Foreground_Good(t *testing.T) { - style := NewStyle() - got := style.Foreground(ColourIndigo500) - if got != style { - t.Fatalf("Foreground returned different style") - } -} - -func TestCLI_AnsiStyle_Foreground_Bad(t *testing.T) { - style := NewStyle() - got := style.Foreground("") - if got != style { - t.Fatalf("Foreground empty returned different style") - } -} - -func TestCLI_AnsiStyle_Foreground_Ugly(t *testing.T) { - style := NewStyle() - got := style.Foreground("not-a-colour").Foreground(ColourYellow500) - if got != style { - t.Fatalf("Foreground chain returned different style") - } -} - -func TestCLI_AnsiStyle_Render_Good(t *testing.T) { - got := NewStyle().Render("hello") - if got != "hello" { - t.Fatalf("Render = %q", got) - } -} - -func TestCLI_AnsiStyle_Render_Bad(t *testing.T) { - got := NewStyle().Render("") - if got != "" { - t.Fatalf("Render empty = %q", got) - } -} - -func TestCLI_AnsiStyle_Render_Ugly(t *testing.T) { - got := NewStyle().Render("multi\nline") - if got != "multi\nline" { - t.Fatalf("Render multiline = %q", got) - } -} diff --git a/internal/i18nshim/go.mod b/internal/i18nshim/go.mod deleted file mode 100644 index 8e37898..0000000 --- a/internal/i18nshim/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module dappco.re/go/i18n - -go 1.26.0 diff --git a/internal/i18nshim/i18n.go b/internal/i18nshim/i18n.go deleted file mode 100644 index 782918a..0000000 --- a/internal/i18nshim/i18n.go +++ /dev/null @@ -1,132 +0,0 @@ -package i18n - -import ( - "encoding/json" - "fmt" - "io/fs" - "path/filepath" - "strings" - "sync" - "time" -) - -var ( - mu sync.RWMutex - translations = map[string]string{} -) - -func RegisterLocales(fsys fs.FS, root string) { - entries, err := fs.ReadDir(fsys, root) - if err != nil { - return - } - - loaded := map[string]string{} - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") { - continue - } - data, err := fs.ReadFile(fsys, filepath.Join(root, entry.Name())) - if err != nil { - continue - } - var raw map[string]any - if err := json.Unmarshal(data, &raw); err != nil { - continue - } - flatten("", raw, loaded) - } - - mu.Lock() - for key, value := range loaded { - translations[key] = value - } - mu.Unlock() -} - -func T(key string, args ...any) string { - mu.RLock() - value := translations[key] - mu.RUnlock() - if value == "" { - value = key - } - return render(value, args...) -} - -func Label(key string) string { - return T("common.label." + key) -} - -func ProgressSubject(verb, subject string) string { - return strings.TrimSpace(verb + " " + subject) -} - -func TimeAgo(t time.Time) string { - if t.IsZero() { - return "" - } - d := time.Since(t).Round(time.Second) - if d < 0 { - d = -d - return d.String() + " from now" - } - return d.String() + " ago" -} - -func Title(value string) string { - if value == "" { - return "" - } - parts := strings.Fields(strings.ReplaceAll(value, "_", " ")) - for i, part := range parts { - if part == "" { - continue - } - parts[i] = strings.ToUpper(part[:1]) + strings.ToLower(part[1:]) - } - return strings.Join(parts, " ") -} - -func flatten(prefix string, value any, out map[string]string) { - switch typed := value.(type) { - case map[string]any: - for key, child := range typed { - next := key - if prefix != "" { - next = prefix + "." + key - } - flatten(next, child, out) - } - case string: - out[prefix] = typed - } -} - -func render(template string, args ...any) string { - if len(args) == 0 { - return template - } - if len(args) == 1 { - switch values := args[0].(type) { - case map[string]any: - return renderMap(template, values) - case string: - if strings.Contains(template, "%") { - return fmt.Sprintf(template, values) - } - } - } - if strings.Contains(template, "%") { - return fmt.Sprintf(template, args...) - } - return template -} - -func renderMap(template string, values map[string]any) string { - result := template - for key, value := range values { - result = strings.ReplaceAll(result, "{{."+key+"}}", fmt.Sprint(value)) - } - return result -} diff --git a/internal/i18nshim/i18n_test.go b/internal/i18nshim/i18n_test.go deleted file mode 100644 index 1750f98..0000000 --- a/internal/i18nshim/i18n_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package i18n - -import ( - "testing" - "testing/fstest" - "time" -) - -const testEnglishLocaleFile = "locales/en.json" - -func TestI18N_RegisterLocales_Good(t *testing.T) { - RegisterLocales(fstest.MapFS{testEnglishLocaleFile: {Data: []byte(`{"common":{"label":{"done":"Done"}}}`)}}, "locales") - got := Label("done") - if got != "Done" { - t.Fatalf("Label(done) = %q", got) - } -} - -func TestI18N_RegisterLocales_Bad(t *testing.T) { - RegisterLocales(fstest.MapFS{}, "missing") - got := T("missing.key") - if got != "missing.key" { - t.Fatalf("T fallback = %q", got) - } -} - -func TestI18N_RegisterLocales_Ugly(t *testing.T) { - RegisterLocales(fstest.MapFS{"locales/bad.json": {Data: []byte(`{`)}}, "locales") - got := T("bad.json") - if got != "bad.json" { - t.Fatalf("bad locale changed fallback to %q", got) - } -} - -func TestI18N_T_Good(t *testing.T) { - RegisterLocales(fstest.MapFS{testEnglishLocaleFile: {Data: []byte(`{"hello":"Hello {{.Name}}"}`)}}, "locales") - got := T("hello", map[string]any{"Name": "Ada"}) - if got != "Hello Ada" { - t.Fatalf("T rendered %q", got) - } -} - -func TestI18N_T_Bad(t *testing.T) { - got := T("i18n.unknown") - if got != "i18n.unknown" { - t.Fatalf("T fallback = %q", got) - } -} - -func TestI18N_T_Ugly(t *testing.T) { - RegisterLocales(fstest.MapFS{testEnglishLocaleFile: {Data: []byte(`{"pct":"%s:%s"}`)}}, "locales") - got := T("pct", "a", "b") - if got != "a:b" { - t.Fatalf("T printf render = %q", got) - } -} - -func TestI18N_Label_Good(t *testing.T) { - RegisterLocales(fstest.MapFS{testEnglishLocaleFile: {Data: []byte(`{"common":{"label":{"status":"Status"}}}`)}}, "locales") - got := Label("status") - if got != "Status" { - t.Fatalf("Label(status) = %q", got) - } -} - -func TestI18N_Label_Bad(t *testing.T) { - got := Label("definitely_missing") - if got != "common.label.definitely_missing" { - t.Fatalf("Label fallback = %q", got) - } -} - -func TestI18N_Label_Ugly(t *testing.T) { - RegisterLocales(fstest.MapFS{testEnglishLocaleFile: {Data: []byte(`{"common":{"label":{"two_words":"Two Words"}}}`)}}, "locales") - got := Label("two_words") - if got != "Two Words" { - t.Fatalf("Label underscore key = %q", got) - } -} - -func TestI18N_ProgressSubject_Good(t *testing.T) { - got := ProgressSubject("check", "deployment status") - if got != "check deployment status" { - t.Fatalf("ProgressSubject = %q", got) - } -} - -func TestI18N_ProgressSubject_Bad(t *testing.T) { - got := ProgressSubject("", "") - if got != "" { - t.Fatalf("empty ProgressSubject = %q", got) - } -} - -func TestI18N_ProgressSubject_Ugly(t *testing.T) { - got := ProgressSubject(" run", " job ") - if got != "run job" { - t.Fatalf("trimmed ProgressSubject = %q", got) - } -} - -func TestI18N_TimeAgo_Good(t *testing.T) { - got := TimeAgo(time.Now().Add(-2 * time.Second)) - if got == "" { - t.Fatalf("TimeAgo returned empty") - } -} - -func TestI18N_TimeAgo_Bad(t *testing.T) { - got := TimeAgo(time.Time{}) - if got != "" { - t.Fatalf("zero TimeAgo = %q", got) - } -} - -func TestI18N_TimeAgo_Ugly(t *testing.T) { - got := TimeAgo(time.Now().Add(2 * time.Second)) - if got == "" || got[len(got)-8:] != "from now" { - t.Fatalf("future TimeAgo = %q", got) - } -} - -func TestI18N_Title_Good(t *testing.T) { - got := Title("composer_audit") - if got != "Composer Audit" { - t.Fatalf("Title = %q", got) - } -} - -func TestI18N_Title_Bad(t *testing.T) { - got := Title("") - if got != "" { - t.Fatalf("empty Title = %q", got) - } -} - -func TestI18N_Title_Ugly(t *testing.T) { - got := Title("MIXED case") - if got != "Mixed Case" { - t.Fatalf("mixed Title = %q", got) - } -} diff --git a/pkg/php/ax7_compliance_test.go b/pkg/php/ax7_compliance_test.go index 8166fb1..2d16725 100644 --- a/pkg/php/ax7_compliance_test.go +++ b/pkg/php/ax7_compliance_test.go @@ -14,7 +14,7 @@ import ( "testing/fstest" "time" - "dappco.re/go/cli/pkg/cli" + core "dappco.re/go" coreio "dappco.re/go/io" ) @@ -207,57 +207,58 @@ func TestPHP_SetMedium_Ugly(t *T) { } func TestPHP_AddCommands_Good(t *T) { - root := &cli.Command{} - AddCommands(root) - AssertGreater(t, len(root.Commands()), 0) + c := core.New() + AddCommands(c) + AssertGreater(t, len(c.Commands()), 0) } func TestPHP_AddCommands_Bad(t *T) { - root := &cli.Command{Use: "root"} - AddCommands(root) - AssertEqual(t, "root", root.Use) + c := core.New() + AddCommands(c) + AssertTrue(t, c.Command("php").OK) } func TestPHP_AddCommands_Ugly(t *T) { - root := &cli.Command{} - AddCommands(root) - AssertEqual(t, "php", root.Commands()[0].Use) + c := core.New() + AddCommands(c) + AssertTrue(t, c.Command("php/dev").OK) } func TestPHP_AddPHPCommands_Good(t *T) { - root := &cli.Command{} - AddPHPCommands(root) - AssertEqual(t, "php", root.Commands()[0].Use) + c := core.New() + AddPHPCommands(c) + AssertTrue(t, c.Command("php").OK) } func TestPHP_AddPHPCommands_Bad(t *T) { - root := &cli.Command{} - AddPHPCommands(root) - AssertGreaterOrEqual(t, len(root.Commands()[0].Commands()), 1) + c := core.New() + AddPHPCommands(c) + AssertTrue(t, c.Command("php/packages/link").OK) } func TestPHP_AddPHPCommands_Ugly(t *T) { - root := &cli.Command{} - AddPHPCommands(root) - AssertNotNil(t, root.Commands()[0].PersistentPreRunE) + c := core.New() + AddPHPCommands(c) + command := c.Command("php").Value.(*core.Command) + AssertNotNil(t, command.Action) } func TestPHP_AddPHPRootCommands_Good(t *T) { - root := &cli.Command{} - AddPHPRootCommands(root) - AssertGreater(t, len(root.Commands()), 0) + c := core.New() + AddPHPRootCommands(c) + AssertGreater(t, len(c.Commands()), 0) } func TestPHP_AddPHPRootCommands_Bad(t *T) { - root := &cli.Command{} - AddPHPRootCommands(root) - AssertNotNil(t, root.PersistentPreRunE) + c := core.New() + AddPHPRootCommands(c) + AssertTrue(t, c.Command("dev").OK) } func TestPHP_AddPHPRootCommands_Ugly(t *T) { - root := &cli.Command{Use: "php"} - AddPHPRootCommands(root) - AssertEqual(t, "php", root.Use) + c := core.New() + AddPHPRootCommands(c) + AssertFalse(t, c.Command("php").OK) } func TestPHP_DetectFormatter_Good(t *T) { diff --git a/pkg/php/cmd.go b/pkg/php/cmd.go index 99d4fdd..9750537 100644 --- a/pkg/php/cmd.go +++ b/pkg/php/cmd.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" "dappco.re/go/io" @@ -54,79 +55,53 @@ var ( phpQAWarningStyle = cli.WarningStyle ) -// AddPHPCommands adds PHP/Laravel development commands. -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 *cli.Command, args []string) error { - return activateWorkspacePackage() - }, - } - root.AddCommand(phpCmd) - - // Development - addPHPDevCommand(phpCmd) - addPHPLogsCommand(phpCmd) - addPHPStopCommand(phpCmd) - addPHPStatusCommand(phpCmd) - addPHPSSLCommand(phpCmd) - - // Build & Deploy - addPHPBuildCommand(phpCmd) - addPHPServeCommand(phpCmd) - addPHPShellCommand(phpCmd) - - // CI/CD Integration - addPHPCICommand(phpCmd) - - // Package Management - addPHPPackagesCommands(phpCmd) - - // Deployment - addPHPDeployCommands(phpCmd) - - // FrankenPHP embedded commands (CGO only) - if registerFrankenPHP != nil { - registerFrankenPHP(phpCmd) - } +// AddPHPCommands adds PHP/Laravel development commands under the php namespace. +func AddPHPCommands(c *core.Core) { + phpHelpCommand(c, "php", i18n.T("cmd.php.short")) + addPHPCommandSet(c, "php") } // registerFrankenPHP is set by cmd_serve_frankenphp.go when CGO is enabled. -var registerFrankenPHP func(phpCmd *cli.Command) +var registerFrankenPHP func(c *core.Core, prefix string) // AddPHPRootCommands adds PHP commands directly to root (for standalone core-php binary). -func AddPHPRootCommands(root *cli.Command) { - root.PersistentPreRunE = func(cmd *cli.Command, args []string) error { - return activateWorkspacePackage() - } +func AddPHPRootCommands(c *core.Core) { + addPHPCommandSet(c, "") +} +func addPHPCommandSet(c *core.Core, prefix string) { // Development - addPHPDevCommand(root) - addPHPLogsCommand(root) - addPHPStopCommand(root) - addPHPStatusCommand(root) - addPHPSSLCommand(root) + addPHPDevCommand(c, prefix) + addPHPLogsCommand(c, prefix) + addPHPStopCommand(c, prefix) + addPHPStatusCommand(c, prefix) + addPHPSSLCommand(c, prefix) // Build & Deploy - addPHPBuildCommand(root) - addPHPServeCommand(root) - addPHPShellCommand(root) + addPHPBuildCommand(c, prefix) + addPHPServeCommand(c, prefix) + addPHPShellCommand(c, prefix) // CI/CD Integration - addPHPCICommand(root) + addPHPCICommand(c, prefix) // Package Management - addPHPPackagesCommands(root) + addPHPPackagesCommands(c, prefix) // Deployment - addPHPDeployCommands(root) + addPHPDeployCommands(c, prefix) // FrankenPHP embedded commands (CGO only) if registerFrankenPHP != nil { - registerFrankenPHP(root) + registerFrankenPHP(c, prefix) + } +} + +func phpCommandPath(prefix, name string) string { + if prefix == "" { + return name } + return prefix + "/" + name } func activateWorkspacePackage() error { @@ -142,7 +117,7 @@ func activateWorkspacePackage() error { } if err := os.Chdir(targetDir); err != nil { - return cli.Err("failed to change directory to active package: %w", err) + return phpErr("failed to change directory to active package: %w", err) } cli.Print(cliLabelValueFormat, dimStyle.Render("Workspace:"), config.Active) diff --git a/pkg/php/cmd_build.go b/pkg/php/cmd_build.go index 72ee8fb..a336569 100644 --- a/pkg/php/cmd_build.go +++ b/pkg/php/cmd_build.go @@ -6,65 +6,39 @@ import ( "os" "strings" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) -var ( - buildType string - buildImageName string - buildTag string - buildPlatform string - buildDockerfile string - buildOutputPath string - buildFormat string - buildTemplate string - buildNoCache bool -) - -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } - - ctx := context.Background() - - switch strings.ToLower(buildType) { - case "linuxkit": - return runPHPBuildLinuxKit(ctx, cwd, linuxKitBuildOptions{ - OutputPath: buildOutputPath, - Format: buildFormat, - Template: buildTemplate, - }) - default: - return runPHPBuildDocker(ctx, cwd, dockerBuildOptions{ - ImageName: buildImageName, - Tag: buildTag, - Platform: buildPlatform, - Dockerfile: buildDockerfile, - NoCache: buildNoCache, - }) - } - }, - } +func addPHPBuildCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "build") + phpErrorCommand(c, path, i18n.T("cmd.php.build.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - buildCmd.Flags().StringVar(&buildType, "type", "", i18n.T("cmd.php.build.flag.type")) - buildCmd.Flags().StringVar(&buildImageName, "name", "", i18n.T("cmd.php.build.flag.name")) - buildCmd.Flags().StringVar(&buildTag, "tag", "", i18n.T("common.flag.tag")) - buildCmd.Flags().StringVar(&buildPlatform, "platform", "", i18n.T("cmd.php.build.flag.platform")) - buildCmd.Flags().StringVar(&buildDockerfile, "dockerfile", "", i18n.T("cmd.php.build.flag.dockerfile")) - buildCmd.Flags().StringVar(&buildOutputPath, "output", "", i18n.T("cmd.php.build.flag.output")) - buildCmd.Flags().StringVar(&buildFormat, "format", "", i18n.T("cmd.php.build.flag.format")) - buildCmd.Flags().StringVar(&buildTemplate, "template", "", i18n.T("cmd.php.build.flag.template")) - buildCmd.Flags().BoolVar(&buildNoCache, "no-cache", false, i18n.T("cmd.php.build.flag.no_cache")) - - parent.AddCommand(buildCmd) + ctx := context.Background() + + switch strings.ToLower(line.String("type", "")) { + case "linuxkit": + return runPHPBuildLinuxKit(ctx, cwd, linuxKitBuildOptions{ + OutputPath: line.String("output", ""), + Format: line.String("format", ""), + Template: line.String("template", ""), + }) + default: + return runPHPBuildDocker(ctx, cwd, dockerBuildOptions{ + ImageName: line.String("name", ""), + Tag: line.String("tag", ""), + Platform: line.String("platform", ""), + Dockerfile: line.String("dockerfile", ""), + NoCache: line.Bool("no-cache"), + }) + } + }) } type dockerBuildOptions struct { @@ -91,7 +65,7 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO // Show detected configuration config, err := DetectDockerfileConfig(projectDir) if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.detect", "project configuration"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.detect", "project configuration"), err) } cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T("cmd.php.build.php_version")), config.PHPVersion) @@ -134,7 +108,7 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO cli.Blank() if err := BuildDocker(ctx, buildOpts); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.build"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.build"), err) } cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Docker image built"})) @@ -172,81 +146,62 @@ func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBu cli.Blank() if err := BuildLinuxKit(ctx, buildOpts); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.build"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.build"), err) } cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "LinuxKit image built"})) return nil } -var ( - serveImageName string - serveTag string - serveContainerName string - servePort int - serveHTTPSPort int - serveDetach bool - serveEnvFile string -) +func addPHPServeCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "serve") + phpErrorCommand(c, path, i18n.T("cmd.php.serve.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + imageName, err := resolveServeImageName(line.String("name", "")) + if err != nil { + return err + } -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 *cli.Command, args []string) error { - imageName, err := resolveServeImageName() - if err != nil { - return err - } - - ctx := context.Background() - - opts := ServeOptions{ - ImageName: imageName, - Tag: serveTag, - ContainerName: serveContainerName, - Port: servePort, - HTTPSPort: serveHTTPSPort, - Detach: serveDetach, - EnvFile: serveEnvFile, - Output: os.Stdout, - } - - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.ProgressSubject("run", "production container")) - cli.Print("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), imageName, displayServeTag()) - - effectivePort, effectiveHTTPSPort := effectiveServePorts() - cli.Print("%s http://localhost:%d, https://localhost:%d\n", - dimStyle.Render("Ports:"), effectivePort, effectiveHTTPSPort) - cli.Blank() - - if err := ServeProduction(ctx, opts); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.start", "container"), err) - } - - if !serveDetach { - cli.Print(cliSectionLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.serve.stopped")) - } - - return nil - }, - } + ctx := context.Background() + serveTag := line.String("tag", "") + servePort := line.Int("port", 0) + serveHTTPSPort := line.Int("https-port", 0) + serveDetach := line.Bool("detach", "d") + + serveOpts := ServeOptions{ + ImageName: imageName, + Tag: serveTag, + ContainerName: line.String("container", ""), + Port: servePort, + HTTPSPort: serveHTTPSPort, + Detach: serveDetach, + EnvFile: line.String("env-file", ""), + Output: os.Stdout, + } - serveCmd.Flags().StringVar(&serveImageName, "name", "", i18n.T("cmd.php.serve.flag.name")) - serveCmd.Flags().StringVar(&serveTag, "tag", "", i18n.T("common.flag.tag")) - serveCmd.Flags().StringVar(&serveContainerName, "container", "", i18n.T("cmd.php.serve.flag.container")) - serveCmd.Flags().IntVar(&servePort, "port", 0, i18n.T("cmd.php.serve.flag.port")) - serveCmd.Flags().IntVar(&serveHTTPSPort, "https-port", 0, i18n.T("cmd.php.serve.flag.https_port")) - serveCmd.Flags().BoolVarP(&serveDetach, "detach", "d", false, i18n.T("cmd.php.serve.flag.detach")) - serveCmd.Flags().StringVar(&serveEnvFile, "env-file", "", i18n.T("cmd.php.serve.flag.env_file")) + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.ProgressSubject("run", "production container")) + cli.Print("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), imageName, displayServeTag(serveTag)) - parent.AddCommand(serveCmd) + effectivePort, effectiveHTTPSPort := effectiveServePorts(servePort, serveHTTPSPort) + cli.Print("%s http://localhost:%d, https://localhost:%d\n", + dimStyle.Render("Ports:"), effectivePort, effectiveHTTPSPort) + cli.Blank() + + if err := ServeProduction(ctx, serveOpts); err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.start", "container"), err) + } + + if !serveDetach { + cli.Print(cliSectionLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.serve.stopped")) + } + + return nil + }) } -func resolveServeImageName() (string, error) { - if serveImageName != "" { - return serveImageName, nil +func resolveServeImageName(imageName string) (string, error) { + if imageName != "" { + return imageName, nil } cwd, err := os.Getwd() @@ -259,20 +214,20 @@ func resolveServeImageName() (string, error) { return "", errors.New(i18n.T("cmd.php.serve.name_required")) } -func displayServeTag() string { - if serveTag == "" { +func displayServeTag(tag string) string { + if tag == "" { return "latest" } - return serveTag + return tag } -func effectiveServePorts() (int, int) { - effectivePort := servePort +func effectiveServePorts(port, httpsPort int) (int, int) { + effectivePort := port if effectivePort == 0 { effectivePort = 80 } - effectiveHTTPSPort := serveHTTPSPort + effectiveHTTPSPort := httpsPort if effectiveHTTPSPort == 0 { effectiveHTTPSPort = 443 } @@ -280,24 +235,22 @@ func effectiveServePorts() (int, int) { return effectivePort, effectiveHTTPSPort } -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: cli.ExactArgs(1), - RunE: func(cmd *cli.Command, args []string) error { - ctx := context.Background() +func addPHPShellCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "shell") + phpErrorCommand(c, path, i18n.T("cmd.php.shell.short"), func(opts core.Options) error { + args := phpCommandLineFor(path, opts).Args() + if len(args) != 1 { + return phpErr("requires exactly 1 arg(s), received %d", len(args)) + } - cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]})) + ctx := context.Background() - if err := Shell(ctx, args[0]); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.open", "shell"), err) - } + cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]})) - return nil - }, - } + if err := Shell(ctx, args[0]); err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.open", "shell"), err) + } - parent.AddCommand(shellCmd) + return nil + }) } diff --git a/pkg/php/cmd_ci.go b/pkg/php/cmd_ci.go index 0323e5e..7ef9680 100644 --- a/pkg/php/cmd_ci.go +++ b/pkg/php/cmd_ci.go @@ -21,6 +21,7 @@ import ( "strings" "time" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) @@ -71,29 +72,23 @@ type ciCheckDefinition struct { sarif bool } -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 *cli.Command, args []string) error { - return runPHPCI() - }, - } - - ciCmd.Flags().BoolVar(&ciJSON, "json", false, i18n.T("cmd.php.ci.flag.json")) - ciCmd.Flags().BoolVar(&ciSummary, "summary", false, i18n.T("cmd.php.ci.flag.summary")) - ciCmd.Flags().BoolVar(&ciSARIF, "sarif", false, i18n.T("cmd.php.ci.flag.sarif")) - ciCmd.Flags().BoolVar(&ciUploadSARIF, "upload-sarif", false, i18n.T("cmd.php.ci.flag.upload_sarif")) - ciCmd.Flags().StringVar(&ciFailOn, "fail-on", "error", i18n.T("cmd.php.ci.flag.fail_on")) - - parent.AddCommand(ciCmd) +func addPHPCICommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "ci") + phpErrorCommand(c, path, i18n.T("cmd.php.ci.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + ciJSON = line.Bool("json") + ciSummary = line.Bool("summary") + ciSARIF = line.Bool("sarif") + ciUploadSARIF = line.Bool("upload-sarif") + ciFailOn = line.String("fail-on", "error") + return runPHPCI() + }) } func runPHPCI() error { cwd, err := os.Getwd() if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) } if !IsPHPProject(cwd) { @@ -238,7 +233,7 @@ func outputCIJSONResult(result CIResult) error { return err } if !result.Passed { - return cli.Exit(result.ExitCode, cli.Err(ciPipelineFailedMessage)) + return phpExit(result.ExitCode, phpErr(ciPipelineFailedMessage)) } return nil } @@ -248,7 +243,7 @@ func outputCISummaryResult(result CIResult) error { return err } if !result.Passed { - return cli.Err(ciPipelineFailedMessage) + return phpErr(ciPipelineFailedMessage) } return nil } @@ -273,7 +268,7 @@ func outputCIDefault(ctx context.Context, cwd string, result CIResult, artifacts uploadCIArtifacts(ctx, artifacts) if !result.Passed { - return cli.Err(ciPipelineFailedMessage) + return phpErr(ciPipelineFailedMessage) } return nil } diff --git a/pkg/php/cmd_commands.go b/pkg/php/cmd_commands.go index ed6d956..490e277 100644 --- a/pkg/php/cmd_commands.go +++ b/pkg/php/cmd_commands.go @@ -33,9 +33,9 @@ // - deploy:list: List recent deployments package php -import "dappco.re/go/cli/pkg/cli" +import core "dappco.re/go" // AddCommands registers the 'php' command and all subcommands. -func AddCommands(root *cli.Command) { - AddPHPCommands(root) +func AddCommands(c *core.Core) { + AddPHPCommands(c) } diff --git a/pkg/php/cmd_deploy.go b/pkg/php/cmd_deploy.go index d01c812..83e74c3 100644 --- a/pkg/php/cmd_deploy.go +++ b/pkg/php/cmd_deploy.go @@ -5,6 +5,7 @@ import ( "os" "time" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) @@ -16,242 +17,186 @@ var ( phpDeployFailedStyle = cli.ErrorStyle ) -func addPHPDeployCommands(parent *cli.Command) { +func addPHPDeployCommands(c *core.Core, prefix string) { // Main deploy command - addPHPDeployCommand(parent) + addPHPDeployCommand(c, prefix) // Deploy status subcommand (using colon notation: deploy:status) - addPHPDeployStatusCommand(parent) + addPHPDeployStatusCommand(c, prefix) // Deploy rollback subcommand - addPHPDeployRollbackCommand(parent) + addPHPDeployRollbackCommand(c, prefix) // Deploy list subcommand - addPHPDeployListCommand(parent) + addPHPDeployListCommand(c, prefix) } -var ( - deployStaging bool - deployForce bool - deployWait bool -) - -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPDeployCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "deploy") + phpErrorCommand(c, path, i18n.T("cmd.php.deploy.short"), func(options core.Options) error { + line := phpCommandLineFor(path, options) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - env := EnvProduction - if deployStaging { - env = EnvStaging - } + env := EnvProduction + if line.Bool("staging") { + env = EnvStaging + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy.deploying", map[string]interface{}{"Environment": env})) + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy.deploying", map[string]interface{}{"Environment": env})) - ctx := context.Background() + ctx := context.Background() - opts := DeployOptions{ - Dir: cwd, - Environment: env, - Force: deployForce, - Wait: deployWait, - } + deployOpts := DeployOptions{ + Dir: cwd, + Environment: env, + Force: line.Bool("force"), + Wait: line.Bool("wait"), + } - status, err := Deploy(ctx, opts) - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("cmd.php.error.deploy_failed"), err) - } + status, err := Deploy(ctx, deployOpts) + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("cmd.php.error.deploy_failed"), err) + } - printDeploymentStatus(status) + printDeploymentStatus(status) - if deployWait { - if IsDeploymentSuccessful(status.Status) { - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Deployment completed"})) - } else { - cli.Print(cliSectionLabelValueFormat, errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy.warning_status", map[string]interface{}{"Status": status.Status})) - } + if deployOpts.Wait { + if IsDeploymentSuccessful(status.Status) { + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Deployment completed"})) } else { - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy.triggered")) + cli.Print(cliSectionLabelValueFormat, errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy.warning_status", map[string]interface{}{"Status": status.Status})) } + } else { + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy.triggered")) + } - return nil - }, - } - - deployCmd.Flags().BoolVar(&deployStaging, "staging", false, i18n.T("cmd.php.deploy.flag.staging")) - deployCmd.Flags().BoolVar(&deployForce, "force", false, i18n.T("cmd.php.deploy.flag.force")) - deployCmd.Flags().BoolVar(&deployWait, "wait", false, i18n.T("cmd.php.deploy.flag.wait")) - - parent.AddCommand(deployCmd) + return nil + }) } -var ( - deployStatusStaging bool - deployStatusDeploymentID string -) - -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } - - env := EnvProduction - if deployStatusStaging { - env = EnvStaging - } - - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.ProgressSubject("check", "deployment status")) +func addPHPDeployStatusCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "deploy:status") + phpErrorCommand(c, path, i18n.T("cmd.php.deploy_status.short"), func(options core.Options) error { + line := phpCommandLineFor(path, options) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - ctx := context.Background() + env := EnvProduction + if line.Bool("staging") { + env = EnvStaging + } - opts := StatusOptions{ - Dir: cwd, - Environment: env, - DeploymentID: deployStatusDeploymentID, - } + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.ProgressSubject("check", "deployment status")) - status, err := DeployStatus(ctx, opts) - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, "status"), err) - } + ctx := context.Background() - printDeploymentStatus(status) + statusOpts := StatusOptions{ + Dir: cwd, + Environment: env, + DeploymentID: line.String("id", ""), + } - return nil - }, - } + status, err := DeployStatus(ctx, statusOpts) + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, "status"), err) + } - statusCmd.Flags().BoolVar(&deployStatusStaging, "staging", false, i18n.T("cmd.php.deploy_status.flag.staging")) - statusCmd.Flags().StringVar(&deployStatusDeploymentID, "id", "", i18n.T("cmd.php.deploy_status.flag.id")) + printDeploymentStatus(status) - parent.AddCommand(statusCmd) + return nil + }) } -var ( - rollbackStaging bool - rollbackDeploymentID string - rollbackWait bool -) - -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPDeployRollbackCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "deploy:rollback") + phpErrorCommand(c, path, i18n.T("cmd.php.deploy_rollback.short"), func(options core.Options) error { + line := phpCommandLineFor(path, options) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - env := EnvProduction - if rollbackStaging { - env = EnvStaging - } + env := EnvProduction + if line.Bool("staging") { + env = EnvStaging + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy_rollback.rolling_back", map[string]interface{}{"Environment": env})) + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy_rollback.rolling_back", map[string]interface{}{"Environment": env})) - ctx := context.Background() + ctx := context.Background() - opts := RollbackOptions{ - Dir: cwd, - Environment: env, - DeploymentID: rollbackDeploymentID, - Wait: rollbackWait, - } + rollbackOpts := RollbackOptions{ + Dir: cwd, + Environment: env, + DeploymentID: line.String("id", ""), + Wait: line.Bool("wait"), + } - status, err := Rollback(ctx, opts) - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("cmd.php.error.rollback_failed"), err) - } + status, err := Rollback(ctx, rollbackOpts) + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("cmd.php.error.rollback_failed"), err) + } - printDeploymentStatus(status) + printDeploymentStatus(status) - if rollbackWait { - if IsDeploymentSuccessful(status.Status) { - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Rollback completed"})) - } else { - cli.Print(cliSectionLabelValueFormat, errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy_rollback.warning_status", map[string]interface{}{"Status": status.Status})) - } + if rollbackOpts.Wait { + if IsDeploymentSuccessful(status.Status) { + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Rollback completed"})) } else { - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy_rollback.triggered")) + cli.Print(cliSectionLabelValueFormat, errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy_rollback.warning_status", map[string]interface{}{"Status": status.Status})) } + } else { + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy_rollback.triggered")) + } - return nil - }, - } - - rollbackCmd.Flags().BoolVar(&rollbackStaging, "staging", false, i18n.T("cmd.php.deploy_rollback.flag.staging")) - rollbackCmd.Flags().StringVar(&rollbackDeploymentID, "id", "", i18n.T("cmd.php.deploy_rollback.flag.id")) - rollbackCmd.Flags().BoolVar(&rollbackWait, "wait", false, i18n.T("cmd.php.deploy_rollback.flag.wait")) - - parent.AddCommand(rollbackCmd) + return nil + }) } -var ( - deployListStaging bool - deployListLimit int -) - -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } - - env := EnvProduction - if deployListStaging { - env = EnvStaging - } - - limit := deployListLimit - if limit == 0 { - limit = 10 - } +func addPHPDeployListCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "deploy:list") + phpErrorCommand(c, path, i18n.T("cmd.php.deploy_list.short"), func(options core.Options) error { + line := phpCommandLineFor(path, options) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy_list.recent", map[string]interface{}{"Environment": env})) + env := EnvProduction + if line.Bool("staging") { + env = EnvStaging + } - ctx := context.Background() + limit := line.Int("limit", 0) + if limit == 0 { + limit = 10 + } - deployments, err := ListDeployments(ctx, cwd, env, limit) - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.list", "deployments"), err) - } + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPDeployLabelKey)), i18n.T("cmd.php.deploy_list.recent", map[string]interface{}{"Environment": env})) - if len(deployments) == 0 { - cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.deploy_list.none_found")) - return nil - } + ctx := context.Background() - for i, d := range deployments { - printDeploymentSummary(i+1, &d) - } + deployments, err := ListDeployments(ctx, cwd, env, limit) + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.list", "deployments"), err) + } + if len(deployments) == 0 { + cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.deploy_list.none_found")) return nil - }, - } + } - listCmd.Flags().BoolVar(&deployListStaging, "staging", false, i18n.T("cmd.php.deploy_list.flag.staging")) - listCmd.Flags().IntVar(&deployListLimit, "limit", 0, i18n.T("cmd.php.deploy_list.flag.limit")) + for i, d := range deployments { + printDeploymentSummary(i+1, &d) + } - parent.AddCommand(listCmd) + return nil + }) } func printDeploymentStatus(status *DeploymentStatus) { diff --git a/pkg/php/cmd_dev.go b/pkg/php/cmd_dev.go index 2aef5b9..403e768 100644 --- a/pkg/php/cmd_dev.go +++ b/pkg/php/cmd_dev.go @@ -10,47 +10,25 @@ import ( "syscall" "time" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) -var ( - devNoVite bool - devNoHorizon bool - devNoReverb bool - devNoRedis bool - devHTTPS bool - devDomain string - devPort int -) - -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 *cli.Command, args []string) error { - return runPHPDev(phpDevOptions{ - NoVite: devNoVite, - NoHorizon: devNoHorizon, - NoReverb: devNoReverb, - NoRedis: devNoRedis, - HTTPS: devHTTPS, - Domain: devDomain, - Port: devPort, - }) - }, - } - - devCmd.Flags().BoolVar(&devNoVite, "no-vite", false, i18n.T("cmd.php.dev.flag.no_vite")) - devCmd.Flags().BoolVar(&devNoHorizon, "no-horizon", false, i18n.T("cmd.php.dev.flag.no_horizon")) - devCmd.Flags().BoolVar(&devNoReverb, "no-reverb", false, i18n.T("cmd.php.dev.flag.no_reverb")) - devCmd.Flags().BoolVar(&devNoRedis, "no-redis", false, i18n.T("cmd.php.dev.flag.no_redis")) - devCmd.Flags().BoolVar(&devHTTPS, "https", false, i18n.T("cmd.php.dev.flag.https")) - devCmd.Flags().StringVar(&devDomain, "domain", "", i18n.T("cmd.php.dev.flag.domain")) - devCmd.Flags().IntVar(&devPort, "port", 0, i18n.T("cmd.php.dev.flag.port")) - - parent.AddCommand(devCmd) +func addPHPDevCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "dev") + phpErrorCommand(c, path, i18n.T("cmd.php.dev.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + return runPHPDev(phpDevOptions{ + NoVite: line.Bool("no-vite"), + NoHorizon: line.Bool("no-horizon"), + NoReverb: line.Bool("no-reverb"), + NoRedis: line.Bool("no-redis"), + HTTPS: line.Bool("https"), + Domain: line.String("domain", ""), + Port: line.Int("port", 0), + }) + }) } type phpDevOptions struct { @@ -66,7 +44,7 @@ type phpDevOptions struct { func runPHPDev(opts phpDevOptions) error { cwd, err := os.Getwd() if err != nil { - return cli.Err("failed to get working directory: %w", err) + return phpErr("failed to get working directory: %w", err) } // Check if this is a Laravel project @@ -91,7 +69,7 @@ func runPHPDev(opts phpDevOptions) error { notifyDevShutdown(cancel) if err := server.Start(ctx, devOpts); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.start", "services"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.start", "services"), err) } // Print status @@ -110,7 +88,6 @@ func runPHPDev(opts phpDevOptions) error { cli.Print(cliLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped")) return nil } - func laravelDisplayName(dir string) string { appName := GetLaravelAppName(dir) if appName == "" { @@ -197,25 +174,12 @@ func streamDevLogs(ctx context.Context, server *DevServer) { } } -var ( - logsFollow bool - logsService string -) - -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 *cli.Command, args []string) error { - return runPHPLogs(logsService, logsFollow) - }, - } - - logsCmd.Flags().BoolVar(&logsFollow, "follow", false, i18n.T("common.flag.follow")) - logsCmd.Flags().StringVar(&logsService, "service", "", i18n.T("cmd.php.logs.flag.service")) - - parent.AddCommand(logsCmd) +func addPHPLogsCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "logs") + phpErrorCommand(c, path, i18n.T("cmd.php.logs.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + return runPHPLogs(line.String("service", ""), line.Bool("follow")) + }) } func runPHPLogs(service string, follow bool) error { @@ -233,7 +197,7 @@ func runPHPLogs(service string, follow bool) error { logsReader, err := server.Logs(service, follow) if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, "logs"), err) + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, "logs"), err) } defer func() { _ = logsReader.Close() }() @@ -262,16 +226,10 @@ func runPHPLogs(service string, follow bool) error { return scanner.Err() } -func addPHPStopCommand(parent *cli.Command) { - stopCmd := &cli.Command{ - Use: "stop", - Short: i18n.T("cmd.php.stop.short"), - RunE: func(cmd *cli.Command, args []string) error { - return runPHPStop() - }, - } - - parent.AddCommand(stopCmd) +func addPHPStopCommand(c *core.Core, prefix string) { + phpErrorCommand(c, phpCommandPath(prefix, "stop"), i18n.T("cmd.php.stop.short"), func(core.Options) error { + return runPHPStop() + }) } func runPHPStop() error { @@ -286,23 +244,17 @@ func runPHPStop() error { // This is a simplified version - in practice you'd want to track PIDs server := NewDevServer(Options{Dir: cwd}) if err := server.Stop(); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.stop", "services"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.stop", "services"), err) } cli.Print(cliLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped")) return nil } -func addPHPStatusCommand(parent *cli.Command) { - statusCmd := &cli.Command{ - Use: "status", - Short: i18n.T("cmd.php.status.short"), - RunE: func(cmd *cli.Command, args []string) error { - return runPHPStatus() - }, - } - - parent.AddCommand(statusCmd) +func addPHPStatusCommand(c *core.Core, prefix string) { + phpErrorCommand(c, phpCommandPath(prefix, "status"), i18n.T("cmd.php.status.short"), func(core.Options) error { + return runPHPStatus() + }) } func runPHPStatus() error { @@ -354,20 +306,12 @@ func runPHPStatus() error { return nil } -var sslDomain string - -func addPHPSSLCommand(parent *cli.Command) { - sslCmd := &cli.Command{ - Use: "ssl", - Short: i18n.T("cmd.php.ssl.short"), - RunE: func(cmd *cli.Command, args []string) error { - return runPHPSSL(sslDomain) - }, - } - - sslCmd.Flags().StringVar(&sslDomain, "domain", "", i18n.T("cmd.php.ssl.flag.domain")) - - parent.AddCommand(sslCmd) +func addPHPSSLCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "ssl") + phpErrorCommand(c, path, i18n.T("cmd.php.ssl.short"), func(opts core.Options) error { + line := phpCommandLineFor(path, opts) + return runPHPSSL(line.String("domain", "")) + }) } func runPHPSSL(domain string) error { @@ -410,7 +354,7 @@ func runPHPSSL(domain string) error { // Setup SSL if err := SetupSSL(domain, SSLOptions{}); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.setup", "SSL"), err) + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.setup", "SSL"), err) } certFile, keyFile, _ := CertPaths(domain, SSLOptions{}) diff --git a/pkg/php/cmd_packages.go b/pkg/php/cmd_packages.go index 6fca7af..ef052e0 100644 --- a/pkg/php/cmd_packages.go +++ b/pkg/php/cmd_packages.go @@ -3,143 +3,123 @@ package php import ( "os" + core "dappco.re/go" "dappco.re/go/cli/pkg/cli" "dappco.re/go/i18n" ) -func addPHPPackagesCommands(parent *cli.Command) { - packagesCmd := &cli.Command{ - Use: "packages", - Short: i18n.T("cmd.php.packages.short"), - Long: i18n.T("cmd.php.packages.long"), - } - parent.AddCommand(packagesCmd) - - addPHPPackagesLinkCommand(packagesCmd) - addPHPPackagesUnlinkCommand(packagesCmd) - addPHPPackagesUpdateCommand(packagesCmd) - addPHPPackagesListCommand(packagesCmd) +func addPHPPackagesCommands(c *core.Core, prefix string) { + phpHelpCommand(c, phpCommandPath(prefix, "packages"), i18n.T("cmd.php.packages.short")) + addPHPPackagesLinkCommand(c, prefix) + addPHPPackagesUnlinkCommand(c, prefix) + addPHPPackagesUpdateCommand(c, prefix) + addPHPPackagesListCommand(c, prefix) } -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: cli.MinimumNArgs(1), - RunE: func(cmd *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPPackagesLinkCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "packages/link") + phpErrorCommand(c, path, i18n.T("cmd.php.packages.link.short"), func(opts core.Options) error { + args := phpCommandLineFor(path, opts).Args() + if len(args) < 1 { + return phpErr("requires at least 1 arg(s), only received %d", len(args)) + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.link.linking")) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - if err := LinkPackages(cwd, args); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.link", "packages"), err) - } + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.link.linking")) - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.link.done")) - return nil - }, - } + if err := LinkPackages(cwd, args); err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.link", "packages"), err) + } - parent.AddCommand(linkCmd) + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.link.done")) + return nil + }) } -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: cli.MinimumNArgs(1), - RunE: func(cmd *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPPackagesUnlinkCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "packages/unlink") + phpErrorCommand(c, path, i18n.T("cmd.php.packages.unlink.short"), func(opts core.Options) error { + args := phpCommandLineFor(path, opts).Args() + if len(args) < 1 { + return phpErr("requires at least 1 arg(s), only received %d", len(args)) + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.unlink.unlinking")) + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - if err := UnlinkPackages(cwd, args); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.unlink", "packages"), err) - } + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.unlink.unlinking")) - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.unlink.done")) - return nil - }, - } + if err := UnlinkPackages(cwd, args); err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.unlink", "packages"), err) + } - parent.AddCommand(unlinkCmd) + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.unlink.done")) + return nil + }) } -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPPackagesUpdateCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "packages/update") + phpErrorCommand(c, path, i18n.T("cmd.php.packages.update.short"), func(opts core.Options) error { + args := phpCommandLineFor(path, opts).Args() + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.update.updating")) + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.update.updating")) - if err := UpdatePackages(cwd, args); err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("cmd.php.error.update_packages"), err) - } + if err := UpdatePackages(cwd, args); err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("cmd.php.error.update_packages"), err) + } - cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.update.done")) - return nil - }, - } - - parent.AddCommand(updateCmd) + cli.Print(cliSectionLabelValueFormat, successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.update.done")) + return nil + }) } -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 *cli.Command, args []string) error { - cwd, err := os.Getwd() - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) - } +func addPHPPackagesListCommand(c *core.Core, prefix string) { + path := phpCommandPath(prefix, "packages/list") + phpErrorCommand(c, path, i18n.T("cmd.php.packages.list.short"), func(opts core.Options) error { + cwd, err := os.Getwd() + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T(i18nFailGetKey, workingDirectorySubject), err) + } + + packages, err := ListLinkedPackages(cwd) + if err != nil { + return phpErr(cliWrapErrorFormat, i18n.T("i18n.fail.list", "packages"), err) + } + + if len(packages) == 0 { + cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.list.none_found")) + return nil + } - packages, err := ListLinkedPackages(cwd) - if err != nil { - return cli.Err(cliWrapErrorFormat, i18n.T("i18n.fail.list", "packages"), err) - } + cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.list.linked")) - if len(packages) == 0 { - cli.Print(cliLabelValueFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.list.none_found")) - return nil + for _, pkg := range packages { + name := pkg.Name + if name == "" { + name = i18n.T("cmd.php.packages.list.unknown") } - - cli.Print(cliLabelValueBlankFormat, dimStyle.Render(i18n.T(cmdPHPLabelKey)), i18n.T("cmd.php.packages.list.linked")) - - for _, pkg := range packages { - name := pkg.Name - if name == "" { - name = i18n.T("cmd.php.packages.list.unknown") - } - version := pkg.Version - if version == "" { - version = "dev" - } - - cli.Print(" %s %s\n", successStyle.Render("*"), name) - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("path")), pkg.Path) - cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("version")), version) - cli.Blank() + version := pkg.Version + if version == "" { + version = "dev" } - return nil - }, - } + cli.Print(" %s %s\n", successStyle.Render("*"), name) + cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("path")), pkg.Path) + cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("version")), version) + cli.Blank() + } - parent.AddCommand(listCmd) + return nil + }) } diff --git a/pkg/php/cmd_serve_frankenphp.go b/pkg/php/cmd_serve_frankenphp.go index c0c3412..bc1d278 100644 --- a/pkg/php/cmd_serve_frankenphp.go +++ b/pkg/php/cmd_serve_frankenphp.go @@ -11,14 +11,7 @@ import ( "os/signal" "syscall" - "dappco.re/go/cli/pkg/cli" -) - -var ( - serveFPPort int - serveFPPath string - serveFPWorkers int - serveFPThreads int + core "dappco.re/go" ) func init() { @@ -27,41 +20,29 @@ func init() { // addFrankenPHPCommands adds FrankenPHP-specific commands to the php parent command. // Called from AddPHPCommands when CGO is enabled. -func addFrankenPHPCommands(phpCmd *cli.Command) { - 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 addFrankenPHPCommands(c *core.Core, prefix string) { + servePath := phpCommandPath(prefix, "serve:embedded") + phpErrorCommand(c, servePath, "Serve Laravel via embedded FrankenPHP runtime", func(opts core.Options) error { + return runFrankenPHPServe(phpCommandLineFor(servePath, opts)) + }) + + execPath := phpCommandPath(prefix, "exec") + phpErrorCommand(c, execPath, "Execute a PHP artisan command via FrankenPHP", func(opts core.Options) error { + return runFrankenPHPExec(phpCommandLineFor(execPath, opts)) + }) } -func runFrankenPHPServe(cmd *cli.Command, args []string) error { - handler, cleanup, err := NewHandler(serveFPPath, HandlerConfig{ - NumThreads: serveFPThreads, - NumWorkers: serveFPWorkers, +func runFrankenPHPServe(line phpCommandLine) error { + handler, cleanup, err := NewHandler(line.String("path", "."), HandlerConfig{ + NumThreads: line.Int("threads", 4), + NumWorkers: line.Int("workers", 2), }) if err != nil { return fmt.Errorf("init FrankenPHP: %w", err) } defer cleanup() - addr := fmt.Sprintf(":%d", serveFPPort) + addr := fmt.Sprintf(":%d", line.Int("port", 8000)) srv := &http.Server{ Addr: addr, Handler: handler, @@ -83,8 +64,13 @@ func runFrankenPHPServe(cmd *cli.Command, args []string) error { return srv.Shutdown(context.Background()) } -func runFrankenPHPExec(cmd *cli.Command, args []string) error { - handler, cleanup, err := NewHandler(serveFPPath, HandlerConfig{ +func runFrankenPHPExec(line phpCommandLine) error { + args := line.Args() + if len(args) < 1 { + return phpErr("requires at least 1 arg(s), only received %d", len(args)) + } + + handler, cleanup, err := NewHandler(line.String("path", "."), HandlerConfig{ NumThreads: 1, NumWorkers: 0, }) diff --git a/pkg/php/container.go b/pkg/php/container.go index 039ad0d..993ab94 100644 --- a/pkg/php/container.go +++ b/pkg/php/container.go @@ -111,7 +111,7 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error { cmd.Stderr = opts.Output if err := cmd.Run(); err != nil { - return cli.Wrap(err, "docker build failed") + return phpWrap(err, "docker build failed") } return nil @@ -121,14 +121,14 @@ func normalizeDockerBuildOptions(opts DockerBuildOptions) (DockerBuildOptions, e if opts.ProjectDir == "" { cwd, err := os.Getwd() if err != nil { - return opts, cli.WrapVerb(err, "get", workingDirectorySubject) + return opts, phpWrapVerb(err, "get", workingDirectorySubject) } opts.ProjectDir = cwd } // Validate project directory if !IsPHPProject(opts.ProjectDir) { - return opts, cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir) + return opts, phpErr("not a PHP project: %s (missing composer.json)", opts.ProjectDir) } // Set defaults @@ -152,13 +152,13 @@ func resolveDockerfilePath(opts DockerBuildOptions) (string, func(), error) { content, err := GenerateDockerfile(opts.ProjectDir) if err != nil { - return "", nil, cli.WrapVerb(err, "generate", "Dockerfile") + return "", nil, phpWrapVerb(err, "generate", "Dockerfile") } m := getMedium() tempDockerfile := filepath.Join(opts.ProjectDir, "Dockerfile.core-generated") if err := m.Write(tempDockerfile, content); err != nil { - return "", nil, cli.WrapVerb(err, "write", "Dockerfile") + return "", nil, phpWrapVerb(err, "write", "Dockerfile") } return tempDockerfile, func() { _ = m.Delete(tempDockerfile) }, nil @@ -190,14 +190,14 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { if opts.ProjectDir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.ProjectDir = cwd } // Validate project directory if !IsPHPProject(opts.ProjectDir) { - return cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir) + return phpErr("not a PHP project: %s (missing composer.json)", opts.ProjectDir) } // Set defaults @@ -218,7 +218,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { m := getMedium() outputDir := filepath.Dir(opts.OutputPath) if err := m.EnsureDir(outputDir); err != nil { - return cli.WrapVerb(err, "create", "output directory") + return phpWrapVerb(err, "create", "output directory") } // Find linuxkit binary @@ -230,7 +230,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { // Get template content templateContent, err := getLinuxKitTemplate(opts.Template) if err != nil { - return cli.WrapVerb(err, "get", "template") + return phpWrapVerb(err, "get", "template") } // Apply variables @@ -243,13 +243,13 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { content, err := applyTemplateVariables(templateContent, opts.Variables) if err != nil { - return cli.WrapVerb(err, "apply", "template variables") + return phpWrapVerb(err, "apply", "template variables") } // Write template to temp file tempYAML := filepath.Join(opts.ProjectDir, ".core-linuxkit.yml") if err := m.Write(tempYAML, content); err != nil { - return cli.WrapVerb(err, "write", "template") + return phpWrapVerb(err, "write", "template") } defer func() { _ = m.Delete(tempYAML) }() @@ -267,7 +267,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { cmd.Stderr = opts.Output if err := cmd.Run(); err != nil { - return cli.Wrap(err, "linuxkit build failed") + return phpWrap(err, "linuxkit build failed") } return nil @@ -276,7 +276,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error { // ServeProduction runs a production PHP container. func ServeProduction(ctx context.Context, opts ServeOptions) error { if opts.ImageName == "" { - return cli.Err("image name is required") + return phpErr("image name is required") } // Set defaults @@ -329,7 +329,7 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error { cmd.Stderr = opts.Output output, err := cmd.Output() if err != nil { - return cli.WrapVerb(err, "start", "container") + return phpWrapVerb(err, "start", "container") } containerID := strings.TrimSpace(string(output)) cli.Print("Container started: %s\n", containerID[:12]) @@ -344,7 +344,7 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error { // Shell opens a shell in a running container. func Shell(ctx context.Context, containerID string) error { if containerID == "" { - return cli.Err("container ID is required") + return phpErr("container ID is required") } // Resolve partial container ID @@ -387,7 +387,7 @@ func lookupLinuxKit() (string, error) { } } - return "", cli.Err("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit") + return "", phpErr("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit") } // getLinuxKitTemplate retrieves a LinuxKit template by name. @@ -399,7 +399,7 @@ func getLinuxKitTemplate(name string) (string, error) { // Try to load from container package templates // This would integrate with forge.lthn.ai/core/go/pkg/container - return "", cli.Err("template not found: %s", name) + return "", phpErr("template not found: %s", name) } // applyTemplateVariables applies variable substitution to template content. @@ -417,7 +417,7 @@ func resolveDockerContainerID(ctx context.Context, partialID string) (string, er cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--no-trunc", "--format", "{{.ID}}") output, err := cmd.Output() if err != nil { - return "", cli.WrapVerb(err, "list", "containers") + return "", phpWrapVerb(err, "list", "containers") } lines := strings.Split(strings.TrimSpace(string(output)), "\n") @@ -431,11 +431,11 @@ func resolveDockerContainerID(ctx context.Context, partialID string) (string, er switch len(matches) { case 0: - return "", cli.Err("no container found matching: %s", partialID) + return "", phpErr("no container found matching: %s", partialID) case 1: return matches[0], nil default: - return "", cli.Err("multiple containers match '%s', be more specific", partialID) + return "", phpErr("multiple containers match '%s', be more specific", partialID) } } diff --git a/pkg/php/coolify.go b/pkg/php/coolify.go index 56a053c..34fde68 100644 --- a/pkg/php/coolify.go +++ b/pkg/php/coolify.go @@ -86,7 +86,7 @@ func LoadCoolifyConfigFromFile(path string) (*CoolifyConfig, error) { content, err := m.Read(path) if err != nil { - return nil, cli.WrapVerb(err, "read", ".env file") + return nil, phpWrapVerb(err, "read", ".env file") } applyCoolifyEnvFile(config, content) @@ -152,10 +152,10 @@ func setCoolifyConfigValue(config *CoolifyConfig, key, value string) { // validateCoolifyConfig checks that required fields are set. func validateCoolifyConfig(config *CoolifyConfig) (*CoolifyConfig, error) { if config.URL == "" { - return nil, cli.Err("COOLIFY_URL is not set") + return nil, phpErr("COOLIFY_URL is not set") } if config.Token == "" { - return nil, cli.Err("COOLIFY_TOKEN is not set") + return nil, phpErr("COOLIFY_TOKEN is not set") } return config, nil } @@ -171,19 +171,19 @@ func (c *CoolifyClient) TriggerDeploy(ctx context.Context, appID string, force b body, err := json.Marshal(payload) if err != nil { - return nil, cli.WrapVerb(err, "marshal", "request") + return nil, phpWrapVerb(err, "marshal", "request") } req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { - return nil, cli.WrapVerb(err, "create", "request") + return nil, phpWrapVerb(err, "create", "request") } c.setHeaders(req) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, cli.Wrap(err, requestFailedMessage) + return nil, phpWrap(err, requestFailedMessage) } defer func() { _ = resp.Body.Close() }() @@ -209,14 +209,14 @@ func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID s req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { - return nil, cli.WrapVerb(err, "create", "request") + return nil, phpWrapVerb(err, "create", "request") } c.setHeaders(req) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, cli.Wrap(err, requestFailedMessage) + return nil, phpWrap(err, requestFailedMessage) } defer func() { _ = resp.Body.Close() }() @@ -226,7 +226,7 @@ func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID s var deployment CoolifyDeployment if err := json.NewDecoder(resp.Body).Decode(&deployment); err != nil { - return nil, cli.WrapVerb(err, "decode", "response") + return nil, phpWrapVerb(err, "decode", "response") } return &deployment, nil @@ -241,14 +241,14 @@ func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { - return nil, cli.WrapVerb(err, "create", "request") + return nil, phpWrapVerb(err, "create", "request") } c.setHeaders(req) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, cli.Wrap(err, requestFailedMessage) + return nil, phpWrap(err, requestFailedMessage) } defer func() { _ = resp.Body.Close() }() @@ -258,7 +258,7 @@ func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit var deployments []CoolifyDeployment if err := json.NewDecoder(resp.Body).Decode(&deployments); err != nil { - return nil, cli.WrapVerb(err, "decode", "response") + return nil, phpWrapVerb(err, "decode", "response") } return deployments, nil @@ -274,19 +274,19 @@ func (c *CoolifyClient) Rollback(ctx context.Context, appID, deploymentID string body, err := json.Marshal(payload) if err != nil { - return nil, cli.WrapVerb(err, "marshal", "request") + return nil, phpWrapVerb(err, "marshal", "request") } req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { - return nil, cli.WrapVerb(err, "create", "request") + return nil, phpWrapVerb(err, "create", "request") } c.setHeaders(req) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, cli.Wrap(err, requestFailedMessage) + return nil, phpWrap(err, requestFailedMessage) } defer func() { _ = resp.Body.Close() }() @@ -311,14 +311,14 @@ func (c *CoolifyClient) GetApp(ctx context.Context, appID string) (*CoolifyApp, req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { - return nil, cli.WrapVerb(err, "create", "request") + return nil, phpWrapVerb(err, "create", "request") } c.setHeaders(req) resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, cli.Wrap(err, requestFailedMessage) + return nil, phpWrap(err, requestFailedMessage) } defer func() { _ = resp.Body.Close() }() @@ -328,7 +328,7 @@ func (c *CoolifyClient) GetApp(ctx context.Context, appID string) (*CoolifyApp, var app CoolifyApp if err := json.NewDecoder(resp.Body).Decode(&app); err != nil { - return nil, cli.WrapVerb(err, "decode", "response") + return nil, phpWrapVerb(err, "decode", "response") } return &app, nil @@ -352,12 +352,12 @@ func (c *CoolifyClient) parseError(resp *http.Response) error { if err := json.Unmarshal(body, &errResp); err == nil { if errResp.Message != "" { - return cli.Err(apiErrorFormat, resp.StatusCode, errResp.Message) + return phpErr(apiErrorFormat, resp.StatusCode, errResp.Message) } if errResp.Error != "" { - return cli.Err(apiErrorFormat, resp.StatusCode, errResp.Error) + return phpErr(apiErrorFormat, resp.StatusCode, errResp.Error) } } - return cli.Err(apiErrorFormat, resp.StatusCode, string(body)) + return phpErr(apiErrorFormat, resp.StatusCode, string(body)) } diff --git a/pkg/php/deploy.go b/pkg/php/deploy.go index fc07146..e0ee40d 100644 --- a/pkg/php/deploy.go +++ b/pkg/php/deploy.go @@ -3,8 +3,6 @@ package php import ( "context" "time" - - "dappco.re/go/cli/pkg/cli" ) // Environment represents a deployment environment. @@ -121,13 +119,13 @@ func Deploy(ctx context.Context, opts DeployOptions) (*DeploymentStatus, error) // Load config config, err := LoadCoolifyConfig(opts.Dir) if err != nil { - return nil, cli.WrapVerb(err, "load", coolifyConfigSubject) + return nil, phpWrapVerb(err, "load", coolifyConfigSubject) } // Get app ID for environment appID := getAppIDForEnvironment(config, opts.Environment) if appID == "" { - return nil, cli.Err(noAppIDEnvironmentFormat, opts.Environment) + return nil, phpErr(noAppIDEnvironmentFormat, opts.Environment) } // Create client @@ -136,7 +134,7 @@ func Deploy(ctx context.Context, opts DeployOptions) (*DeploymentStatus, error) // Trigger deployment deployment, err := client.TriggerDeploy(ctx, appID, opts.Force) if err != nil { - return nil, cli.WrapVerb(err, "trigger", "deployment") + return nil, phpWrapVerb(err, "trigger", "deployment") } status := convertDeployment(deployment) @@ -170,13 +168,13 @@ func DeployStatus(ctx context.Context, opts StatusOptions) (*DeploymentStatus, e // Load config config, err := LoadCoolifyConfig(opts.Dir) if err != nil { - return nil, cli.WrapVerb(err, "load", coolifyConfigSubject) + return nil, phpWrapVerb(err, "load", coolifyConfigSubject) } // Get app ID for environment appID := getAppIDForEnvironment(config, opts.Environment) if appID == "" { - return nil, cli.Err(noAppIDEnvironmentFormat, opts.Environment) + return nil, phpErr(noAppIDEnvironmentFormat, opts.Environment) } // Create client @@ -188,16 +186,16 @@ func DeployStatus(ctx context.Context, opts StatusOptions) (*DeploymentStatus, e // Get specific deployment deployment, err = client.GetDeployment(ctx, appID, opts.DeploymentID) if err != nil { - return nil, cli.WrapVerb(err, "get", "deployment") + return nil, phpWrapVerb(err, "get", "deployment") } } else { // Get latest deployment deployments, err := client.ListDeployments(ctx, appID, 1) if err != nil { - return nil, cli.WrapVerb(err, "list", "deployments") + return nil, phpWrapVerb(err, "list", "deployments") } if len(deployments) == 0 { - return nil, cli.Err("no deployments found") + return nil, phpErr("no deployments found") } deployment = &deployments[0] } @@ -230,7 +228,7 @@ func Rollback(ctx context.Context, opts RollbackOptions) (*DeploymentStatus, err // Trigger rollback deployment, err := client.Rollback(ctx, appID, deploymentID) if err != nil { - return nil, cli.WrapVerb(err, "trigger", "rollback") + return nil, phpWrapVerb(err, "trigger", "rollback") } status := convertDeployment(deployment) @@ -262,12 +260,12 @@ func normalizeRollbackOptions(opts RollbackOptions) RollbackOptions { func coolifyClientForEnvironment(dir string, env Environment) (*CoolifyClient, string, error) { config, err := LoadCoolifyConfig(dir) if err != nil { - return nil, "", cli.WrapVerb(err, "load", coolifyConfigSubject) + return nil, "", phpWrapVerb(err, "load", coolifyConfigSubject) } appID := getAppIDForEnvironment(config, env) if appID == "" { - return nil, "", cli.Err(noAppIDEnvironmentFormat, env) + return nil, "", phpErr(noAppIDEnvironmentFormat, env) } return NewCoolifyClient(config.URL, config.Token), appID, nil @@ -280,7 +278,7 @@ func resolveRollbackDeploymentID(ctx context.Context, client *CoolifyClient, app deployments, err := client.ListDeployments(ctx, appID, 10) if err != nil { - return "", cli.WrapVerb(err, "list", "deployments") + return "", phpWrapVerb(err, "list", "deployments") } for i, d := range deployments { @@ -289,7 +287,7 @@ func resolveRollbackDeploymentID(ctx context.Context, client *CoolifyClient, app } } - return "", cli.Err("no previous successful deployment found to rollback to") + return "", phpErr("no previous successful deployment found to rollback to") } func isSuccessfulDeploymentStatus(status string) bool { @@ -311,13 +309,13 @@ func ListDeployments(ctx context.Context, dir string, env Environment, limit int // Load config config, err := LoadCoolifyConfig(dir) if err != nil { - return nil, cli.WrapVerb(err, "load", coolifyConfigSubject) + return nil, phpWrapVerb(err, "load", coolifyConfigSubject) } // Get app ID for environment appID := getAppIDForEnvironment(config, env) if appID == "" { - return nil, cli.Err(noAppIDEnvironmentFormat, env) + return nil, phpErr(noAppIDEnvironmentFormat, env) } // Create client @@ -325,7 +323,7 @@ func ListDeployments(ctx context.Context, dir string, env Environment, limit int deployments, err := client.ListDeployments(ctx, appID, limit) if err != nil { - return nil, cli.WrapVerb(err, "list", "deployments") + return nil, phpWrapVerb(err, "list", "deployments") } result := make([]DeploymentStatus, len(deployments)) @@ -377,7 +375,7 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy deployment, err := client.GetDeployment(ctx, appID, deploymentID) if err != nil { - return nil, cli.WrapVerb(err, "get", "deployment status") + return nil, phpWrapVerb(err, "get", "deployment status") } status := convertDeployment(deployment) @@ -387,9 +385,9 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy case "finished", "success": return status, nil case "failed", "error": - return status, cli.Err("deployment failed: %s", deployment.Status) + return status, phpErr("deployment failed: %s", deployment.Status) case "cancelled": - return status, cli.Err("deployment was cancelled") + return status, phpErr("deployment was cancelled") } // Still in progress, wait and retry @@ -400,7 +398,7 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy } } - return nil, cli.Err("deployment timed out after %v", timeout) + return nil, phpErr("deployment timed out after %v", timeout) } // IsDeploymentComplete returns true if the status indicates completion. diff --git a/pkg/php/dockerfile.go b/pkg/php/dockerfile.go index 8226842..2f6f212 100644 --- a/pkg/php/dockerfile.go +++ b/pkg/php/dockerfile.go @@ -60,12 +60,12 @@ func DetectDockerfileConfig(dir string) (*DockerfileConfig, error) { composerPath := filepath.Join(dir, composerJSONFile) composerContent, err := m.Read(composerPath) if err != nil { - return nil, cli.WrapVerb(err, "read", composerJSONFile) + return nil, phpWrapVerb(err, "read", composerJSONFile) } var composer ComposerJSON if err := json.Unmarshal([]byte(composerContent), &composer); err != nil { - return nil, cli.WrapVerb(err, "parse", composerJSONFile) + return nil, phpWrapVerb(err, "parse", composerJSONFile) } // Detect PHP version from composer.json diff --git a/pkg/php/go_cli_helpers.go b/pkg/php/go_cli_helpers.go new file mode 100644 index 0000000..c1806d9 --- /dev/null +++ b/pkg/php/go_cli_helpers.go @@ -0,0 +1,238 @@ +package php + +import ( + "fmt" + "strings" + + core "dappco.re/go" + "dappco.re/go/cli/pkg/cli" +) + +var phpCommandValueFlags = map[string]bool{ + "container": true, + "dockerfile": true, + "domain": true, + "env-file": true, + "fail-on": true, + "format": true, + "https-port": true, + "id": true, + "limit": true, + "name": true, + "output": true, + "path": true, + "platform": true, + "port": true, + "service": true, + "tag": true, + "template": true, + "threads": true, + "type": true, + "workers": true, +} + +type phpCommandLine struct { + flags map[string]string + args []string +} + +func phpErr(format string, args ...any) error { + return fmt.Errorf(format, args...) +} + +func phpWrap(err error, message string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %w", message, err) +} + +func phpWrapVerb(err error, verb, subject string) error { + if err == nil { + return nil + } + return fmt.Errorf("failed to %s %s: %w", verb, subject, err) +} + +func phpExit(code int, err error) error { + if err == nil { + return nil + } + return &cli.ExitError{Code: code, Err: err} +} + +func phpErrorResult(err error) core.Result { + if err != nil { + return core.Fail(err) + } + return core.Ok(nil) +} + +func phpCommand(c *core.Core, path, description string, action core.CommandAction) { + c.Command(path, core.Command{ + Description: description, + Action: func(opts core.Options) core.Result { + if err := activateWorkspacePackage(); err != nil { + return core.Fail(err) + } + return action(opts) + }, + }) +} + +func phpErrorCommand(c *core.Core, path, description string, action func(core.Options) error) { + phpCommand(c, path, description, func(opts core.Options) core.Result { + return phpErrorResult(action(opts)) + }) +} + +func phpHelpCommand(c *core.Core, path, description string) { + phpCommand(c, path, description, func(core.Options) core.Result { + if cl := c.Cli(); cl != nil { + cl.PrintHelp() + } + return core.Ok(nil) + }) +} + +func phpCommandLineFor(path string, opts core.Options) phpCommandLine { + if raw, ok := phpRawArgsForCommand(path); ok { + return phpParseCommandLine(raw) + } + return phpCommandLineFromOptions(opts) +} + +func phpRawArgsForCommand(path string) ([]string, bool) { + parts := core.Split(path, "/") + if len(parts) == 0 { + return nil, false + } + + args := core.FilterArgs(core.Args()[1:]) + if len(args) < len(parts) { + return nil, false + } + for i, part := range parts { + if args[i] != part { + return nil, false + } + } + return args[len(parts):], true +} + +func phpCommandLineFromOptions(opts core.Options) phpCommandLine { + line := phpCommandLine{flags: map[string]string{}} + for _, item := range opts.Items() { + switch item.Key { + case "_arg": + if value, ok := item.Value.(string); ok && value != "" { + line.args = []string{value} + } + case "_args": + if values, ok := item.Value.([]string); ok { + line.args = append([]string(nil), values...) + } + default: + switch value := item.Value.(type) { + case string: + line.flags[item.Key] = value + case bool: + if value { + line.flags[item.Key] = "true" + } + case int: + line.flags[item.Key] = core.Itoa(value) + } + } + } + return line +} + +func phpParseCommandLine(args []string) phpCommandLine { + line := phpCommandLine{flags: map[string]string{}} + for i := 0; i < len(args); i++ { + arg := args[i] + if arg == "--" { + line.args = append(line.args, args[i+1:]...) + break + } + + key, value, hasValue, ok := phpSplitFlag(arg) + if ok { + if !hasValue && phpCommandValueFlags[key] && i+1 < len(args) && !core.IsFlag(args[i+1]) { + value = args[i+1] + hasValue = true + i++ + } + if !hasValue { + value = "true" + } + line.flags[key] = value + continue + } + + line.args = append(line.args, arg) + } + return line +} + +func phpSplitFlag(arg string) (key, value string, hasValue bool, ok bool) { + if strings.HasPrefix(arg, "--") { + body := strings.TrimPrefix(arg, "--") + if body == "" { + return "", "", false, false + } + key, value, hasValue = strings.Cut(body, "=") + return key, value, hasValue, key != "" + } + + if strings.HasPrefix(arg, "-") { + body := strings.TrimPrefix(arg, "-") + if body == "" { + return "", "", false, false + } + key, value, hasValue = strings.Cut(body, "=") + return key, value, hasValue, key != "" + } + + return "", "", false, false +} + +func (line phpCommandLine) Bool(name string, aliases ...string) bool { + keys := append([]string{name}, aliases...) + for _, key := range keys { + value, ok := line.flags[key] + if !ok { + continue + } + switch strings.ToLower(value) { + case "", "1", "true", "yes", "on": + return true + default: + return false + } + } + return false +} + +func (line phpCommandLine) String(name string, fallback string) string { + if value, ok := line.flags[name]; ok { + return value + } + return fallback +} + +func (line phpCommandLine) Int(name string, fallback int) int { + value, ok := line.flags[name] + if !ok || value == "" { + return fallback + } + if parsed := core.Atoi(value); parsed.OK { + return parsed.Value.(int) + } + return fallback +} + +func (line phpCommandLine) Args() []string { + return append([]string(nil), line.args...) +} diff --git a/pkg/php/packages.go b/pkg/php/packages.go index bc33499..820ff33 100644 --- a/pkg/php/packages.go +++ b/pkg/php/packages.go @@ -29,12 +29,12 @@ func readComposerJSON(dir string) (map[string]json.RawMessage, error) { composerPath := filepath.Join(dir, composerJSONFile) content, err := m.Read(composerPath) if err != nil { - return nil, cli.WrapVerb(err, "read", composerJSONFile) + return nil, phpWrapVerb(err, "read", composerJSONFile) } var raw map[string]json.RawMessage if err := json.Unmarshal([]byte(content), &raw); err != nil { - return nil, cli.WrapVerb(err, "parse", composerJSONFile) + return nil, phpWrapVerb(err, "parse", composerJSONFile) } return raw, nil @@ -47,14 +47,14 @@ func writeComposerJSON(dir string, raw map[string]json.RawMessage) error { data, err := json.MarshalIndent(raw, "", " ") if err != nil { - return cli.WrapVerb(err, "marshal", composerJSONFile) + return phpWrapVerb(err, "marshal", composerJSONFile) } // Add trailing newline content := string(data) + "\n" if err := m.Write(composerPath, content); err != nil { - return cli.WrapVerb(err, "write", composerJSONFile) + return phpWrapVerb(err, "write", composerJSONFile) } return nil @@ -69,7 +69,7 @@ func getRepositories(raw map[string]json.RawMessage) ([]composerRepository, erro var repos []composerRepository if err := json.Unmarshal(reposRaw, &repos); err != nil { - return nil, cli.WrapVerb(err, "parse", "repositories") + return nil, phpWrapVerb(err, "parse", "repositories") } return repos, nil @@ -84,7 +84,7 @@ func setRepositories(raw map[string]json.RawMessage, repos []composerRepository) reposData, err := json.Marshal(repos) if err != nil { - return cli.WrapVerb(err, "marshal", "repositories") + return phpWrapVerb(err, "marshal", "repositories") } raw["repositories"] = reposData @@ -97,7 +97,7 @@ func getPackageInfo(packagePath string) (name, version string, err error) { composerPath := filepath.Join(packagePath, composerJSONFile) content, err := m.Read(composerPath) if err != nil { - return "", "", cli.WrapVerb(err, "read", "package composer.json") + return "", "", phpWrapVerb(err, "read", "package composer.json") } var pkg struct { @@ -106,11 +106,11 @@ func getPackageInfo(packagePath string) (name, version string, err error) { } if err := json.Unmarshal([]byte(content), &pkg); err != nil { - return "", "", cli.WrapVerb(err, "parse", "package composer.json") + return "", "", phpWrapVerb(err, "parse", "package composer.json") } if pkg.Name == "" { - return "", "", cli.Err("package name not found in composer.json") + return "", "", phpErr("package name not found in composer.json") } return pkg.Name, pkg.Version, nil @@ -119,7 +119,7 @@ func getPackageInfo(packagePath string) (name, version string, err error) { // LinkPackages adds path repositories to composer.json for local package development. func LinkPackages(dir string, packages []string) error { if !IsPHPProject(dir) { - return cli.Err(notPHPProjectComposerMessage) + return phpErr(notPHPProjectComposerMessage) } raw, err := readComposerJSON(dir) @@ -156,16 +156,16 @@ func LinkPackages(dir string, packages []string) error { func validateLinkPackage(packagePath string) (string, string, error) { absPath, err := filepath.Abs(packagePath) if err != nil { - return "", "", cli.Err("failed to resolve path %s: %w", packagePath, err) + return "", "", phpErr("failed to resolve path %s: %w", packagePath, err) } if !IsPHPProject(absPath) { - return "", "", cli.Err("not a PHP package (missing composer.json): %s", absPath) + return "", "", phpErr("not a PHP package (missing composer.json): %s", absPath) } pkgName, _, err := getPackageInfo(absPath) if err != nil { - return "", "", cli.Err("failed to get package info from %s: %w", absPath, err) + return "", "", phpErr("failed to get package info from %s: %w", absPath, err) } return absPath, pkgName, nil @@ -193,7 +193,7 @@ func pathComposerRepository(absPath string) composerRepository { // UnlinkPackages removes path repositories from composer.json. func UnlinkPackages(dir string, packages []string) error { if !IsPHPProject(dir) { - return cli.Err(notPHPProjectComposerMessage) + return phpErr(notPHPProjectComposerMessage) } raw, err := readComposerJSON(dir) @@ -255,7 +255,7 @@ func shouldUnlinkRepository(repo composerRepository, toUnlink map[string]bool) b // UpdatePackages runs composer update for specific packages. func UpdatePackages(dir string, packages []string) error { if !IsPHPProject(dir) { - return cli.Err(notPHPProjectComposerMessage) + return phpErr(notPHPProjectComposerMessage) } args := []string{"update"} @@ -272,7 +272,7 @@ func UpdatePackages(dir string, packages []string) error { // ListLinkedPackages returns all path repositories from composer.json. func ListLinkedPackages(dir string) ([]LinkedPackage, error) { if !IsPHPProject(dir) { - return nil, cli.Err(notPHPProjectComposerMessage) + return nil, phpErr(notPHPProjectComposerMessage) } raw, err := readComposerJSON(dir) diff --git a/pkg/php/php.go b/pkg/php/php.go index 99b958f..e78051c 100644 --- a/pkg/php/php.go +++ b/pkg/php/php.go @@ -70,7 +70,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error { defer d.mu.Unlock() if d.running { - return cli.Err("dev server is already running") + return phpErr("dev server is already running") } if err := d.applyStartOptions(opts); err != nil { @@ -79,7 +79,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error { // Verify this is a Laravel project if !IsLaravelProject(d.opts.Dir) { - return cli.Err("not a Laravel project: %s", d.opts.Dir) + return phpErr("not a Laravel project: %s", d.opts.Dir) } // Create cancellable context @@ -113,7 +113,7 @@ func (d *DevServer) applyStartOptions(opts Options) error { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } d.opts.Dir = cwd return nil @@ -126,7 +126,7 @@ func setupDevSSL(dir string, opts Options) (string, string, error) { certFile, keyFile, err := SetupSSLIfNeeded(devSSLDomain(dir, opts.Domain), SSLOptions{}) if err != nil { - return "", "", cli.WrapVerb(err, "setup", "SSL") + return "", "", phpWrapVerb(err, "setup", "SSL") } return certFile, keyFile, nil @@ -193,7 +193,7 @@ func (d *DevServer) startServices() error { var startErrors []error for _, svc := range d.services { if err := svc.Start(d.ctx); err != nil { - startErrors = append(startErrors, cli.Err("%s: %v", svc.Name(), err)) + startErrors = append(startErrors, phpErr("%s: %v", svc.Name(), err)) } } @@ -203,10 +203,10 @@ func (d *DevServer) startServices() error { for _, svc := range d.services { if err := svc.Stop(); err != nil { - startErrors = append(startErrors, cli.Err("cleanup %s: %v", svc.Name(), err)) + startErrors = append(startErrors, phpErr("cleanup %s: %v", svc.Name(), err)) } } - return cli.Err("failed to start services: %v", startErrors) + return phpErr("failed to start services: %v", startErrors) } // filterServices removes disabled services from the list. @@ -258,14 +258,14 @@ func (d *DevServer) Stop() error { for i := len(d.services) - 1; i >= 0; i-- { svc := d.services[i] if err := svc.Stop(); err != nil { - stopErrors = append(stopErrors, cli.Err("%s: %v", svc.Name(), err)) + stopErrors = append(stopErrors, phpErr("%s: %v", svc.Name(), err)) } } d.running = false if len(stopErrors) > 0 { - return cli.Err("errors stopping services: %v", stopErrors) + return phpErr("errors stopping services: %v", stopErrors) } return nil @@ -289,7 +289,7 @@ func (d *DevServer) Logs(service string, follow bool) (io.ReadCloser, error) { } } - return nil, cli.Err("service not found: %s", service) + return nil, phpErr("service not found: %s", service) } // unifiedLogs creates a reader that combines logs from all services. @@ -307,9 +307,9 @@ func (d *DevServer) unifiedLogs(follow bool) (io.ReadCloser, error) { } } if len(closeErrors) > 0 { - return nil, cli.Err("failed to get logs for %s: %v; failed to close readers: %v", svc.Name(), err, closeErrors) + return nil, phpErr("failed to get logs for %s: %v; failed to close readers: %v", svc.Name(), err, closeErrors) } - return nil, cli.Err("failed to get logs for %s: %v", svc.Name(), err) + return nil, phpErr("failed to get logs for %s: %v", svc.Name(), err) } readers = append(readers, reader) } diff --git a/pkg/php/quality.go b/pkg/php/quality.go index f91585b..021c9ca 100644 --- a/pkg/php/quality.go +++ b/pkg/php/quality.go @@ -133,7 +133,7 @@ func Format(ctx context.Context, opts FormatOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -145,7 +145,7 @@ func Format(ctx context.Context, opts FormatOptions) error { // Check if formatter is available formatter, found := DetectFormatter(opts.Dir) if !found { - return cli.Err("no formatter found (install Laravel Pint: composer require laravel/pint --dev)") + return phpErr("no formatter found (install Laravel Pint: composer require laravel/pint --dev)") } var cmdName string @@ -169,7 +169,7 @@ func Analyse(ctx context.Context, opts AnalyseOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -181,7 +181,7 @@ func Analyse(ctx context.Context, opts AnalyseOptions) error { // Check if analyser is available analyser, found := DetectAnalyser(opts.Dir) if !found { - return cli.Err("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)") + return phpErr("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)") } var cmdName string @@ -318,7 +318,7 @@ func RunPsalm(ctx context.Context, opts PsalmOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -403,7 +403,7 @@ func RunAudit(ctx context.Context, opts AuditOptions) ([]AuditResult, error) { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return nil, cli.WrapVerb(err, "get", workingDirectorySubject) + return nil, phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -553,7 +553,7 @@ func RunRector(ctx context.Context, opts RectorOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -630,7 +630,7 @@ func RunInfection(ctx context.Context, opts InfectionOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } @@ -813,7 +813,7 @@ func RunSecurityChecks(ctx context.Context, opts SecurityOptions) (*SecurityResu if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return nil, cli.WrapVerb(err, "get", workingDirectorySubject) + return nil, phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd } diff --git a/pkg/php/services.go b/pkg/php/services.go index 0ec67c9..82a5c0f 100644 --- a/pkg/php/services.go +++ b/pkg/php/services.go @@ -75,13 +75,13 @@ func (s *baseService) Status() ServiceStatus { func (s *baseService) Logs(follow bool) (io.ReadCloser, error) { if s.logPath == "" { - return nil, cli.Err("no log file available for %s", s.name) + return nil, phpErr("no log file available for %s", s.name) } m := getMedium() file, err := m.Open(s.logPath) if err != nil { - return nil, cli.WrapVerb(err, "open", "log file") + return nil, phpWrapVerb(err, "open", "log file") } if !follow { @@ -93,7 +93,7 @@ func (s *baseService) Logs(follow bool) (io.ReadCloser, error) { osFile, ok := file.(*os.File) if !ok { _ = file.Close() - return nil, cli.Err("log file is not a regular file") + return nil, phpErr("log file is not a regular file") } return newTailReader(osFile), nil } @@ -103,26 +103,26 @@ func (s *baseService) startProcess(ctx context.Context, cmdName string, args []s defer s.mu.Unlock() if s.running { - return cli.Err("%s is already running", s.name) + return phpErr("%s is already running", s.name) } // Create log file m := getMedium() logDir := filepath.Join(s.dir, ".core", "logs") if err := m.EnsureDir(logDir); err != nil { - return cli.WrapVerb(err, "create", "log directory") + return phpWrapVerb(err, "create", "log directory") } s.logPath = filepath.Join(logDir, cli.Sprintf("%s.log", strings.ToLower(s.name))) logWriter, err := m.Create(s.logPath) if err != nil { - return cli.WrapVerb(err, "create", "log file") + return phpWrapVerb(err, "create", "log file") } // Type assert to get the underlying *os.File for use with exec.Cmd logFile, ok := logWriter.(*os.File) if !ok { _ = logWriter.Close() - return cli.Err("log file is not a regular file") + return phpErr("log file is not a regular file") } s.logFile = logFile @@ -138,10 +138,10 @@ func (s *baseService) startProcess(ctx context.Context, cmdName string, args []s if err := s.cmd.Start(); err != nil { if closeErr := logFile.Close(); closeErr != nil { - err = cli.Err("%v; close log file: %v", err, closeErr) + err = phpErr("%v; close log file: %v", err, closeErr) } s.lastError = err - return cli.WrapVerb(err, "start", s.name) + return phpWrapVerb(err, "start", s.name) } s.running = true diff --git a/pkg/php/ssl.go b/pkg/php/ssl.go index 3a2b764..756feba 100644 --- a/pkg/php/ssl.go +++ b/pkg/php/ssl.go @@ -27,13 +27,13 @@ func GetSSLDir(opts SSLOptions) (string, error) { if dir == "" { home, err := os.UserHomeDir() if err != nil { - return "", cli.WrapVerb(err, "get", "home directory") + return "", phpWrapVerb(err, "get", "home directory") } dir = filepath.Join(home, DefaultSSLDir) } if err := m.EnsureDir(dir); err != nil { - return "", cli.WrapVerb(err, "create", "SSL directory") + return "", phpWrapVerb(err, "create", "SSL directory") } return dir, nil @@ -77,7 +77,7 @@ func CertsExist(domain string, opts SSLOptions) bool { func SetupSSL(domain string, opts SSLOptions) error { // Check if mkcert is installed if _, err := exec.LookPath("mkcert"); err != nil { - return cli.Err("mkcert is not installed. Install it with: brew install mkcert (macOS) or see https://github.com/FiloSottile/mkcert") + return phpErr("mkcert is not installed. Install it with: brew install mkcert (macOS) or see https://github.com/FiloSottile/mkcert") } dir, err := GetSSLDir(opts) @@ -88,7 +88,7 @@ func SetupSSL(domain string, opts SSLOptions) error { // Install local CA (idempotent operation) installCmd := exec.Command("mkcert", "-install") if output, err := installCmd.CombinedOutput(); err != nil { - return cli.Err("failed to install mkcert CA: %v\n%s", err, output) + return phpErr("failed to install mkcert CA: %v\n%s", err, output) } // Generate certificates @@ -106,7 +106,7 @@ func SetupSSL(domain string, opts SSLOptions) error { ) if output, err := genCmd.CombinedOutput(); err != nil { - return cli.Err("failed to generate certificates: %v\n%s", err, output) + return phpErr("failed to generate certificates: %v\n%s", err, output) } return nil @@ -137,13 +137,13 @@ func IsMkcertInstalled() bool { // InstallMkcertCA installs the local CA for mkcert. func InstallMkcertCA() error { if !IsMkcertInstalled() { - return cli.Err("mkcert is not installed") + return phpErr("mkcert is not installed") } cmd := exec.Command("mkcert", "-install") output, err := cmd.CombinedOutput() if err != nil { - return cli.Err("failed to install mkcert CA: %v\n%s", err, output) + return phpErr("failed to install mkcert CA: %v\n%s", err, output) } return nil @@ -152,13 +152,13 @@ func InstallMkcertCA() error { // GetMkcertCARoot returns the path to the mkcert CA root directory. func GetMkcertCARoot() (string, error) { if !IsMkcertInstalled() { - return "", cli.Err("mkcert is not installed") + return "", phpErr("mkcert is not installed") } cmd := exec.Command("mkcert", "-CAROOT") output, err := cmd.Output() if err != nil { - return "", cli.WrapVerb(err, "get", "mkcert CA root") + return "", phpWrapVerb(err, "get", "mkcert CA root") } return filepath.Clean(string(output)), nil diff --git a/pkg/php/testing.go b/pkg/php/testing.go index d47f974..d4446ae 100644 --- a/pkg/php/testing.go +++ b/pkg/php/testing.go @@ -6,8 +6,6 @@ import ( "os" "os/exec" "path/filepath" - - "dappco.re/go/cli/pkg/cli" ) // TestOptions configures PHP test execution. @@ -65,7 +63,7 @@ func RunTests(ctx context.Context, opts TestOptions) error { if opts.Dir == "" { cwd, err := os.Getwd() if err != nil { - return cli.WrapVerb(err, "get", workingDirectorySubject) + return phpWrapVerb(err, "get", workingDirectorySubject) } opts.Dir = cwd }