From ae1b51391a949654c55f8da291ba7143b27cbb25 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 15:59:59 -0700 Subject: [PATCH 1/7] use mage sh for git --- common/tea_cmd/git.go | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/common/tea_cmd/git.go b/common/tea_cmd/git.go index f36f849..8f04566 100644 --- a/common/tea_cmd/git.go +++ b/common/tea_cmd/git.go @@ -3,13 +3,13 @@ package tea_cmd import ( "bytes" tea "github.com/charmbracelet/bubbletea" + "github.com/magefile/mage/sh" "os" "os/exec" ) type GitCloneFinishMsg struct { - ErrBuf *bytes.Buffer - Err error + Err error } func Run(cmd *exec.Cmd) (*bytes.Buffer, error) { @@ -27,41 +27,36 @@ func Run(cmd *exec.Cmd) (*bytes.Buffer, error) { func GitCloneCmd(url string, targetDir string, initMsg string) tea.Cmd { return func() tea.Msg { - cmd := exec.Command("git", "clone", url, targetDir) - errBuf, err := Run(cmd) + err := sh.Run("git", "clone", url, targetDir) if err != nil { - return GitCloneFinishMsg{ErrBuf: errBuf, Err: err} + return GitCloneFinishMsg{Err: err} } err = os.Chdir(targetDir) if err != nil { - return GitCloneFinishMsg{ErrBuf: nil, Err: err} + return GitCloneFinishMsg{Err: err} } - cmd = exec.Command("rm", "-rf", ".git") - errBuf, err = Run(cmd) + err = sh.Run("rm", "-rf", ".git") if err != nil { - return GitCloneFinishMsg{ErrBuf: errBuf, Err: err} + return GitCloneFinishMsg{Err: err} } - cmd = exec.Command("git", "init") - errBuf, err = Run(cmd) + err = sh.Run("git", "init") if err != nil { - return GitCloneFinishMsg{ErrBuf: errBuf, Err: err} + return GitCloneFinishMsg{Err: err} } - cmd = exec.Command("git", "add", "-A") - errBuf, err = Run(cmd) + err = sh.Run("git", "add", "-A") if err != nil { - return GitCloneFinishMsg{ErrBuf: errBuf, Err: err} + return GitCloneFinishMsg{Err: err} } - cmd = exec.Command("git", "commit", "-m", initMsg) - errBuf, err = Run(cmd) + err = sh.Run("git", "commit", "-m", initMsg) if err != nil { - return GitCloneFinishMsg{ErrBuf: errBuf, Err: err} + return GitCloneFinishMsg{Err: err} } - return GitCloneFinishMsg{ErrBuf: nil, Err: nil} + return GitCloneFinishMsg{Err: nil} } } From d5996300a66abb9f947ac7f28a1987b933c4f6b0 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 16:03:30 -0700 Subject: [PATCH 2/7] refactor world create error to not use ErrBuf --- cmd/create.go | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 cmd/create.go diff --git a/cmd/create.go b/cmd/create.go new file mode 100644 index 0000000..8eb600a --- /dev/null +++ b/cmd/create.go @@ -0,0 +1,186 @@ +package cmd + +import ( + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" + "pkg.world.dev/world-cli/cmd/action" + "pkg.world.dev/world-cli/cmd/component/steps" + "pkg.world.dev/world-cli/cmd/style" + "strings" +) + +const TemplateGitUrl = "https://github.com/Argus-Labs/starter-game-template.git" + +var CreateDeps = []action.Dependency{ + action.GitDependency, +} + +///////////////// +// Cobra Setup // +///////////////// + +func init() { + rootCmd.AddCommand(createCmd) +} + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Creates a newModel game shard from scratch.", + Long: `Creates a World Engine game shard based on https://github.com/Argus-Labs/starter-game-template`, + RunE: func(_ *cobra.Command, args []string) error { + p := tea.NewProgram(NewWorldCreateModel(args)) + if _, err := p.Run(); err != nil { + return err + } + return nil + }, +} + +////////////////////// +// Bubble Tea Model // +////////////////////// + +type WorldCreateModel struct { + logs []string + steps steps.Model + projectNameInput textinput.Model + args []string + depStatus []action.DependencyStatus + depStatusErr error + err error +} + +func NewWorldCreateModel(args []string) WorldCreateModel { + pnInput := textinput.New() + pnInput.Prompt = style.DoubleRightIcon.Render() + pnInput.Placeholder = "starter-game" + pnInput.Focus() + pnInput.Width = 50 + + createSteps := steps.New() + createSteps.Steps = []steps.Entry{ + steps.NewStep("Set game shard name"), + steps.NewStep("Initialize game shard with starter-game-template"), + } + + // Set the project text if it was passed in as an argument + if len(args) == 1 { + pnInput.SetValue(args[0]) + } + + return WorldCreateModel{ + steps: createSteps, + projectNameInput: pnInput, + args: args, + } +} + +////////////////////////// +// Bubble Tea Lifecycle // +////////////////////////// + +// Init returns an initial command for the application to run +func (m WorldCreateModel) Init() tea.Cmd { + // If the project name was passed in as an argument, skip the 1st step + if m.projectNameInput.Value() != "" { + return tea.Sequence(action.CheckDependenciesCmd(CreateDeps), textinput.Blink, m.steps.StartCmd(), m.steps.CompleteStepCmd(nil)) + } + return tea.Sequence(action.CheckDependenciesCmd(CreateDeps), textinput.Blink, m.steps.StartCmd()) +} + +// Update handles incoming events and updates the model accordingly +func (m WorldCreateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case action.CheckDependenciesMsg: + m.depStatus = msg.DepStatus + m.depStatusErr = msg.Err + if msg.Err != nil { + return m, tea.Quit + } else { + return m, nil + } + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter: + if m.projectNameInput.Value() == "" { + m.projectNameInput.SetValue("starter-game") + } + m.projectNameInput.Blur() + return m, m.steps.CompleteStepCmd(nil) + case tea.KeyCtrlC: + return m, tea.Quit + } + case NewLogMsg: + m.logs = append(m.logs, msg.Log) + return m, nil + case steps.SignalStepStartedMsg: + // If step 1 is started, dispatch the git clone command + if msg.Index == 1 { + return m, tea.Sequence( + NewLogCmd(style.ChevronIcon.Render()+"Cloning starter-game-template..."), + action.GitCloneCmd(TemplateGitUrl, m.projectNameInput.Value(), "Initial commit from World CLI"), + ) + } + return m, nil + case steps.SignalStepCompletedMsg: + // If step 1 is completed, log success message + if msg.Index == 1 { + return m, NewLogCmd(style.ChevronIcon.Render() + "Successfully created a newModel game shard based on starter-game-template!") + } + case steps.SignalStepErrorMsg: + // Log error, then quit + return m, tea.Sequence(NewLogCmd(style.CrossIcon.Render()+"Error: "+msg.Err.Error()), tea.Quit) + case steps.SignalAllStepCompletedMsg: + // All done, quit + return m, tea.Quit + case action.GitCloneFinishMsg: + // If there is an error, log stderr then mark step as failed + if msg.Err != nil { + m.logs = append(m.logs, style.CrossIcon.Render()+msg.Err.Error()) + return m, m.steps.CompleteStepCmd(msg.Err) + } + + // Otherwise, mark step as completed + return m, m.steps.CompleteStepCmd(nil) + default: + var cmd tea.Cmd + m.steps, cmd = m.steps.Update(msg) + return m, cmd + } + + var cmd tea.Cmd + m.projectNameInput, cmd = m.projectNameInput.Update(msg) + return m, cmd +} + +// View renders the UI based on the data in the WorldCreateModel +func (m WorldCreateModel) View() string { + if m.depStatusErr != nil { + return action.PrettyPrintMissingDependency(m.depStatus) + } + + output := "" + output += m.steps.View() + output += "\n\n" + output += style.QuestionIcon.Render() + "What is your game shard name? " + m.projectNameInput.View() + output += "\n\n" + output += strings.Join(m.logs, "\n") + output += "\n\n" + + return output +} + +///////////////////////// +// Bubble Tea Commands // +///////////////////////// + +type NewLogMsg struct { + Log string +} + +func NewLogCmd(log string) tea.Cmd { + return func() tea.Msg { + return NewLogMsg{Log: log} + } +} From 74c432813dc4f02a56d4d88d022c80ec1bedc8d3 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 16:57:47 -0700 Subject: [PATCH 3/7] rebase --- cmd/cardinal/create.go | 8 +- cmd/create.go | 186 --------------------------------------- common/tea_cmd/docker.go | 12 +-- 3 files changed, 7 insertions(+), 199 deletions(-) delete mode 100644 cmd/create.go diff --git a/cmd/cardinal/create.go b/cmd/cardinal/create.go index 620e224..4bacf3a 100644 --- a/cmd/cardinal/create.go +++ b/cmd/cardinal/create.go @@ -4,7 +4,6 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" - "io" "pkg.world.dev/world-cli/common/tea_cmd" "pkg.world.dev/world-cli/tea/component/steps" "pkg.world.dev/world-cli/tea/style" @@ -144,12 +143,7 @@ func (m WorldCreateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea_cmd.GitCloneFinishMsg: // If there is an error, log stderr then mark step as failed if msg.Err != nil { - stderrBytes, err := io.ReadAll(msg.ErrBuf) - if err != nil { - m.logs = append(m.logs, style.CrossIcon.Render()+"Error occurred while reading stderr") - } else { - m.logs = append(m.logs, style.CrossIcon.Render()+string(stderrBytes)) - } + m.logs = append(m.logs, style.CrossIcon.Render()+msg.Err.Error()) return m, m.steps.CompleteStepCmd(msg.Err) } diff --git a/cmd/create.go b/cmd/create.go deleted file mode 100644 index 8eb600a..0000000 --- a/cmd/create.go +++ /dev/null @@ -1,186 +0,0 @@ -package cmd - -import ( - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/spf13/cobra" - "pkg.world.dev/world-cli/cmd/action" - "pkg.world.dev/world-cli/cmd/component/steps" - "pkg.world.dev/world-cli/cmd/style" - "strings" -) - -const TemplateGitUrl = "https://github.com/Argus-Labs/starter-game-template.git" - -var CreateDeps = []action.Dependency{ - action.GitDependency, -} - -///////////////// -// Cobra Setup // -///////////////// - -func init() { - rootCmd.AddCommand(createCmd) -} - -var createCmd = &cobra.Command{ - Use: "create", - Short: "Creates a newModel game shard from scratch.", - Long: `Creates a World Engine game shard based on https://github.com/Argus-Labs/starter-game-template`, - RunE: func(_ *cobra.Command, args []string) error { - p := tea.NewProgram(NewWorldCreateModel(args)) - if _, err := p.Run(); err != nil { - return err - } - return nil - }, -} - -////////////////////// -// Bubble Tea Model // -////////////////////// - -type WorldCreateModel struct { - logs []string - steps steps.Model - projectNameInput textinput.Model - args []string - depStatus []action.DependencyStatus - depStatusErr error - err error -} - -func NewWorldCreateModel(args []string) WorldCreateModel { - pnInput := textinput.New() - pnInput.Prompt = style.DoubleRightIcon.Render() - pnInput.Placeholder = "starter-game" - pnInput.Focus() - pnInput.Width = 50 - - createSteps := steps.New() - createSteps.Steps = []steps.Entry{ - steps.NewStep("Set game shard name"), - steps.NewStep("Initialize game shard with starter-game-template"), - } - - // Set the project text if it was passed in as an argument - if len(args) == 1 { - pnInput.SetValue(args[0]) - } - - return WorldCreateModel{ - steps: createSteps, - projectNameInput: pnInput, - args: args, - } -} - -////////////////////////// -// Bubble Tea Lifecycle // -////////////////////////// - -// Init returns an initial command for the application to run -func (m WorldCreateModel) Init() tea.Cmd { - // If the project name was passed in as an argument, skip the 1st step - if m.projectNameInput.Value() != "" { - return tea.Sequence(action.CheckDependenciesCmd(CreateDeps), textinput.Blink, m.steps.StartCmd(), m.steps.CompleteStepCmd(nil)) - } - return tea.Sequence(action.CheckDependenciesCmd(CreateDeps), textinput.Blink, m.steps.StartCmd()) -} - -// Update handles incoming events and updates the model accordingly -func (m WorldCreateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case action.CheckDependenciesMsg: - m.depStatus = msg.DepStatus - m.depStatusErr = msg.Err - if msg.Err != nil { - return m, tea.Quit - } else { - return m, nil - } - case tea.KeyMsg: - switch msg.Type { - case tea.KeyEnter: - if m.projectNameInput.Value() == "" { - m.projectNameInput.SetValue("starter-game") - } - m.projectNameInput.Blur() - return m, m.steps.CompleteStepCmd(nil) - case tea.KeyCtrlC: - return m, tea.Quit - } - case NewLogMsg: - m.logs = append(m.logs, msg.Log) - return m, nil - case steps.SignalStepStartedMsg: - // If step 1 is started, dispatch the git clone command - if msg.Index == 1 { - return m, tea.Sequence( - NewLogCmd(style.ChevronIcon.Render()+"Cloning starter-game-template..."), - action.GitCloneCmd(TemplateGitUrl, m.projectNameInput.Value(), "Initial commit from World CLI"), - ) - } - return m, nil - case steps.SignalStepCompletedMsg: - // If step 1 is completed, log success message - if msg.Index == 1 { - return m, NewLogCmd(style.ChevronIcon.Render() + "Successfully created a newModel game shard based on starter-game-template!") - } - case steps.SignalStepErrorMsg: - // Log error, then quit - return m, tea.Sequence(NewLogCmd(style.CrossIcon.Render()+"Error: "+msg.Err.Error()), tea.Quit) - case steps.SignalAllStepCompletedMsg: - // All done, quit - return m, tea.Quit - case action.GitCloneFinishMsg: - // If there is an error, log stderr then mark step as failed - if msg.Err != nil { - m.logs = append(m.logs, style.CrossIcon.Render()+msg.Err.Error()) - return m, m.steps.CompleteStepCmd(msg.Err) - } - - // Otherwise, mark step as completed - return m, m.steps.CompleteStepCmd(nil) - default: - var cmd tea.Cmd - m.steps, cmd = m.steps.Update(msg) - return m, cmd - } - - var cmd tea.Cmd - m.projectNameInput, cmd = m.projectNameInput.Update(msg) - return m, cmd -} - -// View renders the UI based on the data in the WorldCreateModel -func (m WorldCreateModel) View() string { - if m.depStatusErr != nil { - return action.PrettyPrintMissingDependency(m.depStatus) - } - - output := "" - output += m.steps.View() - output += "\n\n" - output += style.QuestionIcon.Render() + "What is your game shard name? " + m.projectNameInput.View() - output += "\n\n" - output += strings.Join(m.logs, "\n") - output += "\n\n" - - return output -} - -///////////////////////// -// Bubble Tea Commands // -///////////////////////// - -type NewLogMsg struct { - Log string -} - -func NewLogCmd(log string) tea.Cmd { - return func() tea.Msg { - return NewLogMsg{Log: log} - } -} diff --git a/common/tea_cmd/docker.go b/common/tea_cmd/docker.go index 622bab4..c43df53 100644 --- a/common/tea_cmd/docker.go +++ b/common/tea_cmd/docker.go @@ -104,11 +104,11 @@ func DockerStart(build bool, services []DockerService) error { return err } if build { - if err := sh.Run("docker", dockerArgs("compose up --build -d", services)...); err != nil { + if err := sh.RunV("docker", dockerArgs("compose up --build -d", services)...); err != nil { return err } } else { - if err := sh.Run("docker", dockerArgs("compose up -d", services)...); err != nil { + if err := sh.RunV("docker", dockerArgs("compose up -d", services)...); err != nil { return err } } @@ -165,7 +165,7 @@ func DockerRestart(build bool, services []DockerService) error { return err } } else { - if err := sh.Run("docker", dockerArgs("compose restart", services)...); err != nil { + if err := sh.RunV("docker", dockerArgs("compose restart", services)...); err != nil { return err } } @@ -178,7 +178,7 @@ func DockerStop(services []DockerService) error { if services == nil { return fmt.Errorf("no service names provided") } - if err := sh.Run("docker", dockerArgs("compose stop", services)...); err != nil { + if err := sh.RunV("docker", dockerArgs("compose stop", services)...); err != nil { return err } return nil @@ -223,10 +223,10 @@ func prepareDir(dir string) error { if err := sh.Rm("./vendor"); err != nil { return err } - if err := sh.Run("go", "mod", "tidy"); err != nil { + if err := sh.RunV("go", "mod", "tidy"); err != nil { return err } - if err := sh.Run("go", "mod", "vendor"); err != nil { + if err := sh.RunV("go", "mod", "vendor"); err != nil { return err } if err := os.Chdir(".."); err != nil { From 2aba3d1fb1040d32b657acb9bf99ccd107f8be53 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 17:44:20 -0700 Subject: [PATCH 4/7] add comments --- cmd/cardinal/cardinal.go | 2 ++ cmd/cardinal/create.go | 2 ++ cmd/cardinal/purge.go | 2 ++ cmd/cardinal/restart.go | 2 ++ cmd/cardinal/start.go | 2 ++ cmd/cardinal/stop.go | 2 ++ cmd/doctor.go | 2 ++ cmd/root.go | 3 ++- cmd/version.go | 2 ++ 9 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cmd/cardinal/cardinal.go b/cmd/cardinal/cardinal.go index 9e5cda7..483b2d0 100644 --- a/cmd/cardinal/cardinal.go +++ b/cmd/cardinal/cardinal.go @@ -11,6 +11,8 @@ func init() { BaseCmd.AddCommand(createCmd, startCmd, restartCmd, purgeCmd, stopCmd) } +// BaseCmd is the base command for the cardinal subcommand +// Usage: `world cardinal` var BaseCmd = &cobra.Command{ Use: "cardinal", Short: "Manage your Cardinal game shard project", diff --git a/cmd/cardinal/create.go b/cmd/cardinal/create.go index 4bacf3a..afdb19e 100644 --- a/cmd/cardinal/create.go +++ b/cmd/cardinal/create.go @@ -20,6 +20,8 @@ var CreateDeps = []tea_cmd.Dependency{ // Cobra Setup // ///////////////// +// createCmd creates a new World Engine project based on starter-game-template +// Usage: `world cardinal create [directory_name]` var createCmd = &cobra.Command{ Use: "create [directory_name]", Short: "Create a World Engine game shard from scratch", diff --git a/cmd/cardinal/purge.go b/cmd/cardinal/purge.go index 05d11e4..b35f12b 100644 --- a/cmd/cardinal/purge.go +++ b/cmd/cardinal/purge.go @@ -9,6 +9,8 @@ import ( // Cobra Setup // ///////////////// +// purgeCmd stops and resets the state of your Cardinal game shard +// Usage: `world cardinal purge` var purgeCmd = &cobra.Command{ Use: "purge", Short: "Stop and reset the state of your Cardinal game shard", diff --git a/cmd/cardinal/restart.go b/cmd/cardinal/restart.go index 538731b..f2355cc 100644 --- a/cmd/cardinal/restart.go +++ b/cmd/cardinal/restart.go @@ -9,6 +9,8 @@ import ( // Cobra Setup // ///////////////// +// restartCmd restarts your Cardinal game shard stack +// Usage: `world cardinal restart` var restartCmd = &cobra.Command{ Use: "restart", Short: "Restart your Cardinal game shard stack", diff --git a/cmd/cardinal/start.go b/cmd/cardinal/start.go index bda3641..8af79e5 100644 --- a/cmd/cardinal/start.go +++ b/cmd/cardinal/start.go @@ -16,6 +16,8 @@ func init() { startCmd.Flags().String("mode", "", "Run with special mode [detach/integration-test]") } +// startCmd starts your Cardinal game shard stack +// Usage: `world cardinal start` var startCmd = &cobra.Command{ Use: "start", Short: "Start your Cardinal game shard stack", diff --git a/cmd/cardinal/stop.go b/cmd/cardinal/stop.go index 387d216..2d11afe 100644 --- a/cmd/cardinal/stop.go +++ b/cmd/cardinal/stop.go @@ -9,6 +9,8 @@ import ( // Cobra Setup // ///////////////// +// stopCmd stops your Cardinal game shard stack +// Usage: `world cardinal stop` var stopCmd = &cobra.Command{ Use: "stop", Short: "Stop your Cardinal game shard stack", diff --git a/cmd/doctor.go b/cmd/doctor.go index 6165448..bebd48a 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -19,6 +19,8 @@ var DoctorDeps = []tea_cmd.Dependency{ // Cobra Setup // ///////////////// +// doctorCmd checks that required dependencies are installed +// Usage: `world doctor` var doctorCmd = &cobra.Command{ Use: "doctor", Short: "Check that required dependencies are installed", diff --git a/cmd/root.go b/cmd/root.go index 1044063..597f5bb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,8 @@ func init() { rootCmd.AddCommand(cardinal.BaseCmd) } -// rootCmd represents the base command when called without any subcommands +// rootCmd represents the base command +// Usage: `world` var rootCmd = &cobra.Command{ Use: "world", Short: "A swiss army knife for World Engine projects", diff --git a/cmd/version.go b/cmd/version.go index 9564477..f9d770f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" ) +// versionCmd print the version number of World CLI +// Usage: `world version` var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of World CLI", From 1e822e9dbc597dcfa5cf6f347a6582172e922ed0 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 17:44:36 -0700 Subject: [PATCH 5/7] refactor git --- common/tea_cmd/git.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/common/tea_cmd/git.go b/common/tea_cmd/git.go index 8f04566..802ebf1 100644 --- a/common/tea_cmd/git.go +++ b/common/tea_cmd/git.go @@ -5,29 +5,24 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/magefile/mage/sh" "os" - "os/exec" ) type GitCloneFinishMsg struct { Err error } -func Run(cmd *exec.Cmd) (*bytes.Buffer, error) { +func git(args ...string) error { var outBuff, errBuff bytes.Buffer - cmd.Stdout = &outBuff - cmd.Stderr = &errBuff - - err := cmd.Run() + _, err := sh.Exec(nil, &outBuff, &errBuff, args[0], args[1:]...) if err != nil { - return &errBuff, err + return err } - - return nil, nil + return nil } func GitCloneCmd(url string, targetDir string, initMsg string) tea.Cmd { return func() tea.Msg { - err := sh.Run("git", "clone", url, targetDir) + err := git("clone", url, targetDir) if err != nil { return GitCloneFinishMsg{Err: err} } @@ -42,17 +37,17 @@ func GitCloneCmd(url string, targetDir string, initMsg string) tea.Cmd { return GitCloneFinishMsg{Err: err} } - err = sh.Run("git", "init") + err = git("init") if err != nil { return GitCloneFinishMsg{Err: err} } - err = sh.Run("git", "add", "-A") + err = git("add", "-A") if err != nil { return GitCloneFinishMsg{Err: err} } - err = sh.Run("git", "commit", "-m", initMsg) + err = git("commit", "-m", initMsg) if err != nil { return GitCloneFinishMsg{Err: err} } From 2401417ddc586886ed9e6040d72981cb325aa23a Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 17:44:48 -0700 Subject: [PATCH 6/7] use run instead of runv --- common/tea_cmd/docker.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/common/tea_cmd/docker.go b/common/tea_cmd/docker.go index c43df53..32c2db3 100644 --- a/common/tea_cmd/docker.go +++ b/common/tea_cmd/docker.go @@ -89,7 +89,7 @@ func DockerBuild() error { if err := prepareDirs("cardinal", "nakama"); err != nil { return err } - if err := sh.RunV("docker", "compose", "build"); err != nil { + if err := sh.Run("docker", "compose", "build"); err != nil { return err } return nil @@ -104,11 +104,11 @@ func DockerStart(build bool, services []DockerService) error { return err } if build { - if err := sh.RunV("docker", dockerArgs("compose up --build -d", services)...); err != nil { + if err := sh.Run("docker", dockerArgs("compose up --build -d", services)...); err != nil { return err } } else { - if err := sh.RunV("docker", dockerArgs("compose up -d", services)...); err != nil { + if err := sh.Run("docker", dockerArgs("compose up -d", services)...); err != nil { return err } } @@ -123,7 +123,7 @@ func DockerStartTest() error { if err := prepareDirs("testsuite", "cardinal", "nakama"); err != nil { return err } - if err := sh.RunV("docker", "compose", "up", "--build", "--abort-on-container-exit", "--exit-code-from", "testsuite", "--attach", "testsuite"); err != nil { + if err := sh.Run("docker", "compose", "up", "--build", "--abort-on-container-exit", "--exit-code-from", "testsuite", "--attach", "testsuite"); err != nil { return err } return nil @@ -135,7 +135,7 @@ func DockerStartDebug() error { if err := prepareDirs("cardinal", "nakama"); err != nil { return err } - if err := sh.RunV("docker", "compose", "-f", "docker-compose-debug.yml", "up", "--build", "cardinal", "nakama"); err != nil { + if err := sh.Run("docker", "compose", "-f", "docker-compose-debug.yml", "up", "--build", "cardinal", "nakama"); err != nil { return err } return nil @@ -146,7 +146,7 @@ func DockerStartDetach() error { if err := prepareDirs("cardinal", "nakama"); err != nil { return err } - if err := sh.RunV("docker", "compose", "up", "--detach", "--wait", "--wait-timeout", "60"); err != nil { + if err := sh.Run("docker", "compose", "up", "--detach", "--wait", "--wait-timeout", "60"); err != nil { return err } return nil @@ -165,7 +165,7 @@ func DockerRestart(build bool, services []DockerService) error { return err } } else { - if err := sh.RunV("docker", dockerArgs("compose restart", services)...); err != nil { + if err := sh.Run("docker", dockerArgs("compose restart", services)...); err != nil { return err } } @@ -178,7 +178,7 @@ func DockerStop(services []DockerService) error { if services == nil { return fmt.Errorf("no service names provided") } - if err := sh.RunV("docker", dockerArgs("compose stop", services)...); err != nil { + if err := sh.Run("docker", dockerArgs("compose stop", services)...); err != nil { return err } return nil @@ -187,7 +187,7 @@ func DockerStop(services []DockerService) error { // DockerPurge stops and deletes all docker containers and data volumes // This will completely wipe the state, if you only want to stop the containers, use DockerStop func DockerPurge() error { - return sh.RunV("docker", "compose", "down", "--volumes") + return sh.Run("docker", "compose", "down", "--volumes") } // dockerArgs converts a string of docker args and slice of DockerService to a single slice of strings. @@ -223,10 +223,10 @@ func prepareDir(dir string) error { if err := sh.Rm("./vendor"); err != nil { return err } - if err := sh.RunV("go", "mod", "tidy"); err != nil { + if err := sh.Run("go", "mod", "tidy"); err != nil { return err } - if err := sh.RunV("go", "mod", "vendor"); err != nil { + if err := sh.Run("go", "mod", "vendor"); err != nil { return err } if err := os.Chdir(".."); err != nil { From 456fcdb0105ce21a44fc64d353b8c38bb63fb5a0 Mon Sep 17 00:00:00 2001 From: smsunarto Date: Mon, 30 Oct 2023 18:57:00 -0700 Subject: [PATCH 7/7] feat: add world cardinal dev cmd --- .gitignore | 5 +- cmd/cardinal/cardinal.go | 2 +- cmd/cardinal/dev.go | 131 +++++++++++++++++++++++++++++++++++---- cmd/root.go | 1 - common/tea_cmd/docker.go | 10 +-- common/tea_cmd/git.go | 2 +- 6 files changed, 131 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index d6280be..4f14390 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ world-cli .DS_Store # Jetbrains -.idea \ No newline at end of file +.idea + +# Test +/starter-game \ No newline at end of file diff --git a/cmd/cardinal/cardinal.go b/cmd/cardinal/cardinal.go index 483b2d0..aefa27b 100644 --- a/cmd/cardinal/cardinal.go +++ b/cmd/cardinal/cardinal.go @@ -8,7 +8,7 @@ import ( func init() { // Register subcommands - `world cardinal [subcommand]` - BaseCmd.AddCommand(createCmd, startCmd, restartCmd, purgeCmd, stopCmd) + BaseCmd.AddCommand(createCmd, startCmd, devCmd, restartCmd, purgeCmd, stopCmd) } // BaseCmd is the base command for the cardinal subcommand diff --git a/cmd/cardinal/dev.go b/cmd/cardinal/dev.go index ac028e7..c081908 100644 --- a/cmd/cardinal/dev.go +++ b/cmd/cardinal/dev.go @@ -1,34 +1,143 @@ package cardinal import ( - tea "github.com/charmbracelet/bubbletea" + "fmt" + "github.com/magefile/mage/sh" "github.com/spf13/cobra" - "pkg.world.dev/world-cli/common" - "pkg.world.dev/world-cli/tea/component" + "os" + "os/exec" + "os/signal" + "pkg.world.dev/world-cli/tea/style" + "syscall" +) + +const ( + CardinalPort = "3333" + RedisPort = "6379" + WebdisPort = "7379" ) ///////////////// // Cobra Setup // ///////////////// +// devCmd runs Cardinal in development mode +// Usage: `world cardinal dev` var devCmd = &cobra.Command{ Use: "dev", Short: "TODO", Long: `TODO`, RunE: func(cmd *cobra.Command, args []string) error { - //total width/height doesn't matter here as soon as you put it into the bubbletea framework everything will resize to fit window. - lowerLeftBox := component.NewServerStatusApp() - lowerLeftBoxInfo := component.CreateBoxInfo(lowerLeftBox, 50, 30, component.WithBorder) - triLayout := component.BuildTriLayoutHorizontal(0, 0, nil, lowerLeftBoxInfo, nil) - _, _, _, err := common.RunShellCommandReturnBuffers("cd cardinal && go run .", 1024) + err := os.Chdir("cardinal") + if err != nil { + return err + } + + // Run Redis container + err = sh.Run("docker", "run", "-d", "-p", fmt.Sprintf("%s:%s", RedisPort, RedisPort), "-e", "LOCAL_REDIS=true", "--name", "cardinal-dev-redis", "redis") if err != nil { return err } - p := tea.NewProgram(triLayout, tea.WithAltScreen()) - _, err = p.Run() + + // Run Webdis container - this provides a REST wrapper around Redis + err = sh.Run("docker", "run", "-d", "-p", fmt.Sprintf("%s:%s", WebdisPort, WebdisPort), "--link", "cardinal-dev-redis:redis", "--name", "cardinal-dev-webdis", "anapsix/webdis") if err != nil { return err } - return nil + + // Run Cardinal + execCmd, err := runCardinal() + if err != nil { + return err + } + + // Create a channel to receive termination signals + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM) + + // Create a channel to receive errors from the command + cmdErr := make(chan error, 1) + + go func() { + err := execCmd.Wait() + cmdErr <- err + }() + + select { + case <-signalCh: + // Shutdown signal received, attempt to gracefully stop the command + errCleanup := cleanup() + if errCleanup != nil { + return errCleanup + } + + err = execCmd.Process.Signal(syscall.SIGTERM) + if err != nil { + return err + } + + return nil + + case err := <-cmdErr: + fmt.Println(err) + errCleanup := cleanup() + if errCleanup != nil { + return errCleanup + } + return nil + } }, } + +// runCardinal runs cardinal in dev mode. +// We run cardinal without docker to make it easier to debug and skip the docker image build step +func runCardinal() (*exec.Cmd, error) { + fmt.Print(style.CLIHeader("Cardinal", "Running Cardinal in dev mode"), "\n") + fmt.Println(style.BoldText.Render("Press Ctrl+C to stop")) + fmt.Println() + fmt.Println(fmt.Sprintf("Redis: localhost:%s", RedisPort)) + fmt.Println(fmt.Sprintf("Webdis: localhost:%s", WebdisPort)) + fmt.Println(fmt.Sprintf("Cardinal: localhost:%s", CardinalPort)) + fmt.Println() + + env := map[string]string{ + "REDIS_MODE": "normal", + "CARDINAL_PORT": CardinalPort, + "REDIS_ADDR": fmt.Sprintf("localhost:%s", RedisPort), + "DEPLOY_MODE": "development", + } + + cmd := exec.Command("go", "run", ".") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + for k, v := range env { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + + err := cmd.Start() + if err != nil { + return cmd, err + } + + return cmd, nil +} + +// cleanup stops and removes the Redis and Webdis containers +func cleanup() error { + err := sh.Run("docker", "rm", "-f", "cardinal-dev-redis") + if err != nil { + fmt.Println("Failed to delete Redis container automatically") + fmt.Println("Please delete it manually with `docker rm -f cardinal-dev-redis`") + return err + } + + err = sh.Run("docker", "rm", "-f", "cardinal-dev-webdis") + if err != nil { + fmt.Println("Failed to delete Webdis container automatically") + fmt.Println("Please delete it manually with `docker rm -f cardinal-dev-webdis`") + return err + } + + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 597f5bb..e5beb90 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -36,6 +36,5 @@ var rootCmd = &cobra.Command{ func Execute() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) if err := rootCmd.Execute(); err != nil { - log.Fatal().Err(err).Msg("Failed to execute root command") } } diff --git a/common/tea_cmd/docker.go b/common/tea_cmd/docker.go index 32c2db3..c02b086 100644 --- a/common/tea_cmd/docker.go +++ b/common/tea_cmd/docker.go @@ -86,7 +86,7 @@ func DockerCmd(action DockerCmdArgs) tea.Cmd { // DockerBuild builds all docker images func DockerBuild() error { - if err := prepareDirs("cardinal", "nakama"); err != nil { + if err := prepareDirs("cardinal"); err != nil { return err } if err := sh.Run("docker", "compose", "build"); err != nil { @@ -100,7 +100,7 @@ func DockerStart(build bool, services []DockerService) error { if services == nil { return fmt.Errorf("no service names provided") } - if err := prepareDirs("cardinal", "nakama"); err != nil { + if err := prepareDirs("cardinal"); err != nil { return err } if build { @@ -120,7 +120,7 @@ func DockerStartTest() error { if err := DockerPurge(); err != nil { return err } - if err := prepareDirs("testsuite", "cardinal", "nakama"); err != nil { + if err := prepareDirs("testsuite", "cardinal"); err != nil { return err } if err := sh.Run("docker", "compose", "up", "--build", "--abort-on-container-exit", "--exit-code-from", "testsuite", "--attach", "testsuite"); err != nil { @@ -132,7 +132,7 @@ func DockerStartTest() error { // DockerStartDebug starts Nakama and Cardinal in debug mode with Cardinal debugger listening on port 40000 // Note: Cardinal server will not run until a debugger is attached port 40000 func DockerStartDebug() error { - if err := prepareDirs("cardinal", "nakama"); err != nil { + if err := prepareDirs("cardinal"); err != nil { return err } if err := sh.Run("docker", "compose", "-f", "docker-compose-debug.yml", "up", "--build", "cardinal", "nakama"); err != nil { @@ -143,7 +143,7 @@ func DockerStartDebug() error { // DockerStartDetach starts Nakama and Cardinal with detach and wait-timeout 60s (useful for CI workflow) func DockerStartDetach() error { - if err := prepareDirs("cardinal", "nakama"); err != nil { + if err := prepareDirs("cardinal"); err != nil { return err } if err := sh.Run("docker", "compose", "up", "--detach", "--wait", "--wait-timeout", "60"); err != nil { diff --git a/common/tea_cmd/git.go b/common/tea_cmd/git.go index 802ebf1..2615c29 100644 --- a/common/tea_cmd/git.go +++ b/common/tea_cmd/git.go @@ -13,7 +13,7 @@ type GitCloneFinishMsg struct { func git(args ...string) error { var outBuff, errBuff bytes.Buffer - _, err := sh.Exec(nil, &outBuff, &errBuff, args[0], args[1:]...) + _, err := sh.Exec(nil, &outBuff, &errBuff, "git", args...) if err != nil { return err }