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) + } + }) +}