Skip to content

Commit a58ac1d

Browse files
committed
feat: add .ddx-version preflight
1 parent 3692259 commit a58ac1d

2 files changed

Lines changed: 201 additions & 0 deletions

File tree

cmd/dun/ddx_version.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
)
10+
11+
func checkDDXVersion(root string) string {
12+
versionPath := filepath.Join(root, ".ddx-version")
13+
raw, err := os.ReadFile(versionPath)
14+
if err != nil {
15+
if os.IsNotExist(err) {
16+
return ""
17+
}
18+
return fmt.Sprintf("warning: failed to read .ddx-version: %v", err)
19+
}
20+
21+
expected := firstVersionLine(string(raw))
22+
if expected == "" {
23+
return "warning: .ddx-version is empty"
24+
}
25+
26+
cacheDir, err := os.UserCacheDir()
27+
if err != nil {
28+
return fmt.Sprintf("warning: unable to locate cache directory: %v", err)
29+
}
30+
31+
libraryDir := filepath.Join(cacheDir, "ddx", "library")
32+
if _, err := os.Stat(libraryDir); err != nil {
33+
if os.IsNotExist(err) {
34+
return "warning: .ddx-version set but ddx library cache not found; run `ddx update`"
35+
}
36+
return fmt.Sprintf("warning: unable to access ddx library cache: %v", err)
37+
}
38+
39+
head, err := gitRevParse(libraryDir, "HEAD")
40+
if err != nil {
41+
return fmt.Sprintf("warning: unable to determine cached ddx library version: %v", err)
42+
}
43+
44+
want, err := gitRevParse(libraryDir, expected)
45+
if err != nil {
46+
return fmt.Sprintf("warning: .ddx-version %q not found in cached ddx library; run `ddx update`", expected)
47+
}
48+
49+
if head != want {
50+
return fmt.Sprintf("warning: .ddx-version %q resolves to %s but cached library is %s; run `ddx update`",
51+
expected, shortSHA(want), shortSHA(head))
52+
}
53+
54+
return ""
55+
}
56+
57+
func firstVersionLine(raw string) string {
58+
for _, line := range strings.Split(raw, "\n") {
59+
trimmed := strings.TrimSpace(line)
60+
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
61+
continue
62+
}
63+
return trimmed
64+
}
65+
return ""
66+
}
67+
68+
func gitRevParse(dir, ref string) (string, error) {
69+
cmd := exec.Command("git", "-C", dir, "rev-parse", ref)
70+
out, err := cmd.Output()
71+
if err != nil {
72+
return "", fmt.Errorf("git rev-parse %s: %w", ref, err)
73+
}
74+
return strings.TrimSpace(string(out)), nil
75+
}
76+
77+
func shortSHA(value string) string {
78+
if len(value) > 12 {
79+
return value[:12]
80+
}
81+
return value
82+
}

cmd/dun/ddx_version_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
)
10+
11+
func TestCheckDDXVersionMissingFile(t *testing.T) {
12+
root := t.TempDir()
13+
if warn := checkDDXVersion(root); warn != "" {
14+
t.Fatalf("expected no warning, got %q", warn)
15+
}
16+
}
17+
18+
func TestCheckDDXVersionCacheMissing(t *testing.T) {
19+
root := t.TempDir()
20+
ddxWriteFile(t, filepath.Join(root, ".ddx-version"), "deadbeef\n")
21+
t.Setenv("XDG_CACHE_HOME", t.TempDir())
22+
23+
warn := checkDDXVersion(root)
24+
if warn == "" || !strings.Contains(warn, "ddx library cache not found") {
25+
t.Fatalf("expected cache warning, got %q", warn)
26+
}
27+
}
28+
29+
func TestCheckDDXVersionMatch(t *testing.T) {
30+
ddxEnsureGit(t)
31+
root := t.TempDir()
32+
33+
cacheRoot := t.TempDir()
34+
t.Setenv("XDG_CACHE_HOME", cacheRoot)
35+
libraryDir := filepath.Join(cacheRoot, "ddx", "library")
36+
commit := ddxInitGitRepo(t, libraryDir, "init")
37+
38+
ddxWriteFile(t, filepath.Join(root, ".ddx-version"), commit+"\n")
39+
40+
if warn := checkDDXVersion(root); warn != "" {
41+
t.Fatalf("expected no warning, got %q", warn)
42+
}
43+
}
44+
45+
func TestCheckDDXVersionMismatch(t *testing.T) {
46+
ddxEnsureGit(t)
47+
root := t.TempDir()
48+
49+
cacheRoot := t.TempDir()
50+
t.Setenv("XDG_CACHE_HOME", cacheRoot)
51+
libraryDir := filepath.Join(cacheRoot, "ddx", "library")
52+
first := ddxInitGitRepo(t, libraryDir, "first")
53+
_ = ddxCommitFile(t, libraryDir, "second.txt", "second")
54+
55+
ddxWriteFile(t, filepath.Join(root, ".ddx-version"), first+"\n")
56+
57+
warn := checkDDXVersion(root)
58+
if warn == "" || !strings.Contains(warn, "resolves to") {
59+
t.Fatalf("expected mismatch warning, got %q", warn)
60+
}
61+
}
62+
63+
func ddxEnsureGit(t *testing.T) {
64+
t.Helper()
65+
if _, err := exec.LookPath("git"); err != nil {
66+
t.Skip("git not available")
67+
}
68+
}
69+
70+
func ddxInitGitRepo(t *testing.T, dir, message string) string {
71+
t.Helper()
72+
if err := os.MkdirAll(dir, 0755); err != nil {
73+
t.Fatalf("mkdir: %v", err)
74+
}
75+
ddxRunGit(t, dir, "init")
76+
ddxRunGit(t, dir, "config", "user.email", "test@example.com")
77+
ddxRunGit(t, dir, "config", "user.name", "Test")
78+
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("init\n"), 0644); err != nil {
79+
t.Fatalf("write file: %v", err)
80+
}
81+
ddxRunGit(t, dir, "add", ".")
82+
ddxRunGit(t, dir, "commit", "-m", message)
83+
return strings.TrimSpace(ddxRunGitOutput(t, dir, "rev-parse", "HEAD"))
84+
}
85+
86+
func ddxCommitFile(t *testing.T, dir, name, message string) string {
87+
t.Helper()
88+
if err := os.WriteFile(filepath.Join(dir, name), []byte("change\n"), 0644); err != nil {
89+
t.Fatalf("write file: %v", err)
90+
}
91+
ddxRunGit(t, dir, "add", name)
92+
ddxRunGit(t, dir, "commit", "-m", message)
93+
return strings.TrimSpace(ddxRunGitOutput(t, dir, "rev-parse", "HEAD"))
94+
}
95+
96+
func ddxRunGit(t *testing.T, dir string, args ...string) {
97+
t.Helper()
98+
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
99+
if out, err := cmd.CombinedOutput(); err != nil {
100+
t.Fatalf("git %s failed: %v (%s)", strings.Join(args, " "), err, string(out))
101+
}
102+
}
103+
104+
func ddxRunGitOutput(t *testing.T, dir string, args ...string) string {
105+
t.Helper()
106+
cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
107+
out, err := cmd.Output()
108+
if err != nil {
109+
t.Fatalf("git %s failed: %v", strings.Join(args, " "), err)
110+
}
111+
return string(out)
112+
}
113+
114+
func ddxWriteFile(t *testing.T, path, content string) {
115+
t.Helper()
116+
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
117+
t.Fatalf("write file: %v", err)
118+
}
119+
}

0 commit comments

Comments
 (0)