|
1 | 1 | package sync |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "encoding/json" |
4 | 5 | "path/filepath" |
5 | 6 | "strings" |
6 | 7 | ) |
@@ -29,33 +30,76 @@ func NormalizePathsInJSON(data []byte, claudeDir string) []byte { |
29 | 30 |
|
30 | 31 | // ExpandPathsInJSON replaces the cross-platform placeholder with the local ClaudeDir path. |
31 | 32 | // The expanded path uses the native format for the current platform. |
| 33 | +// Uses JSON parsing to safely handle escape sequences. |
32 | 34 | func ExpandPathsInJSON(data []byte, claudeDir string) []byte { |
| 35 | + // First, parse as JSON to get the structure |
| 36 | + var obj interface{} |
| 37 | + if err := json.Unmarshal(data, &obj); err != nil { |
| 38 | + // If not valid JSON, fall back to string replacement |
| 39 | + return fallbackExpandPaths(data, claudeDir) |
| 40 | + } |
| 41 | + |
| 42 | + // Recursively replace placeholders in the parsed object |
| 43 | + expanded := expandInObject(obj, claudeDir) |
| 44 | + |
| 45 | + // Marshal back to JSON with proper formatting |
| 46 | + result, err := json.MarshalIndent(expanded, "", " ") |
| 47 | + if err != nil { |
| 48 | + // If marshaling fails, fall back |
| 49 | + return fallbackExpandPaths(data, claudeDir) |
| 50 | + } |
| 51 | + |
| 52 | + return result |
| 53 | +} |
| 54 | + |
| 55 | +// expandInObject recursively expands placeholders in JSON objects |
| 56 | +func expandInObject(obj interface{}, claudeDir string) interface{} { |
| 57 | + switch v := obj.(type) { |
| 58 | + case map[string]interface{}: |
| 59 | + for k, val := range v { |
| 60 | + v[k] = expandInObject(val, claudeDir) |
| 61 | + } |
| 62 | + return v |
| 63 | + case []interface{}: |
| 64 | + for i, val := range v { |
| 65 | + v[i] = expandInObject(val, claudeDir) |
| 66 | + } |
| 67 | + return v |
| 68 | + case string: |
| 69 | + if strings.Contains(v, ClaudeDirPlaceholder) { |
| 70 | + // Replace placeholder with local path |
| 71 | + expanded := strings.ReplaceAll(v, ClaudeDirPlaceholder, claudeDir) |
| 72 | + |
| 73 | + // On Unix systems, convert backslashes to forward slashes in paths |
| 74 | + if !strings.Contains(claudeDir, `\`) { |
| 75 | + // This is Unix - convert Windows-style backslashes to forward slashes |
| 76 | + // But only convert path separators, not escape sequences |
| 77 | + // In JSON strings, \\ is already unescaped to \, so we just replace \ |
| 78 | + expanded = strings.ReplaceAll(expanded, `\`, `/`) |
| 79 | + } |
| 80 | + |
| 81 | + return expanded |
| 82 | + } |
| 83 | + return v |
| 84 | + default: |
| 85 | + return v |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +// fallbackExpandPaths is a safe string-based fallback that only replaces in quoted strings |
| 90 | +func fallbackExpandPaths(data []byte, claudeDir string) []byte { |
33 | 91 | content := string(data) |
34 | 92 |
|
35 | 93 | // For JSON files, we need to use escaped backslashes on Windows |
36 | | - // Check if we're on Windows by looking for backslashes in claudeDir |
37 | 94 | if strings.Contains(claudeDir, `\`) { |
38 | 95 | // Windows: use escaped backslashes for JSON |
39 | 96 | escapedClaudeDir := strings.ReplaceAll(claudeDir, `\`, `\\`) |
40 | 97 | content = strings.ReplaceAll(content, ClaudeDirPlaceholder, escapedClaudeDir) |
41 | 98 | } else { |
42 | | - // Unix: replace placeholder first |
43 | | - content = strings.ReplaceAll(content, ClaudeDirPlaceholder, claudeDir) |
44 | | - |
45 | | - // Convert path separators: replace \\ with / on lines that now contain the expanded path |
46 | | - lines := strings.Split(content, "\n") |
47 | | - for i, line := range lines { |
48 | | - if strings.Contains(line, claudeDir) { |
49 | | - // This line now contains the expanded path with backslashes |
50 | | - // Replace \\ (escaped backslash in JSON = one actual backslash) with / |
51 | | - // Go string literal: `\\` is actually two characters (backslash, backslash) in the source |
52 | | - // When applied to JSON content, "C:\\Users" becomes /home/ubuntu/.claude\plugins\... |
53 | | - // And we want to convert \plugins to /plugins |
54 | | - line = strings.ReplaceAll(line, `\`, `/`) |
55 | | - lines[i] = line |
56 | | - } |
57 | | - } |
58 | | - content = strings.Join(lines, "\n") |
| 99 | + // Unix: replace placeholder with forward-slash path |
| 100 | + // This is safer than replacing all backslashes |
| 101 | + normalizedPath := filepath.ToSlash(claudeDir) // ensure forward slashes |
| 102 | + content = strings.ReplaceAll(content, ClaudeDirPlaceholder, normalizedPath) |
59 | 103 | } |
60 | 104 |
|
61 | 105 | return []byte(content) |
|
0 commit comments