From b90a857c42fce999caa83b46419b8b57a5d26583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20R=2E=20da=20Silva?= Date: Sun, 30 Aug 2026 01:26:44 -0300 Subject: [PATCH 1/3] ci: configurar pipeline de CI automatizado no GitHub Actions Adiciona workflow de CI no GitHub Actions com matrix multi-OS e multi-versao do Go (1.22.x e 1.23.x). Valida formatacao (gofmt), dependencias, testes com race detector e compilacao dos binarios. Closes #6 --- .github/workflows/ci.yml | 57 ++++++++++++++++++++++++++++++ cmd/envguard/main.go | 1 - go.mod | 2 +- internal/cli/scan.go | 8 ++--- internal/reporter/json.go | 1 - internal/reporter/reporter_test.go | 1 - internal/reporter/terminal.go | 1 - internal/scanner/finding.go | 1 - internal/scanner/scanner_test.go | 12 +++---- 9 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e243bf8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + build-and-test: + name: Test & Build (${{ matrix.os }}, Go ${{ matrix.go-version }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + go-version: ['1.22.x', '1.23.x'] + + defaults: + run: + shell: bash + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + + - name: Check code formatting (gofmt) + run: | + UNFORMATTED=$(gofmt -l .) + if [ -n "$UNFORMATTED" ]; then + echo "Error: The following files are not formatted according to gofmt:" + echo "$UNFORMATTED" + exit 1 + fi + + - name: Verify module dependencies + run: go mod verify + + - name: Run tests with race detector + run: go test -v -race ./... + + - name: Build all packages + run: go build -v ./... + + - name: Build CLI binary + run: go build -v -o ./bin/envguard ./cmd/envguard diff --git a/cmd/envguard/main.go b/cmd/envguard/main.go index d2bf9ce..dfb7458 100644 --- a/cmd/envguard/main.go +++ b/cmd/envguard/main.go @@ -10,4 +10,3 @@ func main() { code := cli.Run(os.Args[1:], os.Stdout, os.Stderr) os.Exit(code) } - diff --git a/go.mod b/go.mod index 3cc4c96..a9b0f8c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/joaooncode/envguard -go 1.27.0 +go 1.22.0 diff --git a/internal/cli/scan.go b/internal/cli/scan.go index 3e77ffe..d40060f 100644 --- a/internal/cli/scan.go +++ b/internal/cli/scan.go @@ -12,10 +12,10 @@ import ( // scanConfig holds the parsed options for scan/check commands. type scanConfig struct { - path string - format string - severity string - noColor bool + path string + format string + severity string + noColor bool } func parseSeverity(s string) (scanner.Severity, bool) { diff --git a/internal/reporter/json.go b/internal/reporter/json.go index c29aadd..a704227 100644 --- a/internal/reporter/json.go +++ b/internal/reporter/json.go @@ -93,4 +93,3 @@ func (j *JSONReporter) Render(result *scanner.Result, w io.Writer) error { return nil } - diff --git a/internal/reporter/reporter_test.go b/internal/reporter/reporter_test.go index 24181f5..e394428 100644 --- a/internal/reporter/reporter_test.go +++ b/internal/reporter/reporter_test.go @@ -293,4 +293,3 @@ func TestJSONReporter_Render(t *testing.T) { } }) } - diff --git a/internal/reporter/terminal.go b/internal/reporter/terminal.go index cb18d9e..96e690c 100644 --- a/internal/reporter/terminal.go +++ b/internal/reporter/terminal.go @@ -156,4 +156,3 @@ func (t *TerminalReporter) severityBadge(sev scanner.Severity) (icon string, bad return t.c.dim("•"), t.c.dim(fmt.Sprintf("[%s]", sevUpper)) } } - diff --git a/internal/scanner/finding.go b/internal/scanner/finding.go index a148aa4..680c100 100644 --- a/internal/scanner/finding.go +++ b/internal/scanner/finding.go @@ -74,4 +74,3 @@ func (r *Result) CalculateSummary() Summary { r.Summary = CalculateSummary(r.Findings) return r.Summary } - diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go index 0175cff..adeda8e 100644 --- a/internal/scanner/scanner_test.go +++ b/internal/scanner/scanner_test.go @@ -368,12 +368,12 @@ type mockGitClient struct { statusFn func(dir, filePath string) (git.FileStatus, error) } -func (m *mockGitClient) IsAvailable() bool { return true } -func (m *mockGitClient) IsGitRepo(dir string) bool { return true } -func (m *mockGitClient) GetRepoRoot(dir string) (string, error) { return dir, nil } -func (m *mockGitClient) IsTracked(dir, filePath string) (bool, error) { return false, nil } -func (m *mockGitClient) IsStaged(dir, filePath string) (bool, error) { return false, nil } -func (m *mockGitClient) IsIgnored(dir, filePath string) (bool, error) { return false, nil } +func (m *mockGitClient) IsAvailable() bool { return true } +func (m *mockGitClient) IsGitRepo(dir string) bool { return true } +func (m *mockGitClient) GetRepoRoot(dir string) (string, error) { return dir, nil } +func (m *mockGitClient) IsTracked(dir, filePath string) (bool, error) { return false, nil } +func (m *mockGitClient) IsStaged(dir, filePath string) (bool, error) { return false, nil } +func (m *mockGitClient) IsIgnored(dir, filePath string) (bool, error) { return false, nil } func (m *mockGitClient) GetFileStatus(dir, filePath string) (git.FileStatus, error) { if m.statusFn != nil { return m.statusFn(dir, filePath) From 2f48c52674433e5abf4754baa586dd0613bc45bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20R=2E=20da=20Silva?= Date: Sun, 30 Aug 2026 01:31:23 -0300 Subject: [PATCH 2/3] fix(git): normalizar caminhos com resolucao de symlinks Ajusta normalizePath e GetRepoRoot para avaliar symlinks e compatibilizar caminhos no macOS e Windows no CI. --- internal/git/exec.go | 30 +++++++++++++++++++++++++++++- internal/git/git.go | 6 +++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/git/exec.go b/internal/git/exec.go index 63ec37a..6e0451e 100644 --- a/internal/git/exec.go +++ b/internal/git/exec.go @@ -65,6 +65,10 @@ func (r *OSCommandRunner) Run(dir string, args ...string) ([]byte, []byte, int, // normalizePath converts a path (relative to dir or absolute) to a slash-delimited relative path to repoRoot. func normalizePath(repoRoot, dir, targetPath string) string { + if evalRoot, err := filepath.EvalSymlinks(repoRoot); err == nil { + repoRoot = evalRoot + } + var fullPath string if filepath.IsAbs(targetPath) { fullPath = targetPath @@ -74,14 +78,38 @@ func normalizePath(repoRoot, dir, targetPath string) string { fullPath = targetPath } + if evalFull, err := filepath.EvalSymlinks(fullPath); err == nil { + fullPath = evalFull + } else { + parent := filepath.Dir(fullPath) + if evalParent, err := filepath.EvalSymlinks(parent); err == nil { + fullPath = filepath.Join(evalParent, filepath.Base(fullPath)) + } + } + if repoRoot != "" { - if rel, err := filepath.Rel(repoRoot, fullPath); err == nil { + repoRootClean := filepath.Clean(repoRoot) + fullPathClean := filepath.Clean(fullPath) + + volRepo := filepath.VolumeName(repoRootClean) + volFull := filepath.VolumeName(fullPathClean) + if volRepo != "" && strings.EqualFold(volRepo, volFull) && volRepo != volFull { + fullPathClean = volRepo + fullPathClean[len(volFull):] + } + + if rel, err := filepath.Rel(repoRootClean, fullPathClean); err == nil && !strings.HasPrefix(rel, "..") { cleanPath := filepath.ToSlash(rel) cleanPath = strings.TrimPrefix(cleanPath, "./") return cleanPath } } + if !filepath.IsAbs(targetPath) { + clean := filepath.ToSlash(filepath.Clean(targetPath)) + clean = strings.TrimPrefix(clean, "./") + return clean + } + cleanPath := filepath.ToSlash(fullPath) cleanPath = strings.TrimPrefix(cleanPath, "./") return cleanPath diff --git a/internal/git/git.go b/internal/git/git.go index a413469..765a72f 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -82,7 +82,11 @@ func (c *GitClient) GetRepoRoot(dir string) (string, error) { if err != nil || exitCode != 0 { return "", fmt.Errorf("failed to get git repository root: %s (exit code %d)", strings.TrimSpace(string(stderr)), exitCode) } - return filepath.Clean(strings.TrimSpace(string(stdout))), nil + root := filepath.Clean(strings.TrimSpace(string(stdout))) + if evalRoot, err := filepath.EvalSymlinks(root); err == nil { + return evalRoot, nil + } + return root, nil } // IsTracked reports whether the file at filePath is tracked in Git. From 0b6a4f92ca72714d2f355e64d17313917c138771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vitor=20R=2E=20da=20Silva?= Date: Sun, 30 Aug 2026 01:33:23 -0300 Subject: [PATCH 3/3] chore(git): adicionar .gitattributes com quebras de linha LF Garante que arquivos Go sejam lidos e salvos com LF no Windows, prevenindo falha de verificacao do gofmt no CI. --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..45891c3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Enforce LF line endings for Go files and repository sources +* text=auto eol=lf +*.go text eol=lf +*.md text eol=lf +*.json text eol=lf +*.yml text eol=lf +*.yaml text eol=lf