|
9 | 9 | "strings" |
10 | 10 | "syscall" |
11 | 11 |
|
| 12 | + "nvm/legacy" |
| 13 | + |
12 | 14 | winreg "golang.org/x/sys/windows/registry" |
13 | 15 | ) |
14 | 16 |
|
@@ -88,24 +90,127 @@ func removeLegacyCurrentUserEnv(dataRoot string) error { |
88 | 90 | } |
89 | 91 | defer key.Close() |
90 | 92 |
|
91 | | - nvmHome, _, valueErr := key.GetStringValue("NVM_HOME") |
92 | | - if valueErr != nil { |
93 | | - if valueErr == winreg.ErrNotExist { |
94 | | - return nil |
| 93 | + changed := false |
| 94 | + |
| 95 | + nvmHome, _, homeErr := key.GetStringValue("NVM_HOME") |
| 96 | + if homeErr != nil && homeErr != winreg.ErrNotExist { |
| 97 | + return fmt.Errorf("failed to read current-user NVM_HOME: %w", homeErr) |
| 98 | + } |
| 99 | + nvmSymlink, _, linkErr := key.GetStringValue("NVM_SYMLINK") |
| 100 | + if linkErr != nil && linkErr != winreg.ErrNotExist { |
| 101 | + return fmt.Errorf("failed to read current-user NVM_SYMLINK: %w", linkErr) |
| 102 | + } |
| 103 | + |
| 104 | + forceRemove := map[string]bool{} |
| 105 | + if homeErr == nil && (valueReferencesPath(nvmHome, dataRoot) || looksLikeAuthorNvmHome(nvmHome)) { |
| 106 | + if err := key.DeleteValue("NVM_HOME"); err != nil && err != winreg.ErrNotExist { |
| 107 | + return fmt.Errorf("failed to delete current-user NVM_HOME: %w", err) |
95 | 108 | } |
96 | | - return fmt.Errorf("failed to read current-user NVM_HOME: %w", valueErr) |
| 109 | + forceRemove[normalizePathMatch(nvmHome)] = true |
| 110 | + forceRemove[strings.ToLower("%NVM_HOME%")] = true |
| 111 | + changed = true |
97 | 112 | } |
98 | | - if !valueReferencesPath(nvmHome, dataRoot) { |
99 | | - return nil |
| 113 | + if linkErr == nil && (valueReferencesPath(nvmSymlink, dataRoot) || looksLikeLegacyNvmSymlink(nvmSymlink)) { |
| 114 | + if err := key.DeleteValue("NVM_SYMLINK"); err != nil && err != winreg.ErrNotExist { |
| 115 | + return fmt.Errorf("failed to delete current-user NVM_SYMLINK: %w", err) |
| 116 | + } |
| 117 | + forceRemove[normalizePathMatch(nvmSymlink)] = true |
| 118 | + forceRemove[strings.ToLower("%NVM_SYMLINK%")] = true |
| 119 | + changed = true |
100 | 120 | } |
101 | 121 |
|
102 | | - if err := key.DeleteValue("NVM_HOME"); err != nil && err != winreg.ErrNotExist { |
103 | | - return fmt.Errorf("failed to delete current-user NVM_HOME: %w", err) |
| 122 | + userPath, _, pathErr := key.GetStringValue("Path") |
| 123 | + if pathErr != nil { |
| 124 | + if pathErr == winreg.ErrNotExist { |
| 125 | + if changed { |
| 126 | + legacy.BroadcastEnvironmentChange() |
| 127 | + } |
| 128 | + return nil |
| 129 | + } |
| 130 | + return fmt.Errorf("failed to read current-user Path: %w", pathErr) |
104 | 131 | } |
105 | 132 |
|
| 133 | + // Also strip community program-root PATH entries (keep .nodejs). |
| 134 | + cleaned := filterUserPath(userPath, dataRoot, forceRemove) |
| 135 | + if cleaned != userPath { |
| 136 | + if err := key.SetExpandStringValue("Path", cleaned); err != nil { |
| 137 | + return fmt.Errorf("failed to rewrite current-user Path: %w", err) |
| 138 | + } |
| 139 | + changed = true |
| 140 | + } |
| 141 | + if changed { |
| 142 | + legacy.BroadcastEnvironmentChange() |
| 143 | + } |
106 | 144 | return nil |
107 | 145 | } |
108 | 146 |
|
| 147 | +// RemoveLegacyCurrentUserEnv clears leftover HKCU NVM_HOME/NVM_SYMLINK and community |
| 148 | +// program-root user PATH segments while keeping dataRoot\.nodejs. Used by MSI |
| 149 | +// impersonated install CA and first-launch bootstrap. |
| 150 | +func RemoveLegacyCurrentUserEnv(dataRoot string) error { |
| 151 | + return removeLegacyCurrentUserEnv(dataRoot) |
| 152 | +} |
| 153 | + |
| 154 | +func looksLikeLegacyNvmSymlink(value string) bool { |
| 155 | + norm := normalizePathMatch(value) |
| 156 | + if norm == "" { |
| 157 | + return false |
| 158 | + } |
| 159 | + trimmed := strings.TrimSpace(value) |
| 160 | + return strings.EqualFold(norm, `c:\nodejs`) || |
| 161 | + strings.EqualFold(trimmed, `%NVM_SYMLINK%`) || |
| 162 | + strings.HasSuffix(norm, `\author software\nvm\.link`) || |
| 163 | + strings.HasSuffix(norm, `\author software\nvm\.nodejs`) |
| 164 | +} |
| 165 | + |
| 166 | +func looksLikeAuthorNvmHome(value string) bool { |
| 167 | + norm := normalizePathMatch(value) |
| 168 | + if norm == "" { |
| 169 | + return false |
| 170 | + } |
| 171 | + return strings.HasSuffix(norm, `\author software\nvm`) |
| 172 | +} |
| 173 | + |
| 174 | +// filterUserPath drops legacy NVM segments and the community program root for dataRoot |
| 175 | +// while keeping dataRoot\.nodejs. |
| 176 | +func filterUserPath(userPath, dataRoot string, forceRemove map[string]bool) string { |
| 177 | + if forceRemove == nil { |
| 178 | + forceRemove = map[string]bool{} |
| 179 | + } |
| 180 | + dataNorm := normalizePathMatch(dataRoot) |
| 181 | + nodejsNorm := normalizePathMatch(filepath.Join(dataRoot, ".nodejs")) |
| 182 | + |
| 183 | + segments := strings.Split(userPath, ";") |
| 184 | + kept := make([]string, 0, len(segments)) |
| 185 | + for _, seg := range segments { |
| 186 | + trimmed := strings.TrimSpace(seg) |
| 187 | + if trimmed == "" { |
| 188 | + continue |
| 189 | + } |
| 190 | + norm := normalizePathMatch(trimmed) |
| 191 | + expanded := normalizePathMatch(os.ExpandEnv(trimmed)) |
| 192 | + |
| 193 | + // Keep .nodejs shim path even when NVM_SYMLINK pointed at it. |
| 194 | + if norm == nodejsNorm || expanded == nodejsNorm || |
| 195 | + strings.HasSuffix(norm, `\author software\nvm\.nodejs`) || |
| 196 | + strings.HasSuffix(expanded, `\author software\nvm\.nodejs`) { |
| 197 | + kept = append(kept, seg) |
| 198 | + continue |
| 199 | + } |
| 200 | + if forceRemove[norm] || forceRemove[expanded] { |
| 201 | + continue |
| 202 | + } |
| 203 | + // Drop community program root (data root itself). |
| 204 | + if (dataNorm != "" && (norm == dataNorm || expanded == dataNorm)) || |
| 205 | + strings.HasSuffix(norm, `\author software\nvm`) || |
| 206 | + strings.HasSuffix(expanded, `\author software\nvm`) { |
| 207 | + continue |
| 208 | + } |
| 209 | + kept = append(kept, seg) |
| 210 | + } |
| 211 | + return strings.Join(kept, ";") |
| 212 | +} |
| 213 | + |
109 | 214 | func removeLegacyPath(path string) error { |
110 | 215 | if _, err := os.Lstat(path); err != nil { |
111 | 216 | if os.IsNotExist(err) { |
|
0 commit comments