Repository: onflow/flow-cli
Pull request #1491 (2024-04-02 18:09:31)

Merge `master` into `feature/stable-cadence`


Patch:
diff --git a/cmd/flow/main.go b/cmd/flow/main.go
index fc381590..b33b32fb 100644
--- a/cmd/flow/main.go
+++ b/cmd/flow/main.go
@@ -160,6 +160,11 @@ func main() {
 
 	cmd.SetUsageTemplate(command.UsageTemplate)
 
+	// Don't print usage on error
+	cmd.SilenceUsage = true
+	// Don't print errors on error (we handle them)
+	cmd.SilenceErrors = true
+
 	if err := cmd.Execute(); err != nil {
 		util.Exit(1, err.Error())
 	}
diff --git a/internal/dependencymanager/add.go b/internal/dependencymanager/add.go
index a7498332..e2589527 100644
--- a/internal/dependencymanager/add.go
+++ b/internal/dependencymanager/add.go
@@ -30,8 +30,8 @@ import (
 )
 
 type addFlagsCollection struct {
-	name            string `default:"" flag:"name" info:"Name of the dependency"`
-	skipDeployments bool   `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
+	dependencyManagerFlagsCollection
+	name string `default:"" flag:"name" info:"Name of the dependency"`
 }
 
 var addFlags = addFlagsCollection{}
@@ -58,7 +58,7 @@ func add(
 
 	dep := args[0]
 
-	installer, err := NewDependencyInstaller(logger, state, addFlags.skipDeployments)
+	installer, err := NewDependencyInstaller(logger, state, addFlags.dependencyManagerFlagsCollection)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
@@ -69,9 +69,5 @@ func add(
 		return nil, err
 	}
 
-	logger.Info("✅  Dependency installation complete. Check your flow.json")
-	logger.Info("Ensure you add any required dependencies to your 'deployments' section. This can be done using the 'flow config add deployment' command.")
-	logger.Info("Note: Core contracts do not need to be added to deployments. For reference, see this URL: https://github.com/onflow/flow-core-contracts")
-
 	return nil, nil
 }
diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go
index b6430c98..00a866b2 100644
--- a/internal/dependencymanager/dependencyinstaller.go
+++ b/internal/dependencymanager/dependencyinstaller.go
@@ -43,15 +43,48 @@ import (
 	"github.com/onflow/flowkit/v2/output"
 )
 
+type categorizedLogs struct {
+	fileSystemActions []string
+	stateUpdates      []string
+}
+
+func (cl *categorizedLogs) LogAll(logger output.Logger) {
+	logger.Info("📝 Dependency Manager Actions Summary")
+	logger.Info("") // Add a line break after the section
+
+	if len(cl.fileSystemActions) > 0 {
+		logger.Info("🗃️  File System Actions:")
+		for _, msg := range cl.fileSystemActions {
+			logger.Info(fmt.Sprintf("✅ %s", msg))
+		}
+		logger.Info("") // Add a line break after the section
+	}
+
+	if len(cl.stateUpdates) > 0 {
+		logger.Info("💾 State Updates:")
+		for _, msg := range cl.stateUpdates {
+			logger.Info(fmt.Sprintf("✅ %s", msg))
+		}
+		logger.Info("") // Add a line break after the section
+	}
+}
+
+type dependencyManagerFlagsCollection struct {
+	skipDeployments bool `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
+	skipAlias       bool `default:"false" flag:"skip-alias" info:"Skip prompting for an alias"`
+}
+
 type DependencyInstaller struct {
 	Gateways        map[string]gateway.Gateway
 	Logger          output.Logger
 	State           *flowkit.State
 	SkipDeployments bool
+	SkipAlias       bool
+	logs            categorizedLogs
 }
 
 // NewDependencyInstaller creates a new instance of DependencyInstaller
-func NewDependencyInstaller(logger output.Logger, state *flowkit.State, skipDeployments bool) (*DependencyInstaller, error) {
+func NewDependencyInstaller(logger output.Logger, state *flowkit.State, flags dependencyManagerFlagsCollection) (*DependencyInstaller, error) {
 	emulatorGateway, err := gateway.NewGrpcGateway(config.EmulatorNetwork)
 	if err != nil {
 		return nil, fmt.Errorf("error creating emulator gateway: %v", err)
@@ -83,7 +116,8 @@ func NewDependencyInstaller(logger output.Logger, state *flowkit.State, skipDepl
 		Gateways:        gateways,
 		Logger:          logger,
 		State:           state,
-		SkipDeployments: skipDeployments,
+		SkipDeployments: flags.skipDeployments,
+		SkipAlias:       flags.skipAlias,
 	}, nil
 }
 
@@ -95,6 +129,14 @@ func (di *DependencyInstaller) Install() error {
 			return err
 		}
 	}
+
+	err := di.State.SaveDefault()
+	if err != nil {
+		return fmt.Errorf("error saving state: %w", err)
+	}
+
+	di.logs.LogAll(di.Logger)
+
 	return nil
 }
 
@@ -124,6 +166,13 @@ func (di *DependencyInstaller) Add(depSource, customName string) error {
 		return fmt.Errorf("error processing dependency: %w", err)
 	}
 
+	err = di.State.SaveDefault()
+	if err != nil {
+		return fmt.Errorf("error saving state: %w", err)
+	}
+
+	di.logs.LogAll(di.Logger)
+
 	return nil
 }
 
