Skip to content

Commit 369363e

Browse files
authored
Sort action pins JSON entries alphabetically by key (#3974)
1 parent 5aeb3ee commit 369363e

4 files changed

Lines changed: 118 additions & 6 deletions

File tree

.github/workflows/super-linter.lock.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/workflow/action_cache.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"os"
66
"path/filepath"
7+
"sort"
78

89
"github.com/githubnext/gh-aw/pkg/logger"
910
)
@@ -61,7 +62,7 @@ func (c *ActionCache) Load() error {
6162
return nil
6263
}
6364

64-
// Save saves the cache to disk
65+
// Save saves the cache to disk with sorted entries
6566
func (c *ActionCache) Save() error {
6667
actionCacheLog.Printf("Saving action cache to: %s with %d entries", c.path, len(c.Entries))
6768

@@ -72,7 +73,8 @@ func (c *ActionCache) Save() error {
7273
return err
7374
}
7475

75-
data, err := json.MarshalIndent(c, "", " ")
76+
// Marshal with sorted entries
77+
data, err := c.marshalSorted()
7678
if err != nil {
7779
actionCacheLog.Printf("Failed to marshal cache data: %v", err)
7880
return err
@@ -90,6 +92,43 @@ func (c *ActionCache) Save() error {
9092
return nil
9193
}
9294

95+
// marshalSorted marshals the cache with entries sorted by key
96+
func (c *ActionCache) marshalSorted() ([]byte, error) {
97+
// Extract and sort the keys
98+
keys := make([]string, 0, len(c.Entries))
99+
for key := range c.Entries {
100+
keys = append(keys, key)
101+
}
102+
sort.Strings(keys)
103+
104+
// Manually construct JSON with sorted keys
105+
var result []byte
106+
result = append(result, []byte("{\n \"entries\": {\n")...)
107+
108+
for i, key := range keys {
109+
entry := c.Entries[key]
110+
111+
// Marshal the entry
112+
entryJSON, err := json.MarshalIndent(entry, " ", " ")
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
// Add the key and entry
118+
result = append(result, []byte(" \""+key+"\": ")...)
119+
result = append(result, entryJSON...)
120+
121+
// Add comma if not the last entry
122+
if i < len(keys)-1 {
123+
result = append(result, ',')
124+
}
125+
result = append(result, '\n')
126+
}
127+
128+
result = append(result, []byte(" }\n}")...)
129+
return result, nil
130+
}
131+
93132
// Get retrieves a cached entry if it exists
94133
func (c *ActionCache) Get(repo, version string) (string, bool) {
95134
key := repo + "@" + version

pkg/workflow/action_cache_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package workflow
22

33
import (
4+
"encoding/json"
45
"os"
56
"path/filepath"
67
"testing"
@@ -124,3 +125,75 @@ func TestActionCacheTrailingNewline(t *testing.T) {
124125
t.Error("Cache file should end with a trailing newline for prettier compliance")
125126
}
126127
}
128+
129+
func TestActionCacheSortedEntries(t *testing.T) {
130+
// Create temporary directory for testing
131+
tmpDir := t.TempDir()
132+
133+
// Create cache and add entries in non-alphabetical order
134+
cache := NewActionCache(tmpDir)
135+
cache.Set("zzz/last-action", "v1", "sha111")
136+
cache.Set("actions/checkout", "v5", "sha222")
137+
cache.Set("mmm/middle-action", "v2", "sha333")
138+
cache.Set("actions/setup-node", "v4", "sha444")
139+
cache.Set("aaa/first-action", "v3", "sha555")
140+
141+
// Save to disk
142+
err := cache.Save()
143+
if err != nil {
144+
t.Fatalf("Failed to save cache: %v", err)
145+
}
146+
147+
// Read the file content
148+
cachePath := filepath.Join(tmpDir, ".github", "aw", CacheFileName)
149+
data, err := os.ReadFile(cachePath)
150+
if err != nil {
151+
t.Fatalf("Failed to read cache file: %v", err)
152+
}
153+
154+
content := string(data)
155+
156+
// Verify that entries appear in alphabetical order by checking their positions
157+
entries := []string{
158+
"aaa/first-action@v3",
159+
"actions/checkout@v5",
160+
"actions/setup-node@v4",
161+
"mmm/middle-action@v2",
162+
"zzz/last-action@v1",
163+
}
164+
165+
lastPos := -1
166+
for _, entry := range entries {
167+
pos := indexOf(content, entry)
168+
if pos == -1 {
169+
t.Errorf("Entry %s not found in cache file", entry)
170+
continue
171+
}
172+
if pos < lastPos {
173+
t.Errorf("Entry %s appears before previous entry (not sorted)", entry)
174+
}
175+
lastPos = pos
176+
}
177+
178+
// Also verify the file is valid JSON
179+
var loadedCache ActionCache
180+
err = json.Unmarshal(data, &loadedCache)
181+
if err != nil {
182+
t.Fatalf("Saved cache is not valid JSON: %v", err)
183+
}
184+
185+
// Verify all entries are present
186+
if len(loadedCache.Entries) != 5 {
187+
t.Errorf("Expected 5 entries, got %d", len(loadedCache.Entries))
188+
}
189+
}
190+
191+
// indexOf returns the index of substr in s, or -1 if not found
192+
func indexOf(s, substr string) int {
193+
for i := 0; i <= len(s)-len(substr); i++ {
194+
if s[i:i+len(substr)] == substr {
195+
return i
196+
}
197+
}
198+
return -1
199+
}

pkg/workflow/action_pins_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,9 @@ func TestApplyActionPinToStep(t *testing.T) {
345345
func TestGetActionPinsSorting(t *testing.T) {
346346
pins := getActionPins()
347347

348-
// Verify we got all the pins (should be 18)
349-
if len(pins) != 18 {
350-
t.Errorf("getActionPins() returned %d pins, expected 18", len(pins))
348+
// Verify we got all the pins (should be 20)
349+
if len(pins) != 20 {
350+
t.Errorf("getActionPins() returned %d pins, expected 20", len(pins))
351351
}
352352

353353
// Verify they are sorted by version (descending) then by repository name (ascending)

0 commit comments

Comments
 (0)