Repository: vegaprotocol/vegawallet (go)

Issue:
Help find/change wallet config
Occasionally users will want to change an existing network config e.g. add or remove GRPC hosts.

importing again isn't ideal as the may wish to make one change rather than create new and remove old.

I don't mind if it is a command to update, a command to list networks including path to config or something else.

Pull request description:
Support locating the network config files fro CLI
Close #430 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d5de7d..a9ba21b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
 ### 🛠  Improvements
 - [427](https://github.com/vegaprotocol/vegawallet/pull/427) - Sign MacOS binaries
 - [444](https://github.com/vegaprotocol/vegawallet/pull/444) - Fail gracefully when trying to get password input on msys
+- [448](https://github.com/vegaprotocol/vegawallet/pull/448) - Add command `network locate` to locate the folder in which network configuration files are stored.
 
 ### 🔥 Removal
 - [435](https://github.com/vegaprotocol/vegawallet/pull/435) - Support signing commands offline with `command sign`
diff --git a/cmd/network.go b/cmd/network.go
index d6e91fa..afc7795 100644
--- a/cmd/network.go
+++ b/cmd/network.go
@@ -16,6 +16,7 @@ func NewCmdNetwork(w io.Writer, rf *RootFlags) *cobra.Command {
 	cmd.AddCommand(NewCmdListNetworks(w, rf))
 	cmd.AddCommand(NewCmdImportNetwork(w, rf))
 	cmd.AddCommand(NewCmdDescribeNetwork(w, rf))
+	cmd.AddCommand(NewCmdLocateNetworks(w, rf))
 	cmd.AddCommand(NewCmdDeleteNetwork(w, rf))
 	return cmd
 }
diff --git a/cmd/network_locate.go b/cmd/network_locate.go
new file mode 100644
index 0000000..b49d244
--- /dev/null
+++ b/cmd/network_locate.go
@@ -0,0 +1,79 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+
+	"code.vegaprotocol.io/shared/paths"
+	"code.vegaprotocol.io/vegawallet/cmd/cli"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/cmd/printer"
+	netstore "code.vegaprotocol.io/vegawallet/network/store/v1"
+	"github.com/spf13/cobra"
+)
+
+var (
+	locateNetworkLong = cli.LongDesc(`
+		Locate the folder in which all the network configuration files are stored.
+	`)
+
+	locateNetworkExample = cli.Examples(`
+		# Locate network configuration files
+		vegawallet network locate
+	`)
+)
+
+type LocateNetworksResponse struct {
+	Path string `json:"path"`
+}
+
+type LocateNetworksHandler func() (*LocateNetworksResponse, error)
+
+func NewCmdLocateNetworks(w io.Writer, rf *RootFlags) *cobra.Command {
+	h := func() (*LocateNetworksResponse, error) {
+		vegaPaths := paths.New(rf.Home)
+
+		netStore, err := netstore.InitialiseStore(vegaPaths)
+		if err != nil {
+			return nil, fmt.Errorf("couldn't initialise networks store: %w", err)
+		}
+
+		return &LocateNetworksResponse{
+			Path: netStore.GetNetworksPath(),
+		}, nil
+	}
+
+	return BuildCmdLocateNetworks(w, h, rf)
+}
+
+func BuildCmdLocateNetworks(w io.Writer, handler LocateNetworksHandler, rf *RootFlags) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "locate",
+		Short:   "Locate the folder of network configuration files",
+		Long:    locateNetworkLong,
+		Example: locateNetworkExample,
+		RunE: func(_ *cobra.Command, _ []string) error {
+			resp, err := handler()
+			if err != nil {
+				return err
+			}
+
+			switch rf.Output {
+			case flags.InteractiveOutput:
+				PrintLocateNetworksResponse(w, resp)
+			case flags.JSONOutput:
+				return printer.FprintJSON(w, resp)
+			}
+
+			return nil
+		},
+	}
+
+	return cmd
+}
+
+func PrintLocateNetworksResponse(w io.Writer, resp *LocateNetworksResponse) {
+	p := printer.NewInteractivePrinter(w)
+
+	p.Text("Network configuration files are located at: ").SuccessText(resp.Path).NextLine()
+}


Test patch:
diff --git a/tests/commands_for_test.go b/tests/commands_for_test.go
index bfdd821..fcebfaf 100644
--- a/tests/commands_for_test.go
+++ b/tests/commands_for_test.go
@@ -384,6 +384,46 @@ func (a *ImportNetworkAssertion) LocatedUnder(home string) *ImportNetworkAsserti
 	return a
 }
 
+type LocateNetworksResponse struct {
+	Path string `json:"path"`
+}
+
+func NetworkLocate(t *testing.T, args []string) (*LocateNetworksResponse, error) {
+	t.Helper()
+	argsWithCmd := []string{"network", "locate"}
+	argsWithCmd = append(argsWithCmd, args...)
+	output, err := ExecuteCmd(t, argsWithCmd)
+	if err != nil {
+		return nil, err
+	}
+	resp := &LocateNetworksResponse{}
+	if err := json.Unmarshal(output, resp); err != nil {
+		t.Fatalf("couldn't unmarshal command output: %v", err)
+	}
+	return resp, nil
+}
+
+type LocateNetworkAssertion struct {
+	t    *testing.T
+	resp *LocateNetworksResponse
+}
+
+func AssertLocateNetwork(t *testing.T, resp *LocateNetworksResponse) *LocateNetworkAssertion {
+	t.Helper()
+
+	assert.NotNil(t, resp)
+
+	return &LocateNetworkAssertion{
+		t:    t,
+		resp: resp,
+	}
+}
+
+func (a *LocateNetworkAssertion) LocatedUnder(p string) *LocateNetworkAssertion {
+	assert.True(a.t, strings.HasPrefix(a.resp.Path, p), "path returned doesn't start with home path")
+	return a
+}
+
 type ListNetworksResponse struct {
 	Networks []string `json:"networks"`
 }
diff --git a/tests/helpers_for_test.go b/tests/helpers_for_test.go
index 5cd85f3..373123d 100644
--- a/tests/helpers_for_test.go
+++ b/tests/helpers_for_test.go
@@ -61,3 +61,7 @@ URL = "console.example.com"
 LocalPort = 1847
 `, name)
 }
+
+func RandomPath() string {
+	return filepath.Join(vgrand.RandomStr(5), vgrand.RandomStr(5))
+}
diff --git a/tests/sc_locate_networks_test.go b/tests/sc_locate_networks_test.go
new file mode 100644
index 0000000..561552b
--- /dev/null
+++ b/tests/sc_locate_networks_test.go
@@ -0,0 +1,23 @@
+package tests_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestLocateNetworks(t *testing.T) {
+	// given
+	home := RandomPath()
+
+	// when
+	locateNetworkResp, err := NetworkLocate(t, []string{
+		"--home", home,
+		"--output", "json",
+	})
+
+	// then
+	require.NoError(t, err)
+	AssertLocateNetwork(t, locateNetworkResp).
+		LocatedUnder(home)
+}