From 4dd0f24c248bb5ab14b76d10b32473ddad038621 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:16:56 -0700 Subject: [PATCH 1/6] Add logic to not write duplicates --- internal/super/setup.go | 30 ++-------- internal/util/util.go | 128 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 24 deletions(-) diff --git a/internal/super/setup.go b/internal/super/setup.go index 15c5032be..55a3e65d4 100644 --- a/internal/super/setup.go +++ b/internal/super/setup.go @@ -108,35 +108,17 @@ func create( } func updateGitignore(targetDir string) error { - gitignorePath := filepath.Join(targetDir, ".gitignore") - f, err := os.OpenFile(gitignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() - - _, err = f.WriteString("\n# flow\nemulator-account.pkey\nimports\n.env\n") - if err != nil { - return err + rw := afero.Afero{ + Fs: afero.NewOsFs(), } - - return nil + return util.AddFlowEntriesToGitIgnore(targetDir, rw) } func updateCursorIgnore(targetDir string) error { - cursorignorePath := filepath.Join(targetDir, ".cursorignore") - f, err := os.OpenFile(cursorignorePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() - - _, err = f.WriteString("\n# flow\nemulator-account.pkey\n.env\n\n# Pay attention to imports directory\n!imports/**\n") - if err != nil { - return err + rw := afero.Afero{ + Fs: afero.NewOsFs(), } - - return nil + return util.AddFlowEntriesToCursorIgnore(targetDir, rw) } func createConfigOnly(targetDir string, readerWriter flowkit.ReaderWriter) error { diff --git a/internal/util/util.go b/internal/util/util.go index 5c284b451..f7ddb4095 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -64,6 +64,15 @@ func AddToGitIgnore(filename string, loader flowkit.ReaderWriter) error { gitIgnoreFiles = string(gitIgnoreFilesRaw) filePermissions = fileStat.Mode().Perm() } + + // Check if the filename already exists in the content + lines := strings.Split(strings.TrimSpace(gitIgnoreFiles), "\n") + for _, line := range lines { + if strings.TrimSpace(line) == strings.TrimSpace(filename) { + return nil // Entry already exists, no need to add + } + } + return loader.WriteFile( gitIgnorePath, fmt.Appendf(nil, "%s\n%s", gitIgnoreFiles, filename), @@ -90,6 +99,15 @@ func AddToCursorIgnore(filename string, loader flowkit.ReaderWriter) error { cursorIgnoreFiles = string(cursorIgnoreFilesRaw) filePermissions = fileStat.Mode().Perm() } + + // Check if the filename already exists in the content + lines := strings.Split(strings.TrimSpace(cursorIgnoreFiles), "\n") + for _, line := range lines { + if strings.TrimSpace(line) == strings.TrimSpace(filename) { + return nil // Entry already exists, no need to add + } + } + return loader.WriteFile( cursorIgnorePath, fmt.Appendf(nil, "%s\n%s", cursorIgnoreFiles, filename), @@ -97,6 +115,116 @@ func AddToCursorIgnore(filename string, loader flowkit.ReaderWriter) error { ) } +// AddFlowEntriesToGitIgnore adds the standard Flow entries to .gitignore without duplicates +func AddFlowEntriesToGitIgnore(targetDir string, loader flowkit.ReaderWriter) error { + flowEntries := []string{ + "# flow", + "emulator-account.pkey", + "imports", + ".env", + } + + gitIgnorePath := filepath.Join(targetDir, ".gitignore") + gitIgnoreFiles := "" + filePermissions := os.FileMode(0644) + + fileStat, err := os.Stat(gitIgnorePath) + if !os.IsNotExist(err) { + gitIgnoreFilesRaw, err := loader.ReadFile(gitIgnorePath) + if err != nil { + return err + } + gitIgnoreFiles = string(gitIgnoreFilesRaw) + filePermissions = fileStat.Mode().Perm() + } + + // Split existing content into lines + existingLines := strings.Split(strings.TrimSpace(gitIgnoreFiles), "\n") + existingSet := make(map[string]bool) + for _, line := range existingLines { + if strings.TrimSpace(line) != "" { + existingSet[strings.TrimSpace(line)] = true + } + } + + // Add new entries that don't already exist + var newEntries []string + for _, entry := range flowEntries { + if !existingSet[strings.TrimSpace(entry)] { + newEntries = append(newEntries, entry) + } + } + + if len(newEntries) == 0 { + return nil // All entries already exist + } + + // Combine existing content with new entries + content := gitIgnoreFiles + if content != "" && !strings.HasSuffix(content, "\n") { + content += "\n" + } + content += strings.Join(newEntries, "\n") + + return loader.WriteFile(gitIgnorePath, []byte(content), filePermissions) +} + +// AddFlowEntriesToCursorIgnore adds the standard Flow entries to .cursorignore without duplicates +func AddFlowEntriesToCursorIgnore(targetDir string, loader flowkit.ReaderWriter) error { + flowEntries := []string{ + "# flow", + "emulator-account.pkey", + ".env", + "", + "# Pay attention to imports directory", + "!imports/**", + } + + cursorIgnorePath := filepath.Join(targetDir, ".cursorignore") + cursorIgnoreFiles := "" + filePermissions := os.FileMode(0644) + + fileStat, err := os.Stat(cursorIgnorePath) + if !os.IsNotExist(err) { + cursorIgnoreFilesRaw, err := loader.ReadFile(cursorIgnorePath) + if err != nil { + return err + } + cursorIgnoreFiles = string(cursorIgnoreFilesRaw) + filePermissions = fileStat.Mode().Perm() + } + + // Split existing content into lines + existingLines := strings.Split(strings.TrimSpace(cursorIgnoreFiles), "\n") + existingSet := make(map[string]bool) + for _, line := range existingLines { + if strings.TrimSpace(line) != "" { + existingSet[strings.TrimSpace(line)] = true + } + } + + // Add new entries that don't already exist + var newEntries []string + for _, entry := range flowEntries { + if !existingSet[strings.TrimSpace(entry)] { + newEntries = append(newEntries, entry) + } + } + + if len(newEntries) == 0 { + return nil // All entries already exist + } + + // Combine existing content with new entries + content := cursorIgnoreFiles + if content != "" && !strings.HasSuffix(content, "\n") { + content += "\n" + } + content += strings.Join(newEntries, "\n") + + return loader.WriteFile(cursorIgnorePath, []byte(content), filePermissions) +} + // GetAddressNetwork returns the chain ID for an address. func GetAddressNetwork(address flow.Address) (flow.ChainID, error) { networks := []flow.ChainID{ From 6117218b576928708f1a3cdb980f0db0c50b98b8 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:18:30 -0700 Subject: [PATCH 2/6] Clean up code --- internal/util/util.go | 84 +++++++++++++------------------------------ 1 file changed, 24 insertions(+), 60 deletions(-) diff --git a/internal/util/util.go b/internal/util/util.go index f7ddb4095..4f181fc32 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -115,31 +115,23 @@ func AddToCursorIgnore(filename string, loader flowkit.ReaderWriter) error { ) } -// AddFlowEntriesToGitIgnore adds the standard Flow entries to .gitignore without duplicates -func AddFlowEntriesToGitIgnore(targetDir string, loader flowkit.ReaderWriter) error { - flowEntries := []string{ - "# flow", - "emulator-account.pkey", - "imports", - ".env", - } - - gitIgnorePath := filepath.Join(targetDir, ".gitignore") - gitIgnoreFiles := "" +// addEntriesToIgnoreFile is a helper function that adds entries to an ignore file without duplicates +func addEntriesToIgnoreFile(filePath string, entries []string, loader flowkit.ReaderWriter) error { + existingContent := "" filePermissions := os.FileMode(0644) - fileStat, err := os.Stat(gitIgnorePath) + fileStat, err := os.Stat(filePath) if !os.IsNotExist(err) { - gitIgnoreFilesRaw, err := loader.ReadFile(gitIgnorePath) + existingContentRaw, err := loader.ReadFile(filePath) if err != nil { return err } - gitIgnoreFiles = string(gitIgnoreFilesRaw) + existingContent = string(existingContentRaw) filePermissions = fileStat.Mode().Perm() } // Split existing content into lines - existingLines := strings.Split(strings.TrimSpace(gitIgnoreFiles), "\n") + existingLines := strings.Split(strings.TrimSpace(existingContent), "\n") existingSet := make(map[string]bool) for _, line := range existingLines { if strings.TrimSpace(line) != "" { @@ -149,7 +141,7 @@ func AddFlowEntriesToGitIgnore(targetDir string, loader flowkit.ReaderWriter) er // Add new entries that don't already exist var newEntries []string - for _, entry := range flowEntries { + for _, entry := range entries { if !existingSet[strings.TrimSpace(entry)] { newEntries = append(newEntries, entry) } @@ -160,13 +152,26 @@ func AddFlowEntriesToGitIgnore(targetDir string, loader flowkit.ReaderWriter) er } // Combine existing content with new entries - content := gitIgnoreFiles + content := existingContent if content != "" && !strings.HasSuffix(content, "\n") { content += "\n" } content += strings.Join(newEntries, "\n") - return loader.WriteFile(gitIgnorePath, []byte(content), filePermissions) + return loader.WriteFile(filePath, []byte(content), filePermissions) +} + +// AddFlowEntriesToGitIgnore adds the standard Flow entries to .gitignore without duplicates +func AddFlowEntriesToGitIgnore(targetDir string, loader flowkit.ReaderWriter) error { + flowEntries := []string{ + "# flow", + "emulator-account.pkey", + "imports", + ".env", + } + + gitIgnorePath := filepath.Join(targetDir, ".gitignore") + return addEntriesToIgnoreFile(gitIgnorePath, flowEntries, loader) } // AddFlowEntriesToCursorIgnore adds the standard Flow entries to .cursorignore without duplicates @@ -181,48 +186,7 @@ func AddFlowEntriesToCursorIgnore(targetDir string, loader flowkit.ReaderWriter) } cursorIgnorePath := filepath.Join(targetDir, ".cursorignore") - cursorIgnoreFiles := "" - filePermissions := os.FileMode(0644) - - fileStat, err := os.Stat(cursorIgnorePath) - if !os.IsNotExist(err) { - cursorIgnoreFilesRaw, err := loader.ReadFile(cursorIgnorePath) - if err != nil { - return err - } - cursorIgnoreFiles = string(cursorIgnoreFilesRaw) - filePermissions = fileStat.Mode().Perm() - } - - // Split existing content into lines - existingLines := strings.Split(strings.TrimSpace(cursorIgnoreFiles), "\n") - existingSet := make(map[string]bool) - for _, line := range existingLines { - if strings.TrimSpace(line) != "" { - existingSet[strings.TrimSpace(line)] = true - } - } - - // Add new entries that don't already exist - var newEntries []string - for _, entry := range flowEntries { - if !existingSet[strings.TrimSpace(entry)] { - newEntries = append(newEntries, entry) - } - } - - if len(newEntries) == 0 { - return nil // All entries already exist - } - - // Combine existing content with new entries - content := cursorIgnoreFiles - if content != "" && !strings.HasSuffix(content, "\n") { - content += "\n" - } - content += strings.Join(newEntries, "\n") - - return loader.WriteFile(cursorIgnorePath, []byte(content), filePermissions) + return addEntriesToIgnoreFile(cursorIgnorePath, flowEntries, loader) } // GetAddressNetwork returns the chain ID for an address. From da994e2c183111e2ae477c7a1809ae4a5783652a Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:31:23 -0700 Subject: [PATCH 3/6] Add tests --- internal/util/util.go | 14 ++-- internal/util/util_test.go | 129 +++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 internal/util/util_test.go diff --git a/internal/util/util.go b/internal/util/util.go index 4f181fc32..7fc25aada 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -120,14 +120,14 @@ func addEntriesToIgnoreFile(filePath string, entries []string, loader flowkit.Re existingContent := "" filePermissions := os.FileMode(0644) - fileStat, err := os.Stat(filePath) - if !os.IsNotExist(err) { - existingContentRaw, err := loader.ReadFile(filePath) - if err != nil { - return err - } + // Try to read existing content using the loader + existingContentRaw, err := loader.ReadFile(filePath) + if err == nil { existingContent = string(existingContentRaw) - filePermissions = fileStat.Mode().Perm() + // Try to get file permissions, but don't fail if we can't + if stat, err := os.Stat(filePath); err == nil { + filePermissions = stat.Mode().Perm() + } } // Split existing content into lines diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 000000000..7244add8b --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,129 @@ +package util + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddFlowEntriesToGitIgnore_NoDuplicates(t *testing.T) { + _, state, _ := TestMocks(t) + + err := AddFlowEntriesToGitIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to gitignore") + + content, err := state.ReaderWriter().ReadFile(".gitignore") + require.NoError(t, err, "Failed to read gitignore file") + + expectedEntries := []string{"# flow", "emulator-account.pkey", "imports", ".env"} + for _, entry := range expectedEntries { + assert.Contains(t, string(content), entry, "Expected gitignore to contain %s", entry) + } + + err = AddFlowEntriesToGitIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to gitignore again") + + content, err = state.ReaderWriter().ReadFile(".gitignore") + require.NoError(t, err, "Failed to read gitignore file again") + + for _, entry := range expectedEntries { + occurrences := strings.Count(string(content), entry) + assert.Equal(t, 1, occurrences, "Expected 1 occurrence of %s, but found %d", entry, occurrences) + } +} + +func TestAddFlowEntriesToCursorIgnore_NoDuplicates(t *testing.T) { + _, state, _ := TestMocks(t) + + err := AddFlowEntriesToCursorIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to cursorignore") + + content, err := state.ReaderWriter().ReadFile(".cursorignore") + require.NoError(t, err, "Failed to read cursorignore file") + + expectedEntries := []string{"# flow", "emulator-account.pkey", ".env", "# Pay attention to imports directory", "!imports/**"} + for _, entry := range expectedEntries { + assert.Contains(t, string(content), entry, "Expected cursorignore to contain %s", entry) + } + + err = AddFlowEntriesToCursorIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to cursorignore again") + + content, err = state.ReaderWriter().ReadFile(".cursorignore") + require.NoError(t, err, "Failed to read cursorignore file again") + + for _, entry := range expectedEntries { + occurrences := strings.Count(string(content), entry) + assert.Equal(t, 1, occurrences, "Expected 1 occurrence of %s, but found %d", entry, occurrences) + } +} + +func TestAddFlowEntriesToGitIgnore_WithExistingContent(t *testing.T) { + _, state, _ := TestMocks(t) + + existingContent := "# existing content\nnode_modules/\n*.log\n" + err := state.ReaderWriter().WriteFile(".gitignore", []byte(existingContent), 0644) + require.NoError(t, err, "Failed to create existing .gitignore") + + err = AddFlowEntriesToGitIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to gitignore") + + content, err := state.ReaderWriter().ReadFile(".gitignore") + require.NoError(t, err, "Failed to read gitignore file") + + assert.Contains(t, string(content), existingContent, "Expected existing content to be preserved") + + flowEntries := []string{"# flow", "emulator-account.pkey", "imports", ".env"} + for _, entry := range flowEntries { + assert.Contains(t, string(content), entry, "Expected gitignore to contain %s", entry) + } +} + +func TestAddFlowEntriesToCursorIgnore_WithExistingContent(t *testing.T) { + _, state, _ := TestMocks(t) + + existingContent := "# existing cursor ignore\n.vscode/\n.idea/\n" + err := state.ReaderWriter().WriteFile(".cursorignore", []byte(existingContent), 0644) + require.NoError(t, err, "Failed to create existing .cursorignore") + + err = AddFlowEntriesToCursorIgnore("", state.ReaderWriter()) + require.NoError(t, err, "Failed to add Flow entries to cursorignore") + + content, err := state.ReaderWriter().ReadFile(".cursorignore") + require.NoError(t, err, "Failed to read cursorignore file") + + assert.Contains(t, string(content), existingContent, "Expected existing content to be preserved") + + flowEntries := []string{"# flow", "emulator-account.pkey", ".env", "# Pay attention to imports directory", "!imports/**"} + for _, entry := range flowEntries { + assert.Contains(t, string(content), entry, "Expected cursorignore to contain %s", entry) + } +} + +func TestAddEntriesToIgnoreFile_HelperFunction(t *testing.T) { + _, state, _ := TestMocks(t) + + entries := []string{"# test", "test-file.txt", "another-file.log"} + err := addEntriesToIgnoreFile("test-ignore.txt", entries, state.ReaderWriter()) + require.NoError(t, err, "Failed to add entries to ignore file") + + content, err := state.ReaderWriter().ReadFile("test-ignore.txt") + require.NoError(t, err, "Failed to read ignore file") + + for _, entry := range entries { + assert.Contains(t, string(content), entry, "Expected ignore file to contain %s", entry) + } + + err = addEntriesToIgnoreFile("test-ignore.txt", entries, state.ReaderWriter()) + require.NoError(t, err, "Failed to add entries to ignore file again") + + content, err = state.ReaderWriter().ReadFile("test-ignore.txt") + require.NoError(t, err, "Failed to read ignore file again") + + for _, entry := range entries { + occurrences := strings.Count(string(content), entry) + assert.Equal(t, 1, occurrences, "Expected 1 occurrence of %s, but found %d", entry, occurrences) + } +} From d977936a0a7a0fcfefc5032e1190d944feb39290 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:35:38 -0700 Subject: [PATCH 4/6] Use reader writer --- internal/super/setup.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/internal/super/setup.go b/internal/super/setup.go index 55a3e65d4..9442314b6 100644 --- a/internal/super/setup.go +++ b/internal/super/setup.go @@ -107,18 +107,12 @@ func create( return &setupResult{targetDir: targetDir}, nil } -func updateGitignore(targetDir string) error { - rw := afero.Afero{ - Fs: afero.NewOsFs(), - } - return util.AddFlowEntriesToGitIgnore(targetDir, rw) +func updateGitignore(targetDir string, readerWriter flowkit.ReaderWriter) error { + return util.AddFlowEntriesToGitIgnore(targetDir, readerWriter) } -func updateCursorIgnore(targetDir string) error { - rw := afero.Afero{ - Fs: afero.NewOsFs(), - } - return util.AddFlowEntriesToCursorIgnore(targetDir, rw) +func updateCursorIgnore(targetDir string, readerWriter flowkit.ReaderWriter) error { + return util.AddFlowEntriesToCursorIgnore(targetDir, readerWriter) } func createConfigOnly(targetDir string, readerWriter flowkit.ReaderWriter) error { @@ -139,12 +133,12 @@ func createConfigOnly(targetDir string, readerWriter flowkit.ReaderWriter) error return err } - err = updateGitignore(targetDir) + err = updateGitignore(targetDir, readerWriter) if err != nil { return err } - err = updateCursorIgnore(targetDir) + err = updateCursorIgnore(targetDir, readerWriter) if err != nil { return err } @@ -275,12 +269,12 @@ func startInteractiveSetup( return "", err } - err = updateGitignore(tempDir) + err = updateGitignore(tempDir, state.ReaderWriter()) if err != nil { return "", err } - err = updateCursorIgnore(tempDir) + err = updateCursorIgnore(tempDir, state.ReaderWriter()) if err != nil { return "", err } From 3d2bc52611ce833987fe43fae27e6e7ad4f58a26 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:37:11 -0700 Subject: [PATCH 5/6] Add helper function --- internal/util/util.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/internal/util/util.go b/internal/util/util.go index 7fc25aada..ae39723ae 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -45,6 +45,17 @@ func Exit(code int, msg string) { os.Exit(code) } +// entryExists checks if an entry already exists in the content +func entryExists(content, entry string) bool { + lines := strings.Split(strings.TrimSpace(content), "\n") + for _, line := range lines { + if strings.TrimSpace(line) == strings.TrimSpace(entry) { + return true + } + } + return false +} + // AddToGitIgnore adds a new line to the .gitignore if one doesn't exist it creates it. func AddToGitIgnore(filename string, loader flowkit.ReaderWriter) error { currentWd, err := os.Getwd() @@ -65,12 +76,8 @@ func AddToGitIgnore(filename string, loader flowkit.ReaderWriter) error { filePermissions = fileStat.Mode().Perm() } - // Check if the filename already exists in the content - lines := strings.Split(strings.TrimSpace(gitIgnoreFiles), "\n") - for _, line := range lines { - if strings.TrimSpace(line) == strings.TrimSpace(filename) { - return nil // Entry already exists, no need to add - } + if entryExists(gitIgnoreFiles, filename) { + return nil // Entry already exists, no need to add } return loader.WriteFile( @@ -100,12 +107,8 @@ func AddToCursorIgnore(filename string, loader flowkit.ReaderWriter) error { filePermissions = fileStat.Mode().Perm() } - // Check if the filename already exists in the content - lines := strings.Split(strings.TrimSpace(cursorIgnoreFiles), "\n") - for _, line := range lines { - if strings.TrimSpace(line) == strings.TrimSpace(filename) { - return nil // Entry already exists, no need to add - } + if entryExists(cursorIgnoreFiles, filename) { + return nil // Entry already exists, no need to add } return loader.WriteFile( From 08b1344b6eb6ae70031e24373a8f7e3b92f1f839 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:47:05 -0700 Subject: [PATCH 6/6] Add header --- internal/util/util_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 7244add8b..acfadb92c 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -1,3 +1,21 @@ +/* + * Flow CLI + * + * Copyright Flow Foundation + * + * 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 util import (