@@ -218,7 +267,7 @@ func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, cont
 			return fmt.Errorf("failed to create contract file: %w", err)
 		}
 
-		di.Logger.Info(fmt.Sprintf("Dependency Manager: %s from %s on %s installed", contractName, contractAddr, networkName))
+		di.logs.fileSystemActions = append(di.logs.fileSystemActions, fmt.Sprintf("%s from %s on %s installed", contractName, contractAddr, networkName))
 	}
 
 	return nil
@@ -273,12 +322,26 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 		return err
 	}
 
+	// If the contract is not a core contract and the user does not want to skip deployments, then prompt for a deployment
 	if !di.SkipDeployments && !isCoreContract(contractName) {
 		err = di.updateDependencyDeployment(contractName)
 		if err != nil {
 			di.Logger.Error(fmt.Sprintf("Error updating deployment: %v", err))
 			return err
 		}
+
+		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("%s added to emulator deployments", contractName))
+	}
+
+	// If the contract is not a core contract and the user does not want to skip aliasing, then prompt for an alias
+	if !di.SkipAlias && !isCoreContract(contractName) {
+		err = di.updateDependencyAlias(contractName, networkName)
+		if err != nil {
+			di.Logger.Error(fmt.Sprintf("Error updating alias: %v", err))
+			return err
+		}
+
+		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("Alias added for %s on %s", contractName, networkName))
 	}
 
 	return nil
@@ -302,13 +365,30 @@ func (di *DependencyInstaller) updateDependencyDeployment(contractName string) e
 		for _, c := range raw.Contracts {
 			deployment.AddContract(config.ContractDeployment{Name: c})
 		}
