Repository: onflow/flow-cli
Pull request #1563 (2024-05-02 22:45:06)

Merge master into feature branch

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 37d69a3a..9b44cc6f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,6 +12,13 @@ on:
       - master
       - 'feature/**'
 
+# We need to set this explicitly to make sure the CI works on Windows
+# Default shell does not terminate on error in GitHub Actions
+# https://github.com/actions/runner-images/issues/6668
+defaults:
+  run:
+    shell: bash
+
 jobs:
   test:
     strategy:
@@ -22,7 +29,7 @@ jobs:
       - uses: actions/checkout@v3
         with:
           fetch-depth: 0
-      - uses: actions/setup-go@v4
+      - uses: actions/setup-go@v5
         with:
           go-version: "1.20"
       - name: Run tests
@@ -35,12 +42,14 @@ jobs:
         with:
           file: ./coverage.txt
           flags: unittests
+          token: ${{ secrets.CODECOV_TOKEN }}
+        if: matrix.os == 'ubuntu-latest'
 
   lint:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v3
-      - uses: actions/setup-go@v4
+      - uses: actions/setup-go@v5
         with:
           go-version: "1.20"
       - name: generate
diff --git a/Makefile b/Makefile
index 2b51cd90..9708ecac 100644
--- a/Makefile
+++ b/Makefile
@@ -28,8 +28,8 @@ binary: $(BINARY)
 
 .PHONY: install-tools
 install-tools:
-	cd ${GOPATH}; \
-	mkdir -p ${GOPATH}; \
+	cd '${GOPATH}'; \
+	mkdir -p '${GOPATH}'; \
 	GO111MODULE=on go install github.com/axw/gocov/gocov@latest; \
 	GO111MODULE=on go install github.com/matm/gocov-html/cmd/gocov-html@latest; \
 	GO111MODULE=on go install github.com/sanderhahn/gozip/cmd/gozip@latest; \
@@ -86,10 +86,6 @@ publish:
 clean:
 	rm ./cmd/flow/flow*
 
-.PHONY: install-linter
-install-linter:
-	curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ${GOPATH}/bin v1.47.2
-
 .PHONY: lint
 lint: generate
 	golangci-lint run -v ./...
diff --git a/go.mod b/go.mod
index 1b8b56ee..70765299 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
 	github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f
 	github.com/onflow/flow-emulator v0.62.1
 	github.com/onflow/flow-go v0.33.2-0.20240412174857-015156b297b5
-	github.com/onflow/flow-go-sdk v0.46.2
+	github.com/onflow/flow-go-sdk v0.46.3
 	github.com/onflow/flowkit v1.17.3
 	github.com/onflowser/flowser/v3 v3.1.3
 	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
diff --git a/go.sum b/go.sum
index b016a3f0..fb3856c2 100644
--- a/go.sum
+++ b/go.sum
@@ -949,8 +949,8 @@ github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13/
 github.com/onflow/flow-go v0.33.2-0.20240412174857-015156b297b5 h1:2785d9unR8CpzJzRQcMz+yjKKRmqN+Kbvu0gKdy6iT4=
 github.com/onflow/flow-go v0.33.2-0.20240412174857-015156b297b5/go.mod h1:FkonSWZ60bOtc5MdH+CRHScza34k+uNpa6GwXnRpUvg=
 github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJoIv4FaEMg74=
-github.com/onflow/flow-go-sdk v0.46.2 h1:ypVGBeH9m5XpBOTU/CYVC0y/+z42e8mhUlq5aLiD24A=
-github.com/onflow/flow-go-sdk v0.46.2/go.mod h1:tfLjB9FZmwqtT5gaacjvpIhz7KCd67YPm6v+iqYAjEA=
+github.com/onflow/flow-go-sdk v0.46.3 h1:kVe3duL2fS0ojcptB3tuZZSyatCqAsPtykso7irIaxo=
+github.com/onflow/flow-go-sdk v0.46.3/go.mod h1:tfLjB9FZmwqtT5gaacjvpIhz7KCd67YPm6v+iqYAjEA=
 github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ=
 github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0=
 github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY=
diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go
index 70e49b5b..c2a27f01 100644
--- a/internal/dependencymanager/dependencyinstaller.go
+++ b/internal/dependencymanager/dependencyinstaller.go
@@ -51,6 +51,7 @@ import (
 type categorizedLogs struct {
 	fileSystemActions []string
 	stateUpdates      []string
+	issues            []string
 }
 
 func (cl *categorizedLogs) LogAll(logger output.Logger) {
@@ -58,21 +59,29 @@ func (cl *categorizedLogs) LogAll(logger output.Logger) {
 	logger.Info("") // Add a line break after the section
 
 	if len(cl.fileSystemActions) > 0 {
-		logger.Info("🗃️  File System Actions:")
+		logger.Info(util.MessageWithEmojiPrefix("🗃️", "File System Actions:"))
 		for _, msg := range cl.fileSystemActions {
-			logger.Info(util.MessageWithEmojiPrefix("✅", msg))
+			logger.Info(msg)
 		}
 		logger.Info("") // Add a line break after the section
 	}
 
 	if len(cl.stateUpdates) > 0 {
-		logger.Info("💾 State Updates:")
+		logger.Info(util.MessageWithEmojiPrefix("💾", "State Updates:"))
 		for _, msg := range cl.stateUpdates {
-			logger.Info(util.MessageWithEmojiPrefix("✅", msg))
+			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."))
 	}
@@ -326,7 +335,8 @@ func (di *DependencyInstaller) handleFileSystem(contractAddr, contractName, cont
 			return fmt.Errorf("failed to create contract file: %w", err)
 		}
 
-		di.logs.fileSystemActions = append(di.logs.fileSystemActions, fmt.Sprintf("%s from %s on %s installed", contractName, contractAddr, networkName))
+		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
@@ -343,6 +353,20 @@ func isCoreContract(contractName string) bool {
 	return false
 }
 
+// checkForContractConflicts checks if a contract with the same name already exists in the state and adds a warning
+func (di *DependencyInstaller) checkForContractConflicts(contractName string) error {
+	_, err := di.State.Contracts().ByName(contractName)
+	if err != nil {
+		return nil
+	} else {
+		if !isCoreContract(contractName) {
+			msg := util.MessageWithEmojiPrefix("❌", fmt.Sprintf("Contract named %s already exists in flow.json", contractName))
+			di.logs.issues = append(di.logs.issues, msg)
+		}
+		return nil
+	}
+}
+
 func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, assignedName, contractName string, program *project.Program) error {
 	hash := sha256.New()
 	hash.Write(program.CodeWithUnprocessedImports())
@@ -370,7 +394,14 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 		}
 	}
 
-	err := di.handleFileSystem(contractAddr, contractName, contractData, networkName)
+	//// This needs to happen before dependency state is updated
+	err := di.checkForContractConflicts(assignedName)
+	if err != nil {
+		di.Logger.Error(fmt.Sprintf("Error checking for contract conflicts: %v", err))
+		return err
+	}
+
+	err = di.handleFileSystem(contractAddr, contractName, contractData, networkName)
 	if err != nil {
 		return fmt.Errorf("error handling file system: %w", err)
 	}
@@ -389,7 +420,8 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 			return err
 		}
 
-		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("%s added to emulator deployments", contractName))
+		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
@@ -400,7 +432,8 @@ func (di *DependencyInstaller) handleFoundContract(networkName, contractAddr, as
 			return err
 		}
 
-		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("Alias added for %s on %s", contractName, networkName))
+		msg := util.MessageWithEmojiPrefix("✅", fmt.Sprintf("Alias added for %s on %s", contractName, networkName))
+		di.logs.stateUpdates = append(di.logs.stateUpdates, msg)
 	}
 
 	return nil
