From ddcef16cff911abcb324bd77a896a6d2f72a3500 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:09:14 +0000 Subject: [PATCH] fix(#1768): remove stale .adoc files before doc generation GenerateAsciidoc() only created/overwrote .adoc files for currently existing packages but never removed files for deleted packages. This caused stale documentation to persist in the repo and published docs. Add removeAdocFiles() helper that clears all .adoc files from the packages/ directory before regenerating. Since everything in that directory is generated content, this is safe and simpler than a marker-based approach. Add unit tests covering removal of .adoc files, preservation of non-.adoc files and subdirectories, and graceful handling of nonexistent or empty directories. Closes #1768 --- docs/asciidoc/asciidoc.go | 28 ++++++++++ docs/asciidoc/asciidoc_test.go | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/docs/asciidoc/asciidoc.go b/docs/asciidoc/asciidoc.go index 32729894b..4b63bd188 100644 --- a/docs/asciidoc/asciidoc.go +++ b/docs/asciidoc/asciidoc.go @@ -362,12 +362,40 @@ func inspect(rego []string) ([]ast.FlatAnnotationsRefSet, error) { return annotations, nil } +// removeAdocFiles removes all .adoc files from the given directory. +// If the directory does not exist, it returns nil. +func removeAdocFiles(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("reading directory %q: %w", dir, err) + } + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".adoc") { + if err := os.Remove(filepath.Join(dir, entry.Name())); err != nil { + return fmt.Errorf("removing file %q: %w", entry.Name(), err) + } + } + } + return nil +} + func GenerateAsciidoc(module string, rego ...string) error { annotations, err := inspect(rego) if err != nil { return err } + // Remove all .adoc files from the packages directory before + // regenerating. Everything in this directory is generated, so + // removing stale files from deleted packages is safe. + packagesDir := filepath.Join(module, "pages", "packages") + if err := removeAdocFiles(packagesDir); err != nil { + return err + } + for _, d := range docs { d.SetAnnotations(annotations) if err := d.generateNav(module); err != nil { diff --git a/docs/asciidoc/asciidoc_test.go b/docs/asciidoc/asciidoc_test.go index d5b6b1238..885e9b184 100644 --- a/docs/asciidoc/asciidoc_test.go +++ b/docs/asciidoc/asciidoc_test.go @@ -17,6 +17,8 @@ package asciidoc import ( + "os" + "path/filepath" "testing" "time" ) @@ -151,3 +153,94 @@ func TestFormatTimeRoundTrip(t *testing.T) { t.Errorf("Round-trip failed: original %v != parsed %v", original, parsed) } } + +func TestRemoveAdocFiles(t *testing.T) { + t.Run("removes .adoc files", func(t *testing.T) { + dir := t.TempDir() + + // Create .adoc files + for _, name := range []string{"release_foo.adoc", "pipeline_bar.adoc"} { + if err := os.WriteFile(filepath.Join(dir, name), []byte("content"), 0644); err != nil { + t.Fatal(err) + } + } + + if err := removeAdocFiles(dir); err != nil { + t.Fatalf("removeAdocFiles() returned error: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Errorf("expected empty directory, got %d entries", len(entries)) + } + }) + + t.Run("preserves non-adoc files", func(t *testing.T) { + dir := t.TempDir() + + // Create a mix of files + if err := os.WriteFile(filepath.Join(dir, "stale.adoc"), []byte("stale"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("keep"), 0644); err != nil { + t.Fatal(err) + } + + if err := removeAdocFiles(dir); err != nil { + t.Fatalf("removeAdocFiles() returned error: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if len(entries) != 1 { + t.Fatalf("expected 1 entry, got %d", len(entries)) + } + if entries[0].Name() != "keep.txt" { + t.Errorf("expected keep.txt, got %s", entries[0].Name()) + } + }) + + t.Run("preserves subdirectories", func(t *testing.T) { + dir := t.TempDir() + + subDir := filepath.Join(dir, "subdir") + if err := os.Mkdir(subDir, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "stale.adoc"), []byte("stale"), 0644); err != nil { + t.Fatal(err) + } + + if err := removeAdocFiles(dir); err != nil { + t.Fatalf("removeAdocFiles() returned error: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if len(entries) != 1 { + t.Fatalf("expected 1 entry, got %d", len(entries)) + } + if entries[0].Name() != "subdir" || !entries[0].IsDir() { + t.Errorf("expected subdir directory, got %s", entries[0].Name()) + } + }) + + t.Run("nonexistent directory is not an error", func(t *testing.T) { + if err := removeAdocFiles(filepath.Join(t.TempDir(), "nonexistent")); err != nil { + t.Fatalf("removeAdocFiles() returned error for nonexistent dir: %v", err) + } + }) + + t.Run("empty directory is not an error", func(t *testing.T) { + if err := removeAdocFiles(t.TempDir()); err != nil { + t.Fatalf("removeAdocFiles() returned error for empty dir: %v", err) + } + }) +}