Repository: onflow/flow-cli
Pull request #1565 (2024-05-02 23:56:56)

Merge master into Cadence 1.0 feature branch
Depends on #1564

## Description

Merge `master` into `feature/stable-cadence`

<details>
<summary>Conflict resolution:</summary>

```diff
commit 82b037adb618fa3cdea4a1778fc494fd9dd86100
Merge: 75655ff1 c2de12d5
Author: Bastian Müller <bastian@turbolent.com>
Date:   Thu May 2 16:55:26 2024 -0700

    Merge branch 'master' into bastian/update-stable-cadence-2

diff --git a/go.mod b/go.mod
remerge CONFLICT (content): Merge conflict in go.mod
index 08a359d5..1e6d0245 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,6 @@ require (
 	github.com/gosuri/uilive v0.0.4
 	github.com/logrusorgru/aurora/v4 v4.0.0
 	github.com/manifoldco/promptui v0.9.0
-<<<<<<< 75655ff1 (adjust code to API changes in cadence package)
 	github.com/onflow/cadence v1.0.0-preview.25
 	github.com/onflow/cadence-tools/languageserver v1.0.0-preview.14
 	github.com/onflow/cadence-tools/lint v1.0.0-preview.17
@@ -26,20 +25,6 @@ require (
 	github.com/onflow/flowkit/v2 v2.0.0-stable-cadence-alpha.18
 	github.com/onflow/go-ethereum v1.13.4
 	github.com/onflowser/flowser/v3 v3.2.1-0.20240131200229-7d4d22715f48
-=======
-	github.com/onflow/cadence v0.42.10
-	github.com/onflow/cadence-tools/languageserver v0.33.5
-	github.com/onflow/cadence-tools/lint v0.14.2
-	github.com/onflow/cadence-tools/test v0.14.7
-	github.com/onflow/fcl-dev-wallet v0.7.4
-	github.com/onflow/flixkit-go v1.1.3
-	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.3
-	github.com/onflow/flowkit v1.17.3
-	github.com/onflowser/flowser/v3 v3.1.3
->>>>>>> c2de12d5 (Default `flow test` behaviour to test `**/*_test.cdc` (#1550))
 	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
 	github.com/pkg/errors v0.9.1
 	github.com/psiemens/sconfig v0.1.0
diff --git a/go.sum b/go.sum
remerge CONFLICT (content): Merge conflict in go.sum
index 15c56b12..21d90620 100644
--- a/go.sum
+++ b/go.sum
@@ -2073,7 +2073,6 @@ github.com/onflow/contract-updater/lib/go/templates v1.0.1/go.mod h1:OXO6s0X7OW4
 github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI=
 github.com/onflow/crypto v0.25.1 h1:0txy2PKPMM873JbpxQNbJmuOJtD56bfs48RQfm0ts5A=
 github.com/onflow/crypto v0.25.1/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI=
-<<<<<<< 75655ff1 (adjust code to API changes in cadence package)
 github.com/onflow/fcl-dev-wallet v0.8.0-stable-cadence.1 h1:IqdUzdqFCSW0klWmA3J9c17ZyQTab9SWcWSLouX6o0Q=
 github.com/onflow/fcl-dev-wallet v0.8.0-stable-cadence.1/go.mod h1:kc42jkiuoPJmxMRFjfbRO9XvnR/3XLheaOerxVMDTiw=
 github.com/onflow/flixkit-go v1.2.1-cadence-v1-preview.13 h1:/3oNbdEsi98SWlSzmabe1DXfy4OCUSC1Gwe9vFX6anY=
@@ -2104,33 +2103,6 @@ github.com/onflow/flow/protobuf/go/flow v0.4.1-0.20240412170550-911321113030 h1:
 github.com/onflow/flow/protobuf/go/flow v0.4.1-0.20240412170550-911321113030/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk=
 github.com/onflow/flowkit/v2 v2.0.0-stable-cadence-alpha.18 h1:vgQ1SpQ9mpTX+c6x1hm71DmaLnHoMr3kIG479Ye0ll4=
 github.com/onflow/flowkit/v2 v2.0.0-stable-cadence-alpha.18/go.mod h1:uQiVIHhm6dXrXDM9SQG00FIaEQgh+TdVvM+ortvpF+M=
-=======
-github.com/onflow/fcl-dev-wallet v0.7.4 h1:vI6t3U0AO88R/Iitn5KsnniSpbN9Lqsqwvi9EJT4C0k=
-github.com/onflow/fcl-dev-wallet v0.7.4/go.mod h1:kc42jkiuoPJmxMRFjfbRO9XvnR/3XLheaOerxVMDTiw=
-github.com/onflow/flixkit-go v1.1.3 h1:td94mkklIwXvDP075D0eofPE04oKse24PxvFTyOvMJI=
-github.com/onflow/flixkit-go v1.1.3/go.mod h1:qOYI1l7r3gSPk6luTnk95ni020UeTY+E48GuB3Tl0xk=
-github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f h1:S8yIZw9LFXfYD1V5H9BiixihHw3GrXVPrmfplSzYaww=
-github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:jM6GMAL+m0hjusUgiYDNrixPQ6b9s8xjoJQoEu5bHQI=
-github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f h1:Ep+Mpo2miWMe4pjPGIaEvEzshRep30dvNgxqk+//FrQ=
-github.com/onflow/flow-core-contracts/lib/go/templates v1.2.4-0.20231016154253-a00dbf7c061f/go.mod h1:ZeLxwaBkzuSInESGjL8/IPZWezF+YOYsYbMrZlhN+q4=
-github.com/onflow/flow-emulator v0.62.1 h1:VivECYftZbaIo9oZBVsmeE/LUcxdx7hJJ1ET2rGY8p8=
-github.com/onflow/flow-emulator v0.62.1/go.mod h1:JbvxBZ39Q8hOQ5fSwuV0ZJwfaiQeaYTiIY04ws8LcNE=
-github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 h1:B4ll7e3j+MqTJv2122Enq3RtDNzmIGRu9xjV7fo7un0=
-github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13/go.mod h1:kTMFIySzEJJeupk+7EmXs0EJ6CBWY/MV9fv9iYQk+RU=
-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.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=
-github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8=
-github.com/onflow/flow/protobuf/go/flow v0.4.0 h1:5TGmPwRmnSt7aawgtPGF9ehoGHHir9Cy9LVoAiU9t/E=
-github.com/onflow/flow/protobuf/go/flow v0.4.0/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk=
-github.com/onflow/flowkit v1.17.3 h1:0Fi3tLnieWOYq95A3ySk3TZ0GUPtKaX8ofssgve9e58=
-github.com/onflow/flowkit v1.17.3/go.mod h1:laapt1diM4Pbqt0dotKMH+LbUAOEqgugpH1p4+sPhcw=
->>>>>>> c2de12d5 (Default `flow test` behaviour to test `**/*_test.cdc` (#1550))
 github.com/onflow/go-ethereum v1.13.4 h1:iNO86fm8RbBbhZ87ZulblInqCdHnAQVY8okBrNsTevc=
 github.com/onflow/go-ethereum v1.13.4/go.mod h1:cE/gEUkAffhwbVmMJYz+t1dAfVNHNwZCgc3BWtZxBGY=
 github.com/onflow/sdks v0.5.1-0.20230912225508-b35402f12bba h1:rIehuhO6bj4FkwE4VzwEjX7MoAlOhUJENBJLqDqVxAo=
diff --git a/internal/command/command.go b/internal/command/command.go
remerge CONFLICT (content): Merge conflict in internal/command/command.go
index 55d6f2d0..797ecdf6 100644
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -261,7 +261,6 @@ func createLogger(logFlag string, formatFlag string) output.Logger {
 
 // checkVersion fetches latest version and compares it to local.
 func checkVersion(logger output.Logger) {
-<<<<<<< 75655ff1 (adjust code to API changes in cadence package)
 	currentVersion := build.Semver()
 	if isDevelopment() {
 		return // avoid warning in local development
@@ -273,10 +272,7 @@ func checkVersion(logger output.Logger) {
 		return
 	}
 
-	resp, err := http.Get("https://raw.githubusercontent.com/onflow/flow-cli/master/version.txt")
-=======
 	resp, err := http.Get("https://formulae.brew.sh/api/formula/flow-cli.json")
->>>>>>> c2de12d5 (Default `flow test` behaviour to test `**/*_test.cdc` (#1550))
 	if err != nil || resp.StatusCode >= 400 {
 		return
 	}

```


</details>

 
______

For contributor use:

- [ ] Targeted PR against `master` branch
- [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [x] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [x] Updated relevant documentation
- [x] Re-reviewed `Files changed` in the Github PR explorer
- [x] Added appropriate labels



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 792f1410..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,17 +42,19 @@ 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
         run: make generate
-      - uses: golangci/golangci-lint-action@v3.7.0
+      - uses: golangci/golangci-lint-action@v5.1.0
         with:
           version: v1.52.2
           only-new-issues: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1ccf6330..8cce65bb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -159,7 +159,7 @@ You can also release manually although this is not recommended:
 
 To make the new version the default version that is installed
 
-- Change `version.txt` and commit it
+- **DEPRECATED** Change `version.txt` and commit it.  (This file is no longer user for versions of the Flow CLI later than v1.18.0, although it should still be maintained to support older versions of the CLI for a while)
 
 ## Adding a scaffold
 You can add your own scaffold by creating a GitHub repository containing the scaffold content and then making a PR 
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/internal/command/command.go b/internal/command/command.go
index 266ab7e3..797ecdf6 100644
--- a/internal/command/command.go
+++ b/internal/command/command.go
@@ -272,7 +272,7 @@ func checkVersion(logger output.Logger) {
 		return
 	}
 
-	resp, err := http.Get("https://raw.githubusercontent.com/onflow/flow-cli/master/version.txt")
+	resp, err := http.Get("https://formulae.brew.sh/api/formula/flow-cli.json")
 	if err != nil || resp.StatusCode >= 400 {
 		return
 	}
@@ -285,11 +285,25 @@ func checkVersion(logger output.Logger) {
 	}(resp.Body)
 
 	body, _ := io.ReadAll(resp.Body)
-	latestVersion := strings.TrimSpace(string(body))
+	var data map[string]interface{}
+	err = json.Unmarshal(body, &data)
+	if err != nil {
+		return
+	}
+
+	versions, ok := data["versions"].(map[string]interface{})
+	if !ok {
+		return
+	}
+
+	latestVersion, ok := versions["stable"].(string)
+	if !ok {
+		return
+	}
 
 	if currentVersion != latestVersion {
 		logger.Info(fmt.Sprintf(
-			"\n%s  Version warning: a new version of Flow CLI is available (%s).\n"+
+			"\n%s  Version warning: a new version of Flow CLI is available (v%s).\n"+
 				"   Read the installation guide for upgrade instructions: https://docs.onflow.org/flow-cli/install\n",
 			output.WarningEmoji(),
 			strings.ReplaceAll(latestVersion, "\n", ""),
diff --git a/internal/dependencymanager/dependencyinstaller.go b/internal/dependencymanager/dependencyinstaller.go
index 0b33930e..eb3e78f3 100644
--- a/internal/dependencymanager/dependencyinstaller.go
+++ b/internal/dependencymanager/dependencyinstaller.go
@@ -50,6 +50,7 @@ import (
 type categorizedLogs struct {
 	fileSystemActions []string
 	stateUpdates      []string
+	issues            []string
 }
 
 func (cl *categorizedLogs) LogAll(logger output.Logger) {
@@ -57,21 +58,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."))
 	}
@@ -286,7 +295,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
@@ -303,6 +313,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())
@@ -330,7 +354,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)
 	}
@@ -349,7 +380,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
@@ -360,7 +392,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
@@ -430,7 +463,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 3c28c883..59b481fb 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -112,3 +112,7 @@ func removeFromStringArray(s []string, el string) []string {
 
 	return s
 }
+
+func NormalizeLineEndings(s string) string {
+	return strings.ReplaceAll(s, "\r\n", "\n")
+}
diff --git a/version.txt b/version.txt
index 7893098b..f7201912 100644
--- a/version.txt
+++ b/version.txt
@@ -1,1 +1,1 @@
-v1.17.1
+v1.18.0


Test patch:
diff --git a/internal/super/generate_test.go b/internal/super/generate_test.go
index 21a8e2a8..e0ea7ec9 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) {
@@ -200,5 +204,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/test/test.go b/internal/test/test.go
index 9f7d3cea..6bf38698 100644
--- a/internal/test/test.go
+++ b/internal/test/test.go
@@ -61,6 +61,9 @@ const helperScriptSubstr = "_helper"
 // scripts and transactions are excluded from coverage report.
 const contractsCoverCode = "contracts"
 
+// The default glob pattern to find test files.
+const defaultTestSuffix = "_test.cdc"
+
 type flagsTests struct {
 	Cover        bool   `default:"false" flag:"cover" info:"Use the cover flag to calculate coverage report"`
 	CoverProfile string `default:"coverage.json" flag:"coverprofile" info:"Filename to write the calculated coverage report. Supported extensions are .json and .lcov"`
@@ -74,10 +77,14 @@ var testFlags = flagsTests{}
 
 var TestCommand = &command.Command{
 	Cmd: &cobra.Command{
-		Use:     "test <filename>",
-		Short:   "Run Cadence tests",
-		Example: `flow test script.cdc`,
-		Args:    cobra.MinimumNArgs(1),
+		Use:   "test [files...]",
+		Short: "Run Cadence tests",
+		Example: `# Run tests in files matching default pattern **/*_test.cdc
+flow test
+
+# Run tests in the specified files
+flow test test1.cdc test2.cdc`,
+		Args:    cobra.ArbitraryArgs,
 		GroupID: "tools",
 	},
 	Flags: &testFlags,
@@ -101,8 +108,19 @@ func run(
 		)
 	}
 
+	var filenames []string
+	if len(args) == 0 {
+		var err error
+		filenames, err = findAllTestFiles(".")
+		if err != nil {
+			return nil, fmt.Errorf("error loading script files: %w", err)
+		}
+	} else {
+		filenames = args
+	}
+
 	testFiles := make(map[string][]byte, 0)
-	for _, filename := range args {
+	for _, filename := range filenames {
 		code, err := state.ReadFile(filename)
 		if err != nil {
 			return nil, fmt.Errorf("error loading script file: %w", err)
@@ -341,14 +359,18 @@ func (r *result) String() string {
 	var b bytes.Buffer
 	writer := util.CreateTabWriter(&b)
 
-	for scriptPath, testResult := range r.Results {
-		_, _ = fmt.Fprint(writer, cdcTests.PrettyPrintResults(testResult, scriptPath))
-	}
-	if r.CoverageReport != nil {
-		_, _ = fmt.Fprint(writer, r.CoverageReport.String())
-	}
-	if r.RandomSeed > 0 {
-		_, _ = fmt.Fprintf(writer, "\nSeed: %d", r.RandomSeed)
+	if len(r.Results) == 0 {
+		_, _ = fmt.Fprint(writer, "No tests found")
+	} else {
+		for scriptPath, testResult := range r.Results {
+			_, _ = fmt.Fprint(writer, cdcTests.PrettyPrintResults(testResult, scriptPath))
+		}
+		if r.CoverageReport != nil {
+			_, _ = fmt.Fprint(writer, r.CoverageReport.String())
+		}
+		if r.RandomSeed > 0 {
+			_, _ = fmt.Fprintf(writer, "\nSeed: %d", r.RandomSeed)
+		}
 	}
 
 	_ = writer.Flush()
@@ -382,3 +404,24 @@ func (r *result) Oneliner() string {
 func (r *result) ExitCode() int {
 	return r.exitCode
 }
+
+func findAllTestFiles(baseDir string) ([]string, error) {
+	var filenames []string
+	err := filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if !strings.HasSuffix(path, defaultTestSuffix) {
+			return nil
+		}
+
+		filenames = append(filenames, path)
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return filenames, nil
+}
diff --git a/internal/transactions/transactions_test.go b/internal/transactions/transactions_test.go
index 6604f657..122a763f 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())
 	})
 }