Repository: onflow/flow-cli
Pull request #1486 (2024-03-29 22:07:58)

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


Patch:
diff --git a/internal/dependencymanager/add.go b/internal/dependencymanager/add.go
index e67ebc35..a7498332 100644
--- a/internal/dependencymanager/add.go
+++ b/internal/dependencymanager/add.go
@@ -30,7 +30,8 @@ import (
 )
 
 type addFlagsCollection struct {
-	name string `default:"" flag:"name" info:"Name of the dependency"`
+	name            string `default:"" flag:"name" info:"Name of the dependency"`
+	skipDeployments bool   `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
 }
 
 var addFlags = addFlagsCollection{}
@@ -57,7 +58,7 @@ func add(
 
 	dep := args[0]
 
-	installer, err := NewDependencyInstaller(logger, state)
+	installer, err := NewDependencyInstaller(logger, state, addFlags.skipDeployments)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go
index 9a42f4ff..b6430c98 100644
--- a/internal/dependencymanager/dependencyinstaller.go
+++ b/internal/dependencymanager/dependencyinstaller.go
@@ -25,7 +25,9 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"sync"
+
+	"github.com/onflow/flow-go/fvm/systemcontracts"
+	flowGo "github.com/onflow/flow-go/model/flow"
 
 	"github.com/onflow/flow-cli/internal/util"
 
@@ -42,14 +44,14 @@ import (
 )
 
 type DependencyInstaller struct {
-	Gateways map[string]gateway.Gateway
-	Logger   output.Logger
-	State    *flowkit.State
-	Mutex    sync.Mutex
+	Gateways        map[string]gateway.Gateway
+	Logger          output.Logger
+	State           *flowkit.State
+	SkipDeployments bool
 }
 
 // NewDependencyInstaller creates a new instance of DependencyInstaller
-func NewDependencyInstaller(logger output.Logger, state *flowkit.State) (*DependencyInstaller, error) {
+func NewDependencyInstaller(logger output.Logger, state *flowkit.State, skipDeployments bool) (*DependencyInstaller, error) {
 	emulatorGateway, err := gateway.NewGrpcGateway(config.EmulatorNetwork)
 	if err != nil {
 		return nil, fmt.Errorf("error creating emulator gateway: %v", err)
@@ -78,9 +80,10 @@ func NewDependencyInstaller(logger output.Logger, state *flowkit.State) (*Depend
 	}
 
 	return &DependencyInstaller{
-		Gateways: gateways,
-		Logger:   logger,
-		State:    state,
+		Gateways:        gateways,
+		Logger:          logger,
+		State:           state,
+		SkipDeployments: skipDeployments,
 	}, nil
 }
 
@@ -143,13 +146,6 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo
 		return fmt.Errorf("contracts are nil for account: %s", address)
 	}
 
-	var wg sync.WaitGroup
-	errCh := make(chan error, len(account.Contracts))
-
-	// Create a max number of goroutines so that we don't rate limit the access node
-	maxGoroutines := 5
-	semaphore := make(chan struct{}, maxGoroutines)
-
 	found := false
 
 	for _, contract := range account.Contracts {
@@ -173,18 +169,11 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo
 			if program.HasAddressImports() {
 				imports := program.AddressImportDeclarations()
 				for _, imp := range imports {
-					wg.Add(1)
-					go func(importAddress flowsdk.Address, contractName string) {
-						semaphore <- struct{}{}
-						defer func() {
-							<-semaphore
-							wg.Done()
-						}()
-						err := di.fetchDependencies(networkName, importAddress, contractName, contractName)
-						if err != nil {
-							errCh <- err
-						}
-					}(flowsdk.HexToAddress(imp.Location.String()), imp.Identifiers[0].String())
+					contractName := imp.Identifiers[0].String()
+					err := di.fetchDependencies(networkName, flowsdk.HexToAddress(imp.Location.String()), contractName, contractName)
+					if err != nil {
+						return err
+					}
 				}
 			}
 		}
@@ -195,16 +184,6 @@ func (di *DependencyInstaller) fetchDependencies(networkName string, address flo
 		di.Logger.Error(errMsg)
 	}
 
-	wg.Wait()
-	close(errCh)
-	close(semaphore)
-
-	for err := range errCh {
-		if err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 
@@ -234,9 +213,6 @@ func (di *DependencyInstaller) createContractFile(address, contractName, data st
 }
 
 func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, contractData, networkName string) error {
-	di.Mutex.Lock()
-	defer di.Mutex.Unlock()
-
 	if !di.contractFileExists(contractAddr, contractName) {
 		if err := di.createContractFile(contractAddr, contractName, contractData); err != nil {
 			return fmt.Errorf("failed to create contract file: %w", err)
@@ -248,6 +224,17 @@ func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, cont
 	return nil
 }
 
+func isCoreContract(contractName string) bool {
+	sc := systemcontracts.SystemContractsForChain(flowGo.Emulator)
+
+	for _, coreContract := range sc.All() {
+		if coreContract.Name == contractName {
+			return true
+		}
+	}
+	return false
+}
+
 func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, assignedName, contractName string, program *project.Program) error {
 	hash := sha256.New()
 	hash.Write(program.CodeWithUnprocessedImports())
@@ -280,16 +267,54 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 		return fmt.Errorf("error handling file system: %w", err)
 	}
 
-	err = di.updateState(networkName, contractAddr, assignedName, contractName, originalContractDataHash)
+	err = di.updateDependencyState(networkName, contractAddr, assignedName, contractName, originalContractDataHash)
 	if err != nil {
 		di.Logger.Error(fmt.Sprintf("Error updating state: %v", err))
 		return err
 	}
 
+	if !di.SkipDeployments && !isCoreContract(contractName) {
+		err = di.updateDependencyDeployment(contractName)
+		if err != nil {
+			di.Logger.Error(fmt.Sprintf("Error updating deployment: %v", err))
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (di *DependencyInstaller) updateDependencyDeployment(contractName string) error {
+	// Add to deployments
+	// If a deployment already exists for that account, contract, and network, then ignore
+	raw := util.AddContractToDeploymentPrompt("emulator", *di.State.Accounts(), contractName)
+
+	if raw != nil {
+		deployment := di.State.Deployments().ByAccountAndNetwork(raw.Account, raw.Network)
+		if deployment == nil {
+			di.State.Deployments().AddOrUpdate(config.Deployment{
+				Network: raw.Network,
+				Account: raw.Account,
+			})
+			deployment = di.State.Deployments().ByAccountAndNetwork(raw.Account, raw.Network)
+		}
+
+		for _, c := range raw.Contracts {
+			deployment.AddContract(config.ContractDeployment{Name: c})
+		}
+
+		err := di.State.SaveDefault()
+		if err != nil {
+			return err
+		}
+
+		di.Logger.Info(fmt.Sprintf("Dependency Manager: %s added to emulator deployments in flow.json", contractName))
+	}
+
 	return nil
 }
 
-func (di *DependencyInstaller) updateState(networkName, contractAddress, assignedName, contractName, contractHash string) error {
+func (di *DependencyInstaller) updateDependencyState(networkName, contractAddress, assignedName, contractName, contractHash string) error {
 	dep := config.Dependency{
 		Name: assignedName,
 		Source: config.Source{
diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go
index 4014b4c6..e13e2c2f 100644
--- a/internal/dependencymanager/install.go
+++ b/internal/dependencymanager/install.go
@@ -29,7 +29,9 @@ import (
 	"github.com/onflow/flow-cli/internal/command"
 )
 
-type installFlagsCollection struct{}
+type installFlagsCollection struct {
+	skipDeployments bool `default:"false" flag:"skip-deployments" info:"Skip adding the dependency to deployments"`
+}
 
 var installFlags = installFlagsCollection{}
 
@@ -52,7 +54,7 @@ func install(
 ) (result command.Result, err error) {
 	logger.Info("🔄 Installing dependencies from flow.json...")
 
-	installer, err := NewDependencyInstaller(logger, state)
+	installer, err := NewDependencyInstaller(logger, state, installFlags.skipDeployments)
 	if err != nil {
 		logger.Error(fmt.Sprintf("Error: %v", err))
 		return nil, err
diff --git a/internal/util/prompt.go b/internal/util/prompt.go
index 66eefbe0..1b2d79ac 100644
--- a/internal/util/prompt.go
+++ b/internal/util/prompt.go
@@ -25,6 +25,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/onflow/flowkit/v2/accounts"
+
 	"github.com/gosuri/uilive"
 	"github.com/manifoldco/promptui"
 	"github.com/onflow/flow-go-sdk"
@@ -497,6 +499,41 @@ func NewDeploymentPrompt(
 	return deploymentData
 }
 
+// AddContractToDeploymentPrompt prompts a user to select an account to deploy a given contract on a given network
+func AddContractToDeploymentPrompt(networkName string, accounts accounts.Accounts, contractName string) *DeploymentData {
+	deploymentData := &DeploymentData{
+		Network:   networkName,
+		Contracts: []string{contractName},
+	}
+	var err error
+
+	accountNames := make([]string, 0)
+	for _, account := range accounts {
+		accountNames = append(accountNames, account.Name)
+	}
+
+	// Add a "none" option to the list of accounts
+	accountNames = append(accountNames, "none")
+
+	accountPrompt := promptui.Select{
+		Label: fmt.Sprintf("Choose an account to deploy %s to on %s (or 'none' to skip)", contractName, networkName),
+		Items: accountNames,
+	}
+	selectedIndex, _, err := accountPrompt.Run()
+	if err == promptui.ErrInterrupt {
+		os.Exit(-1)
+	}
+
+	// Handle the "none" selection based on its last position
+	if selectedIndex == len(accountNames)-1 {
+		return nil
+	}
+
+	deploymentData.Account = accounts[selectedIndex].Name
+
+	return deploymentData
+}
+
 func RemoveAccountPrompt(accounts config.Accounts) string {
 	accountNames := make([]string, 0)
 


Test patch:
diff --git a/internal/dependencymanager/dependencyinstaller_test.go b/internal/dependencymanager/dependencyinstaller_test.go
index 9fd03cde..0a1111b0 100644
--- a/internal/dependencymanager/dependencyinstaller_test.go
+++ b/internal/dependencymanager/dependencyinstaller_test.go
@@ -75,8 +75,9 @@ func TestDependencyInstallerInstall(t *testing.T) {
 				config.MainnetNetwork.Name:    gw.Mock,
 				config.PreviewnetNetwork.Name: gw.Mock,
 			},
-			Logger: logger,
-			State:  state,
+			Logger:          logger,
+			State:           state,
+			SkipDeployments: true,
 		}
 
 		err := di.Install()
@@ -118,8 +119,9 @@ func TestDependencyInstallerAdd(t *testing.T) {
 				config.MainnetNetwork.Name:    gw.Mock,
 				config.PreviewnetNetwork.Name: gw.Mock,
 			},
-			Logger: logger,
-			State:  state,
+			Logger:          logger,
+			State:           state,
+			SkipDeployments: true,
 		}
 
 		sourceStr := fmt.Sprintf("emulator://%s.%s", serviceAddress.String(), tests.ContractHelloString.Name)