diff --git a/go.mod b/go.mod index bfa84e964..f3e98da33 100644 --- a/go.mod +++ b/go.mod @@ -299,3 +299,6 @@ require ( nhooyr.io/websocket v1.8.7 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) + +//replace github.com/onflow/flowkit/v2 => github.com/onflowser/flowkit/v2 v2.0.0-20240623105755-c0604188301b +replace github.com/onflow/flowkit/v2 => ../flowkit diff --git a/go.sum b/go.sum index df038be48..451a970e7 100644 --- a/go.sum +++ b/go.sum @@ -2208,6 +2208,7 @@ github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba h1:rIehuhO6bj4FkwE4V github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba/go.mod h1:F0dj0EyHC55kknLkeD10js4mo14yTdMotnWMslPirrU= github.com/onflow/wal v1.0.2 h1:5bgsJVf2O3cfMNK12fiiTyYZ8cOrUiELt3heBJfHOhc= github.com/onflow/wal v1.0.2/go.mod h1:iMC8gkLqu4nkbkAla5HkSBb+FGyQOZiWz3DYm2wSXCk= +github.com/onflowser/flowkit/v2 v2.0.0-20240623105755-c0604188301b/go.mod h1:89ipBRMJE9nWRimoqaa02+l6kCQuBiVyzszI6N3a6Sk= github.com/onflowser/flowser/v3 v3.2.1-0.20240131200229-7d4d22715f48 h1:eBS8Rm1V9TTGpj9lcpoCi6MIwWgYQZ8crJ7cSrS39Y4= github.com/onflowser/flowser/v3 v3.2.1-0.20240131200229-7d4d22715f48/go.mod h1:Pvw+OkV3eqLGMpHWu38rJYE30N9G3VgQ2MRQQnRr4to= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/internal/command/template.go b/internal/command/template.go index 87cfa62dd..9643731fa 100644 --- a/internal/command/template.go +++ b/internal/command/template.go @@ -39,10 +39,10 @@ Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help") Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} -Flags: +flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} -Global Flags: +Global flags: {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} diff --git a/internal/dependencymanager/add.go b/internal/dependencymanager/add.go index 0bae6cf03..83dfc624c 100644 --- a/internal/dependencymanager/add.go +++ b/internal/dependencymanager/add.go @@ -20,12 +20,12 @@ package dependencymanager import ( "fmt" - "github.com/onflow/flow-cli/internal/util" "github.com/spf13/cobra" "github.com/onflow/flowkit/v2" + "github.com/onflow/flowkit/v2/deps" "github.com/onflow/flowkit/v2/output" "github.com/onflow/flow-cli/internal/command" @@ -52,9 +52,9 @@ var addCommand = &command.Command{ } func init() { - // Add common flags. + // Add common Flags. addFlags.Flags.AddToCommand(addCommand.Cmd) - // Add command-specific flags. + // Add command-specific Flags. addCommand.Cmd.Flags().StringVar(&addFlags.name, "name", "", "Name of the dependency") } @@ -69,7 +69,21 @@ func add( dep := args[0] - installer, err := NewDependencyInstaller(logger, state, true, "", *addFlags.Flags) + options := []deps.Option{ + deps.WithSaveState(), + deps.WithLogger(logger), + } + + if addFlags.skipDeployments { + options = append(options, deps.WithSkippedDeployments()) + } + + if addFlags.skipAlias { + options = append(options, deps.WithSkippedAlias()) + } + + installer, err := NewCliDependencyInstaller(state, options...) + 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 ab54c6655..e5c7565de 100644 --- a/internal/dependencymanager/dependencyinstaller.go +++ b/internal/dependencymanager/dependencyinstaller.go @@ -19,72 +19,18 @@ package dependencymanager import ( - "context" - "crypto/sha256" - "encoding/hex" "fmt" - "os" - "path/filepath" - - "github.com/psiemens/sconfig" - "github.com/onflow/flow-cli/internal/prompt" "github.com/onflow/flow-cli/internal/util" - - "github.com/spf13/cobra" - - "github.com/onflow/flow-go/fvm/systemcontracts" - flowGo "github.com/onflow/flow-go/model/flow" - - "github.com/onflow/flowkit/v2/gateway" - - "github.com/onflow/flowkit/v2/project" - - flowsdk "github.com/onflow/flow-go-sdk" - - "github.com/onflow/flowkit/v2/config" - "github.com/onflow/flowkit/v2" - "github.com/onflow/flowkit/v2/output" + "github.com/onflow/flowkit/v2/accounts" + "github.com/onflow/flowkit/v2/deps" + "github.com/psiemens/sconfig" + "github.com/spf13/cobra" ) -type categorizedLogs struct { - fileSystemActions []string - stateUpdates []string - issues []string -} - -func (cl *categorizedLogs) LogAll(logger output.Logger) { - logger.Info(util.MessageWithEmojiPrefix("📝", "Dependency Manager Actions Summary")) - logger.Info("") // Add a line break after the section - - if len(cl.fileSystemActions) > 0 { - logger.Info(util.MessageWithEmojiPrefix("🗃️", "File System Actions:")) - for _, msg := range cl.fileSystemActions { - logger.Info(msg) - } - logger.Info("") // Add a line break after the section - } - - if len(cl.stateUpdates) > 0 { - logger.Info(util.MessageWithEmojiPrefix("💾", "State Updates:")) - for _, msg := range cl.stateUpdates { - logger.Info(msg) - } - logger.Info("") // Add a line break after the section - } - - if len(cl.issues) > 0 { - logger.Info(util.MessageWithEmojiPrefix("⚠️", "Issues:")) - for _, msg := range cl.issues { - logger.Info(msg) - } - logger.Info("") - } - - if len(cl.fileSystemActions) == 0 && len(cl.stateUpdates) == 0 { - logger.Info(util.MessageWithEmojiPrefix("👍", "Zero changes were made. Everything looks good.")) - } +func NewCliDependencyInstaller(state *flowkit.State, options ...deps.Option) (*deps.DependencyInstaller, error) { + return deps.NewDependencyInstaller(state, cliPrompter{}, options...) } type Flags struct { @@ -103,448 +49,19 @@ func (f *Flags) AddToCommand(cmd *cobra.Command) { } } -type DependencyInstaller struct { - Gateways map[string]gateway.Gateway - Logger output.Logger - State *flowkit.State - SaveState bool - TargetDir string - SkipDeployments bool - SkipAlias bool - logs categorizedLogs - dependencies map[string]config.Dependency -} - -// NewDependencyInstaller creates a new instance of DependencyInstaller -func NewDependencyInstaller(logger output.Logger, state *flowkit.State, saveState bool, targetDir string, flags Flags) (*DependencyInstaller, error) { - emulatorGateway, err := gateway.NewGrpcGateway(config.EmulatorNetwork) - if err != nil { - return nil, fmt.Errorf("error creating emulator gateway: %v", err) - } - - testnetGateway, err := gateway.NewGrpcGateway(config.TestnetNetwork) - if err != nil { - return nil, fmt.Errorf("error creating testnet gateway: %v", err) - } - - mainnetGateway, err := gateway.NewGrpcGateway(config.MainnetNetwork) - if err != nil { - return nil, fmt.Errorf("error creating mainnet gateway: %v", err) - } - - previewnetGateway, err := gateway.NewGrpcGateway(config.PreviewnetNetwork) - if err != nil { - return nil, fmt.Errorf("error creating previewnet gateway: %v", err) - } - - gateways := map[string]gateway.Gateway{ - config.EmulatorNetwork.Name: emulatorGateway, - config.TestnetNetwork.Name: testnetGateway, - config.MainnetNetwork.Name: mainnetGateway, - config.PreviewnetNetwork.Name: previewnetGateway, - } +type cliPrompter struct{} - return &DependencyInstaller{ - Gateways: gateways, - Logger: logger, - State: state, - SaveState: saveState, - TargetDir: targetDir, - SkipDeployments: flags.skipDeployments, - SkipAlias: flags.skipAlias, - dependencies: make(map[string]config.Dependency), - }, nil +func (c cliPrompter) ShouldUpdateDependency(contractName string) bool { + msg := fmt.Sprintf("The latest version of %s is different from the one you have locally. Do you want to update it?", contractName) + return prompt.GenericBoolPrompt(msg) } -// saveState checks the SaveState flag and saves the state if set to true. -func (di *DependencyInstaller) saveState() error { - if di.SaveState { - statePath := filepath.Join(di.TargetDir, "flow.json") - if err := di.State.Save(statePath); err != nil { - return fmt.Errorf("error saving state: %w", err) - } - } - return nil +func (c cliPrompter) AddContractToDeployment(networkName string, accounts accounts.Accounts, contractName string) *deps.DeploymentData { + return prompt.AddContractToDeploymentPrompt(networkName, accounts, contractName) } -// Install processes all the dependencies in the state and installs them and any dependencies they have -func (di *DependencyInstaller) Install() error { - for _, dependency := range *di.State.Dependencies() { - if err := di.processDependency(dependency); err != nil { - di.Logger.Error(fmt.Sprintf("Error processing dependency: %v", err)) - return err - } - } - - di.checkForConflictingContracts() - - if err := di.saveState(); err != nil { - return fmt.Errorf("error saving state: %w", err) - } - - di.logs.LogAll(di.Logger) - - return nil -} - -// AddBySourceString processes a single dependency and installs it and any dependencies it has, as well as adding it to the state -func (di *DependencyInstaller) AddBySourceString(depSource, customName string) error { - depNetwork, depAddress, depContractName, err := config.ParseSourceString(depSource) - if err != nil { - return fmt.Errorf("error parsing source: %w", err) - } - - name := depContractName - - if customName != "" { - name = customName - } - - dep := config.Dependency{ - Name: name, - Source: config.Source{ - NetworkName: depNetwork, - Address: flowsdk.HexToAddress(depAddress), - ContractName: depContractName, - }, - } - - if err := di.processDependency(dep); err != nil { - return fmt.Errorf("error processing dependency: %w", err) - } - - di.checkForConflictingContracts() - - if err := di.saveState(); err != nil { - return err - } - - di.logs.LogAll(di.Logger) - - return nil +func (c cliPrompter) AddressPromptOrEmpty(label, errorMessage string) string { + return prompt.AddressPromptOrEmpty(label, errorMessage) } -// Add processes a single dependency and installs it and any dependencies it has, as well as adding it to the state -func (di *DependencyInstaller) Add(dep config.Dependency) error { - if err := di.processDependency(dep); err != nil { - return fmt.Errorf("error processing dependency: %w", err) - } - - if err := di.saveState(); err != nil { - return err - } - - di.logs.LogAll(di.Logger) - - return nil -} - -// AddMany processes multiple dependencies and installs them as well as adding them to the state -func (di *DependencyInstaller) AddMany(dependencies []config.Dependency) error { - for _, dep := range dependencies { - if err := di.processDependency(dep); err != nil { - return fmt.Errorf("error processing dependency: %w", err) - } - } - - if err := di.saveState(); err != nil { - return err - } - - di.logs.LogAll(di.Logger) - - return nil -} - -func (di *DependencyInstaller) addDependency(dep config.Dependency) error { - sourceString := fmt.Sprintf("%s://%s.%s", dep.Source.NetworkName, dep.Source.Address.String(), dep.Source.ContractName) - - if _, exists := di.dependencies[sourceString]; exists { - return nil - } - - di.dependencies[sourceString] = dep - - return nil -} - -// checkForConflictingContracts checks if any of the dependencies conflict with contracts already in the state -func (di *DependencyInstaller) checkForConflictingContracts() { - for _, dependency := range di.dependencies { - foundContract, _ := di.State.Contracts().ByName(dependency.Name) - if foundContract != nil && !foundContract.IsDependency { - msg := util.MessageWithEmojiPrefix("❌", fmt.Sprintf("Contract named %s already exists in flow.json", dependency.Name)) - di.logs.issues = append(di.logs.issues, msg) - } - } -} - -func (di *DependencyInstaller) processDependency(dependency config.Dependency) error { - depAddress := flowsdk.HexToAddress(dependency.Source.Address.String()) - return di.fetchDependencies(dependency.Source.NetworkName, depAddress, dependency.Name, dependency.Source.ContractName) -} - -func (di *DependencyInstaller) fetchDependencies(networkName string, address flowsdk.Address, assignedName, contractName string) error { - err := di.addDependency(config.Dependency{ - Name: assignedName, - Source: config.Source{ - NetworkName: networkName, - Address: address, - ContractName: contractName, - }, - }) - if err != nil { - return fmt.Errorf("error adding dependency: %w", err) - } - - ctx := context.Background() - account, err := di.Gateways[networkName].GetAccount(ctx, address) - if err != nil { - return fmt.Errorf("failed to get account: %w", err) - } - if account == nil { - return fmt.Errorf("account is nil for address: %s", address) - } - - if account.Contracts == nil { - return fmt.Errorf("contracts are nil for account: %s", address) - } - - found := false - - for _, contract := range account.Contracts { - program, err := project.NewProgram(contract, nil, "") - if err != nil { - return fmt.Errorf("failed to parse program: %w", err) - } - - parsedContractName, err := program.Name() - if err != nil { - return fmt.Errorf("failed to parse contract name: %w", err) - } - - if parsedContractName == contractName { - found = true - - if err := di.handleFoundContract(networkName, address.String(), assignedName, parsedContractName, program); err != nil { - return fmt.Errorf("failed to handle found contract: %w", err) - } - - if program.HasAddressImports() { - imports := program.AddressImportDeclarations() - for _, imp := range imports { - contractName := imp.Identifiers[0].String() - err := di.fetchDependencies(networkName, flowsdk.HexToAddress(imp.Location.String()), contractName, contractName) - if err != nil { - return err - } - } - } - } - } - - if !found { - errMsg := fmt.Sprintf("contract %s not found for account %s on network %s", contractName, address, networkName) - di.Logger.Error(errMsg) - } - - return nil -} - -func (di *DependencyInstaller) contractFileExists(address, contractName string) bool { - fileName := fmt.Sprintf("%s.cdc", contractName) - path := filepath.Join("imports", address, fileName) - - _, err := di.State.ReaderWriter().Stat(path) - - return err == nil -} - -func (di *DependencyInstaller) createContractFile(address, contractName, data string) error { - fileName := fmt.Sprintf("%s.cdc", contractName) - path := filepath.Join(di.TargetDir, "imports", address, fileName) - dir := filepath.Dir(path) - - if err := di.State.ReaderWriter().MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("error creating directories: %w", err) - } - - if err := di.State.ReaderWriter().WriteFile(path, []byte(data), 0644); err != nil { - return fmt.Errorf("error writing file: %w", err) - } - - return nil -} - -func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, contractData, networkName string) error { - if !di.contractFileExists(contractAddr, contractName) { - if err := di.createContractFile(contractAddr, contractName, contractData); err != nil { - return fmt.Errorf("failed to create contract file: %w", err) - } - - msg := util.MessageWithEmojiPrefix("✅️", fmt.Sprintf("Contract %s from %s on %s installed", contractName, contractAddr, networkName)) - di.logs.fileSystemActions = append(di.logs.fileSystemActions, msg) - } - - 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()) - originalContractDataHash := hex.EncodeToString(hash.Sum(nil)) - - program.ConvertAddressImports() - contractData := string(program.CodeWithUnprocessedImports()) - - dependency := di.State.Dependencies().ByName(assignedName) - - // If a dependency by this name already exists and its remote source network or address does not match, then give option to stop or continue - if dependency != nil && (dependency.Source.NetworkName != networkName || dependency.Source.Address.String() != contractAddr) { - di.Logger.Info(fmt.Sprintf("%s A dependency named %s already exists with a different remote source. Please fix the conflict and retry.", util.PrintEmoji("🚫"), assignedName)) - os.Exit(0) - return nil - } - - // Check if remote source version is different from local version - // If it is, ask if they want to update - // If no hash, ignore - if dependency != nil && dependency.Hash != "" && dependency.Hash != originalContractDataHash { - msg := fmt.Sprintf("The latest version of %s is different from the one you have locally. Do you want to update it?", contractName) - if !prompt.GenericBoolPrompt(msg) { - return nil - } - } - - // Needs to happen before handleFileSystem - if !di.contractFileExists(contractAddr, contractName) { - err := di.handleAdditionalDependencyTasks(networkName, contractName) - if err != nil { - di.Logger.Error(fmt.Sprintf("Error handling additional dependency tasks: %v", err)) - return err - } - } - - err := di.handleFileSystem(contractAddr, contractName, contractData, networkName) - if err != nil { - return fmt.Errorf("error handling file system: %w", err) - } - - err = di.updateDependencyState(networkName, contractAddr, assignedName, contractName, originalContractDataHash) - if err != nil { - di.Logger.Error(fmt.Sprintf("Error updating state: %v", err)) - return err - } - - return nil -} - -func (di *DependencyInstaller) handleAdditionalDependencyTasks(networkName, contractName string) error { - // 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 - } - - msg := util.MessageWithEmojiPrefix("✅", fmt.Sprintf("%s added to emulator deployments", contractName)) - di.logs.stateUpdates = append(di.logs.stateUpdates, msg) - } - - // 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 - } - - msg := util.MessageWithEmojiPrefix("✅", fmt.Sprintf("Alias added for %s on %s", contractName, networkName)) - di.logs.stateUpdates = append(di.logs.stateUpdates, msg) - } - - 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 := prompt.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}) - } - } - - 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 - } - - label := fmt.Sprintf("Enter an alias address for %s on %s if you have one, otherwise leave blank", contractName, missingNetwork) - raw := prompt.AddressPromptOrEmpty(label, "Invalid alias address") - - if raw != "" { - contract, err := di.State.Contracts().ByName(contractName) - if err != nil { - return err - } - - contract.Aliases.Add(missingNetwork, flowsdk.HexToAddress(raw)) - } - - return nil -} - -func (di *DependencyInstaller) updateDependencyState(networkName, contractAddress, assignedName, contractName, contractHash string) error { - dep := config.Dependency{ - Name: assignedName, - Source: config.Source{ - NetworkName: networkName, - Address: flowsdk.HexToAddress(contractAddress), - ContractName: contractName, - }, - Hash: contractHash, - } - - isNewDep := di.State.Dependencies().ByName(dep.Name) == nil - - di.State.Dependencies().AddOrUpdate(dep) - di.State.Contracts().AddDependencyAsContract(dep, networkName) - - if isNewDep { - msg := util.MessageWithEmojiPrefix("✅", fmt.Sprintf("%s added to flow.json", dep.Name)) - di.logs.stateUpdates = append(di.logs.stateUpdates, msg) - } - - return nil -} +var _ deps.Prompter = cliPrompter{} diff --git a/internal/dependencymanager/dependencyinstaller_test.go b/internal/dependencymanager/dependencyinstaller_test.go deleted file mode 100644 index f1a9e56a9..000000000 --- a/internal/dependencymanager/dependencyinstaller_test.go +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Flow CLI - * - * Copyright 2019 Dapper Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dependencymanager - -import ( - "fmt" - "testing" - - "github.com/onflow/flow-go-sdk" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - - "github.com/onflow/flowkit/v2/config" - "github.com/onflow/flowkit/v2/gateway" - "github.com/onflow/flowkit/v2/gateway/mocks" - "github.com/onflow/flowkit/v2/output" - "github.com/onflow/flowkit/v2/tests" - - "github.com/onflow/flow-cli/internal/util" -) - -func TestDependencyInstallerInstall(t *testing.T) { - - logger := output.NewStdoutLogger(output.NoneLog) - _, state, _ := util.TestMocks(t) - - serviceAcc, _ := state.EmulatorServiceAccount() - serviceAddress := serviceAcc.Address - - dep := config.Dependency{ - Name: "Hello", - Source: config.Source{ - NetworkName: "emulator", - Address: serviceAddress, - ContractName: "Hello", - }, - } - - state.Dependencies().AddOrUpdate(dep) - - t.Run("Success", func(t *testing.T) { - gw := mocks.DefaultMockGateway() - - gw.GetAccount.Run(func(args mock.Arguments) { - addr := args.Get(1).(flow.Address) - assert.Equal(t, addr.String(), serviceAcc.Address.String()) - acc := tests.NewAccountWithAddress(addr.String()) - acc.Contracts = map[string][]byte{ - tests.ContractHelloString.Name: tests.ContractHelloString.Source, - } - - gw.GetAccount.Return(acc, nil) - }) - - di := &DependencyInstaller{ - Gateways: map[string]gateway.Gateway{ - config.EmulatorNetwork.Name: gw.Mock, - config.TestnetNetwork.Name: gw.Mock, - config.MainnetNetwork.Name: gw.Mock, - config.PreviewnetNetwork.Name: gw.Mock, - }, - Logger: logger, - State: state, - SaveState: true, - TargetDir: "", - SkipDeployments: true, - SkipAlias: true, - dependencies: make(map[string]config.Dependency), - } - - err := di.Install() - assert.NoError(t, err, "Failed to install dependencies") - - filePath := fmt.Sprintf("imports/%s/%s.cdc", serviceAddress.String(), tests.ContractHelloString.Name) - fileContent, err := state.ReaderWriter().ReadFile(filePath) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, fileContent) - }) -} - -func TestDependencyInstallerAdd(t *testing.T) { - - logger := output.NewStdoutLogger(output.NoneLog) - _, state, _ := util.TestMocks(t) - - serviceAcc, _ := state.EmulatorServiceAccount() - serviceAddress := serviceAcc.Address - - t.Run("Success", func(t *testing.T) { - gw := mocks.DefaultMockGateway() - - gw.GetAccount.Run(func(args mock.Arguments) { - addr := args.Get(1).(flow.Address) - assert.Equal(t, addr.String(), serviceAcc.Address.String()) - acc := tests.NewAccountWithAddress(addr.String()) - acc.Contracts = map[string][]byte{ - tests.ContractHelloString.Name: tests.ContractHelloString.Source, - } - - gw.GetAccount.Return(acc, nil) - }) - - di := &DependencyInstaller{ - Gateways: map[string]gateway.Gateway{ - config.EmulatorNetwork.Name: gw.Mock, - config.TestnetNetwork.Name: gw.Mock, - config.MainnetNetwork.Name: gw.Mock, - config.PreviewnetNetwork.Name: gw.Mock, - }, - Logger: logger, - State: state, - SaveState: true, - TargetDir: "", - SkipDeployments: true, - SkipAlias: true, - dependencies: make(map[string]config.Dependency), - } - - sourceStr := fmt.Sprintf("emulator://%s.%s", serviceAddress.String(), tests.ContractHelloString.Name) - err := di.AddBySourceString(sourceStr, "") - assert.NoError(t, err, "Failed to install dependencies") - - filePath := fmt.Sprintf("imports/%s/%s.cdc", serviceAddress.String(), tests.ContractHelloString.Name) - fileContent, err := state.ReaderWriter().ReadFile(filePath) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, fileContent) - }) - - t.Run("Success", func(t *testing.T) { - gw := mocks.DefaultMockGateway() - - gw.GetAccount.Run(func(args mock.Arguments) { - addr := args.Get(1).(flow.Address) - assert.Equal(t, addr.String(), serviceAcc.Address.String()) - acc := tests.NewAccountWithAddress(addr.String()) - acc.Contracts = map[string][]byte{ - tests.ContractHelloString.Name: tests.ContractHelloString.Source, - } - - gw.GetAccount.Return(acc, nil) - }) - - di := &DependencyInstaller{ - Gateways: map[string]gateway.Gateway{ - config.EmulatorNetwork.Name: gw.Mock, - config.TestnetNetwork.Name: gw.Mock, - config.MainnetNetwork.Name: gw.Mock, - }, - Logger: logger, - State: state, - SaveState: true, - TargetDir: "", - SkipDeployments: true, - SkipAlias: true, - dependencies: make(map[string]config.Dependency), - } - - dep := config.Dependency{ - Name: tests.ContractHelloString.Name, - Source: config.Source{ - NetworkName: "emulator", - Address: flow.HexToAddress(serviceAddress.String()), - ContractName: tests.ContractHelloString.Name, - }, - } - err := di.Add(dep) - assert.NoError(t, err, "Failed to install dependencies") - - filePath := fmt.Sprintf("imports/%s/%s.cdc", serviceAddress.String(), tests.ContractHelloString.Name) - fileContent, err := state.ReaderWriter().ReadFile(filePath) - assert.NoError(t, err, "Failed to read generated file") - assert.NotNil(t, fileContent) - }) -} - -func TestDependencyInstallerAddMany(t *testing.T) { - logger := output.NewStdoutLogger(output.NoneLog) - _, state, _ := util.TestMocks(t) - - serviceAcc, _ := state.EmulatorServiceAccount() - serviceAddress := serviceAcc.Address.String() - - dependencies := []config.Dependency{ - { - Name: "ContractOne", - Source: config.Source{ - NetworkName: "emulator", - Address: flow.HexToAddress(serviceAddress), - ContractName: "ContractOne", - }, - }, - { - Name: "ContractTwo", - Source: config.Source{ - NetworkName: "emulator", - Address: flow.HexToAddress(serviceAddress), - ContractName: "ContractTwo", - }, - }, - } - - t.Run("AddMultipleDependencies", func(t *testing.T) { - gw := mocks.DefaultMockGateway() - gw.GetAccount.Run(func(args mock.Arguments) { - addr := args.Get(1).(flow.Address) - assert.Equal(t, addr.String(), serviceAddress) - acc := tests.NewAccountWithAddress(addr.String()) - acc.Contracts = map[string][]byte{ - "ContractOne": []byte("access(all) contract ContractOne {}"), - "ContractTwo": []byte("access(all) contract ContractTwo {}"), - } - gw.GetAccount.Return(acc, nil) - }) - - di := &DependencyInstaller{ - Gateways: map[string]gateway.Gateway{ - config.EmulatorNetwork.Name: gw.Mock, - config.TestnetNetwork.Name: gw.Mock, - config.MainnetNetwork.Name: gw.Mock, - }, - Logger: logger, - State: state, - SaveState: true, - TargetDir: "", - SkipDeployments: true, - SkipAlias: true, - dependencies: make(map[string]config.Dependency), - } - - err := di.AddMany(dependencies) - assert.NoError(t, err, "Failed to add multiple dependencies") - - for _, dep := range dependencies { - filePath := fmt.Sprintf("imports/%s/%s.cdc", dep.Source.Address.String(), dep.Name) - _, err := state.ReaderWriter().ReadFile(filePath) - assert.NoError(t, err, fmt.Sprintf("Failed to read generated file for %s", dep.Name)) - } - }) -} diff --git a/internal/dependencymanager/install.go b/internal/dependencymanager/install.go index a73abf53c..9b1ff92e5 100644 --- a/internal/dependencymanager/install.go +++ b/internal/dependencymanager/install.go @@ -20,12 +20,12 @@ package dependencymanager import ( "fmt" - "github.com/onflow/flow-cli/internal/util" "github.com/spf13/cobra" "github.com/onflow/flowkit/v2" + "github.com/onflow/flowkit/v2/deps" "github.com/onflow/flowkit/v2/output" "github.com/onflow/flow-cli/internal/command" @@ -52,7 +52,12 @@ func install( ) (result command.Result, err error) { logger.Info(util.MessageWithEmojiPrefix("🔄", "Installing dependencies from flow.json...")) - installer, err := NewDependencyInstaller(logger, state, true, "", installFlags) + options := []deps.Option{ + deps.WithSaveState(), + deps.WithLogger(logger), + } + + installer, err := NewCliDependencyInstaller(state, options...) if err != nil { logger.Error(fmt.Sprintf("Error: %v", err)) return nil, err diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index bd8fc26c0..2a65352f8 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -20,6 +20,7 @@ package prompt import ( "fmt" + "github.com/onflow/flowkit/v2/deps" "os" "path/filepath" "strconv" @@ -425,18 +426,12 @@ func NewNetworkPrompt() map[string]string { return networkData } -type DeploymentData struct { - Network string - Account string - Contracts []string -} - func NewDeploymentPrompt( networks config.Networks, accounts config.Accounts, contracts config.Contracts, -) *DeploymentData { - deploymentData := &DeploymentData{} +) *deps.DeploymentData { + deploymentData := &deps.DeploymentData{} var err error networkNames := make([]string, 0) @@ -503,8 +498,8 @@ func removeFromStringArray(s []string, el string) []string { } // 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{ +func AddContractToDeploymentPrompt(networkName string, accounts accounts.Accounts, contractName string) *deps.DeploymentData { + deploymentData := &deps.DeploymentData{ Network: networkName, Contracts: []string{contractName}, } diff --git a/internal/super/setup.go b/internal/super/setup.go index d9652b765..d88ed775d 100644 --- a/internal/super/setup.go +++ b/internal/super/setup.go @@ -21,6 +21,8 @@ package super import ( "bytes" "fmt" + "github.com/onflow/flow-cli/internal/dependencymanager" + "github.com/onflow/flowkit/v2/deps" "io" "os" "path/filepath" @@ -31,7 +33,6 @@ import ( flowkitConfig "github.com/onflow/flowkit/v2/config" "golang.org/x/exp/slices" - "github.com/onflow/flow-cli/internal/dependencymanager" "github.com/onflow/flow-cli/internal/util" "github.com/spf13/afero" @@ -305,8 +306,13 @@ func installCoreContracts(logger output.Logger, state *flowkit.State, tempDir st logger.Info("") logger.Info(util.MessageWithEmojiPrefix("🔄", "Installing selected core contracts and dependencies...")) + options := []deps.Option{ + deps.WithTargetDir(tempDir), + deps.WithLogger(logger), + } + // Add the selected core contracts as dependencies - installer, err := dependencymanager.NewDependencyInstaller(logger, state, false, tempDir, dependencymanager.Flags{}) + installer, err := dependencymanager.NewCliDependencyInstaller(state, options...) if err != nil { return err }