Skip to content

Commit

Permalink
Merge pull request #1488 from onflow/chasefleming/1363
Browse files Browse the repository at this point in the history
Dependency Manager: Prompt to add an alias for other networks on install/add
  • Loading branch information
chasefleming authored Apr 2, 2024
2 parents 783914c + 39e029e commit 8c8f7a4
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 64 deletions.
10 changes: 3 additions & 7 deletions internal/dependencymanager/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand All @@ -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
Expand All @@ -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
}
96 changes: 86 additions & 10 deletions internal/dependencymanager/dependencyinstaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,48 @@ import (
"github.com/onflow/flowkit/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)
Expand All @@ -77,7 +110,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
}

Expand All @@ -89,6 +123,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
}

Expand Down Expand Up @@ -118,6 +160,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
}

Expand Down Expand Up @@ -212,7 +261,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
Expand Down Expand Up @@ -267,12 +316,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
Expand All @@ -296,13 +359,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
Expand All @@ -323,13 +403,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
Expand Down
2 changes: 2 additions & 0 deletions internal/dependencymanager/dependencyinstaller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func TestDependencyInstallerInstall(t *testing.T) {
Logger: logger,
State: state,
SkipDeployments: true,
SkipAlias: true,
}

err := di.Install()
Expand Down Expand Up @@ -120,6 +121,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)
Expand Down
10 changes: 2 additions & 8 deletions internal/dependencymanager/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
Expand All @@ -65,7 +61,5 @@ func install(
return nil, err
}

logger.Info("✅ Dependency installation complete. Check your flow.json")

return nil, nil
}
68 changes: 29 additions & 39 deletions internal/util/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -371,41 +393,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
}
Expand Down

0 comments on commit 8c8f7a4

Please sign in to comment.