@@ -470,7 +503,8 @@ func (di *DependencyInstaller) updateDependencyState(networkName, contractAddres
 	di.State.Contracts().AddDependencyAsContract(dep, networkName)
 
 	if isNewDep {
-		di.logs.stateUpdates = append(di.logs.stateUpdates, fmt.Sprintf("%s added to flow.json", dep.Name))
+		msg := util.MessageWithEmojiPrefix("✅", fmt.Sprintf("%s added to flow.json", dep.Name))
+		di.logs.stateUpdates = append(di.logs.stateUpdates, msg)
 	}
 
 	return nil
diff --git a/internal/util/util.go b/internal/util/util.go
index b9d33c7f..845673d9 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -100,3 +100,18 @@ func ValidateECDSAP256Pub(key string) error {
 
 	return nil
 }
+
+func removeFromStringArray(s []string, el string) []string {
+	for i, v := range s {
+		if v == el {
+			s = append(s[:i], s[i+1:]...)
+			break
+		}
+	}
+
+	return s
+}
+
+func NormalizeLineEndings(s string) string {
+	return strings.ReplaceAll(s, "\r\n", "\n")
+}


Test patch:
diff --git a/internal/super/generate_test.go b/internal/super/generate_test.go
index c21ef688..951d6ed9 100644
--- a/internal/super/generate_test.go
+++ b/internal/super/generate_test.go
@@ -19,6 +19,8 @@
 package super
 
 import (
+	"fmt"
+	"path/filepath"
 	"testing"
 
 	"github.com/onflow/flow-cli/internal/util"
@@ -64,13 +66,14 @@ access(all) fun testContract() {
     Test.expect(err, Test.beNil())
 }`
 
-	assert.Equal(t, expectedContent, string(fileContent))
-	assert.Equal(t, expectedTestContent, string(testContent))
+	assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(fileContent)))
+	assert.Equal(t, expectedTestContent, util.NormalizeLineEndings(string(testContent)))
 
 	// Test file already exists scenario
 	_, err = generateNew([]string{"TestContract"}, "contract", "", logger, state)
 	assert.Error(t, err)
-	assert.Equal(t, "file already exists: cadence/contracts/TestContract.cdc", err.Error())
+	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
+	assert.Equal(t, expectedError, err.Error())
 }
 
 func TestGenerateNewContractSkipTests(t *testing.T) {
@@ -129,7 +132,8 @@ func TestGenerateNewContractFileAlreadyExists(t *testing.T) {
 	// Test file already exists scenario
 	_, err = generateNew([]string{"TestContract"}, "contract", "", logger, state)
 	assert.Error(t, err)
-	assert.Equal(t, "file already exists: cadence/contracts/TestContract.cdc", err.Error())
+	expectedError := fmt.Sprintf("file already exists: %s", filepath.FromSlash("cadence/contracts/TestContract.cdc"))
+	assert.Equal(t, expectedError, err.Error())
 }
 
 func TestGenerateNewContractWithFileExtension(t *testing.T) {
@@ -160,7 +164,7 @@ func TestGenerateNewScript(t *testing.T) {
 fun main() {
     // Script details here
 }`
-	assert.Equal(t, expectedContent, string(content))
+	assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(content)))
 }
 
 func TestGenerateNewTransaction(t *testing.T) {
@@ -179,7 +183,7 @@ func TestGenerateNewTransaction(t *testing.T) {
 
     execute {}
 }`
-	assert.Equal(t, expectedContent, string(content))
+	assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(content)))
 }
 
 func TestGenerateNewWithDirFlag(t *testing.T) {
@@ -201,5 +205,5 @@ func TestGenerateNewWithDirFlag(t *testing.T) {
 contract TestContract {
     init() {}
 }`
-	assert.Equal(t, expectedContent, string(content))
+	assert.Equal(t, expectedContent, util.NormalizeLineEndings(string(content)))
 }
diff --git a/internal/transactions/transactions_test.go b/internal/transactions/transactions_test.go
index 3f45b942..e8831d02 100644
--- a/internal/transactions/transactions_test.go
+++ b/internal/transactions/transactions_test.go
@@ -447,10 +447,10 @@ Fee Events (hidden, use --include fee-events)`, output.OkEmoji()), "\n")
 	t.Run("Result without fee events", func(t *testing.T) {
 		result := transactionResult{tx: tx, result: txResultFeeEvents}
 
-		assert.Equal(t, strings.TrimPrefix(`
+		assert.Equal(t, strings.TrimPrefix(fmt.Sprintf(`
 Block ID	7aa74143741c1c3b837d389fcffa7a5e251b67b4ffef6d6887b40cd9c803f537
 Block Height	1
-Status		✅ SEALED
+Status		%s SEALED
 ID		e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f
 Payer		0000000000000002
 Authorizers	[]
@@ -477,15 +477,15 @@ Code (hidden, use --include code)
 
 Payload (hidden, use --include payload)
 
-Fee Events (hidden, use --include fee-events)`, "\n"), result.String())
+Fee Events (hidden, use --include fee-events)`, output.OkEmoji()), "\n"), result.String())
 	})
 	t.Run("Result with fee events", func(t *testing.T) {
 		result := transactionResult{tx: tx, result: txResultFeeEvents, include: []string{"fee-events"}}
 
-		assert.Equal(t, strings.TrimPrefix(`
+		assert.Equal(t, strings.TrimPrefix(fmt.Sprintf(`
 Block ID	7aa74143741c1c3b837d389fcffa7a5e251b67b4ffef6d6887b40cd9c803f537
 Block Height	1
-Status		✅ SEALED
+Status		%s SEALED
 ID		e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f
 Payer		0000000000000002
 Authorizers	[]
@@ -528,6 +528,6 @@ Events:
 
 Code (hidden, use --include code)
 
-Payload (hidden, use --include payload)`, "\n"), result.String())
+Payload (hidden, use --include payload)`, output.OkEmoji()), "\n"), result.String())
 	})
 }