+	}
+
+	return nil
+}
+
+func (di *DependencyInstaller) updateDependencyAlias(contractName, aliasNetwork string) error {
+	var missingNetwork string
+
+	if aliasNetwork == config.TestnetNetwork.Name {
+		missingNetwork = config.MainnetNetwork.Name
+	} else {
+		missingNetwork = config.TestnetNetwork.Name
+	}
 
-		err := di.State.SaveDefault()
+	label := fmt.Sprintf("Enter an alias address for %s on %s if you have one, otherwise leave blank", contractName, missingNetwork)
+	raw := util.AddressPromptOrEmpty(label, "Invalid alias address")
+
+	if raw != "" {
+		contract, err := di.State.Contracts().ByName(contractName)
 		if err != nil {
 			return err
 		}
 
-		di.Logger.Info(fmt.Sprintf("Dependency Manager: %s added to emulator deployments in flow.json", contractName))
+		contract.Aliases.Add(missingNetwork, flowsdk.HexToAddress(raw))
 	}
 
 	return nil
@@ -329,13 +409,9 @@ func (di *DependencyInstaller) updateDependencyState(networkName, contractAddres
 
 	di.State.Dependencies().AddOrUpdate(dep)
 	di.State.Contracts().AddDependencyAsContract(dep, networkName)
-	err := di.State.SaveDefault()
-	if err != nil {
-		return err
-	}
 
 	if isNewDep {
-		di.Logger.Info(fmt.Sprintf("Dependency Manager: %s added to flow.json", dep.Name))
+		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("%s added to flow.json", dep.Name))
 	}
 
 	return nil
diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go
index e13e2c2f..4edc9762 100644
--- a/internal/dependencymanager/install.go
+++ b/internal/dependencymanager/install.go
@@ -29,11 +29,7 @@ import (
 	"github.com/onflow/flow-cli/internal/command"
 )
 
-type installFlagsCollection struct {
-	skipDeployments bool `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
-}
-
-var installFlags = installFlagsCollection{}
+var installFlags = dependencyManagerFlagsCollection{}
 
 var installCommand = &command.Command{
 	Cmd: &cobra.Command{
@@ -54,7 +50,7 @@ func install(
 ) (result command.Result, err error) {
 	logger.Info("🔄 Installing dependencies from flow.json...")
 
-	installer, err := NewDependencyInstaller(logger, state, installFlags.skipDeployments)
+	installer, err := NewDependencyInstaller(logger, state, installFlags)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
@@ -65,7 +61,5 @@ func install(
 		return nil, err
 	}
 
-	logger.Info("✅  Dependency installation complete. Check your flow.json")
-
 	return nil, nil
 }
diff --git a/internal/util/prompt.go b/internal/util/prompt.go
index 1b2d79ac..f17663f0 100644
--- a/internal/util/prompt.go
+++ b/internal/util/prompt.go
@@ -204,12 +204,34 @@ func secureNetworkKeyPrompt() string {
 	return networkKey
 }
 
-func addressPrompt() string {
+func addressPrompt(label, errorMessage string) string {
 	addressPrompt := promptui.Prompt{
-		Label: "Enter address",
+		Label: label,
 		Validate: func(s string) error {
 			if flow.HexToAddress(s) == flow.EmptyAddress {
-				return fmt.Errorf("invalid address")
+				return fmt.Errorf(errorMessage)
+			}
+			return nil
+		},
+	}
+
+	address, err := addressPrompt.Run()
+	if err == promptui.ErrInterrupt {
+		os.Exit(-1)
+	}
+
+	return address
+}
+
+func AddressPromptOrEmpty(label, errorMessage string) string {
+	addressPrompt := promptui.Prompt{
+		Label: label,
+		Validate: func(s string) error {
+			if s == "" {
+				return nil
+			}
+			if flow.HexToAddress(s) == flow.EmptyAddress {
+				return fmt.Errorf(errorMessage)
 			}
 			return nil
 		},
@@ -286,7 +308,7 @@ func NewAccountPrompt() *AccountData {
 	var err error
 	account := &AccountData{
 		Name:    NamePrompt(),
-		Address: addressPrompt(),
+		Address: addressPrompt("Enter address", "invalid address"),
 	}
 
 	sigAlgoPrompt := promptui.Select{
@@ -372,41 +394,9 @@ func NewContractPrompt() *ContractData {
 		os.Exit(-1)
 	}
 
-	emulatorAliasPrompt := promptui.Prompt{
-		Label: "Enter emulator alias, if exists",
-		Validate: func(s string) error {
-			if s != "" && flow.HexToAddress(s) == flow.EmptyAddress {
-				return fmt.Errorf("invalid alias address")
-			}
-
-			return nil
-		},
-	}
-	contract.Emulator, _ = emulatorAliasPrompt.Run()
-
-	testnetAliasPrompt := promptui.Prompt{
-		Label: "Enter testnet alias, if exists",
-		Validate: func(s string) error {
-			if s != "" && flow.HexToAddress(s) == flow.EmptyAddress {
-				return fmt.Errorf("invalid testnet address")
-			}
-
-			return nil
-		},
-	}
-	contract.Testnet, _ = testnetAliasPrompt.Run()
-
-	mainnetAliasPrompt := promptui.Prompt{
-		Label: "Enter mainnet alias, if exists",
-		Validate: func(s string) error {
-			if s != "" && flow.HexToAddress(s) == flow.EmptyAddress {
-				return fmt.Errorf("invalid mainnet address")
-			}
-
-			return nil
-		},
-	}
-	contract.Mainnet, _ = mainnetAliasPrompt.Run()
+	contract.Emulator = addressPrompt("Enter emulator alias, if exists", "invalid alias address")
+	contract.Testnet = addressPrompt("Enter testnet alias, if exists", "invalid testnet address")
+	contract.Mainnet = addressPrompt("Enter mainnet alias, if exists", "invalid mainnet address")
 
 	return contract
 }
diff --git a/internal/version/version.go b/internal/version/version.go
index a60fec24..c7b09fe2 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -21,7 +21,6 @@ package version
 import (
 	"encoding/json"
 	"fmt"
-	"log"
 	"runtime/debug"
 	"strings"
 
@@ -45,12 +44,12 @@ func (c versionCmd) Print(format string) error {
 		txtBuilder.WriteString(fmt.Sprintf("Version: %s\n", c.Version))
 		txtBuilder.WriteString(fmt.Sprintf("Commit: %s\n", c.Commit))
 
-		txtBuilder.WriteString(fmt.Sprintf("\nFlow Package Dependencies \n"))
+		txtBuilder.WriteString("\nFlow Package Dependencies \n")
 		for _, dep := range c.Dependencies {
 			txtBuilder.WriteString(fmt.Sprintf("%s %s\n", dep.Path, dep.Version))
 		}
 
-		log.Println(txtBuilder.String())
+		fmt.Println(txtBuilder.String())
 
 		return nil
 
@@ -60,7 +59,7 @@ func (c versionCmd) Print(format string) error {
 			return err
 		}
 
-		log.Println(string(jsonRes))
+		fmt.Println(string(jsonRes))
 
 		return nil
 
@@ -99,7 +98,7 @@ func (c *versionCmd) MarshalJSON() ([]byte, error) {
 var Cmd = &cobra.Command{
 	Use:   "version",
 	Short: "View version and commit information",
-	Run: func(cmd *cobra.Command, args []string) {
+	RunE: func(cmd *cobra.Command, args []string) error {
 		semver := build.Semver()
 		commit := build.Commit()
 
@@ -110,8 +109,7 @@ var Cmd = &cobra.Command{
 
 		bi, ok := debug.ReadBuildInfo()
 		if !ok {
-			log.Printf("Failed to read build info")
-			return
+			return fmt.Errorf("failed to read build info")
 		}
 
 		// only add dependencies from github.com/onflow
@@ -122,8 +120,9 @@ var Cmd = &cobra.Command{
 		}
 
 		if err := v.Print(command.Flags.Format); err != nil {
-			log.Printf("Failed to print version information: %s", err)
-			return
+			return err
 		}
+
+		return nil
 	},
 }
diff --git a/version.txt b/version.txt
index 440ddd8f..a406138e 100644
--- a/version.txt
+++ b/version.txt
@@ -1,1 +1,1 @@
-v1.15.0
+v1.16.0


Test patch:
diff --git a/internal/dependencymanager/dependencyinstaller_test.go b/internal/dependencymanager/dependencyinstaller_test.go
index 0a1111b0..1654e75c 100644
--- a/internal/dependencymanager/dependencyinstaller_test.go
+++ b/internal/dependencymanager/dependencyinstaller_test.go
@@ -78,6 +78,7 @@ func TestDependencyInstallerInstall(t *testing.T) {
 			Logger:          logger,
 			State:           state,
 			SkipDeployments: true,
+			SkipAlias:       true,
 		}
 
 		err := di.Install()
@@ -122,6 +123,7 @@ func TestDependencyInstallerAdd(t *testing.T) {
 			Logger:          logger,
 			State:           state,
 			SkipDeployments: true,
+			SkipAlias:       true,
 		}
 
 		sourceStr := fmt.Sprintf("emulator://%s.%s", serviceAddress.String(), tests.ContractHelloString.Name)