node-version-use 1.9.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -23
- package/dist/cjs/cli.js +25 -6
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/default.d.cts +7 -0
- package/dist/cjs/commands/default.d.ts +7 -0
- package/dist/cjs/commands/default.js +57 -0
- package/dist/cjs/commands/default.js.map +1 -0
- package/dist/cjs/commands/index.d.cts +3 -0
- package/dist/cjs/commands/index.d.ts +3 -0
- package/dist/cjs/commands/index.js +54 -0
- package/dist/cjs/commands/index.js.map +1 -0
- package/dist/cjs/commands/install.d.cts +6 -0
- package/dist/cjs/commands/install.d.ts +6 -0
- package/dist/cjs/commands/install.js +69 -0
- package/dist/cjs/commands/install.js.map +1 -0
- package/dist/cjs/commands/list.d.cts +6 -0
- package/dist/cjs/commands/list.d.ts +6 -0
- package/dist/cjs/commands/list.js +93 -0
- package/dist/cjs/commands/list.js.map +1 -0
- package/dist/cjs/commands/local.d.cts +7 -0
- package/dist/cjs/commands/local.d.ts +7 -0
- package/dist/cjs/commands/local.js +69 -0
- package/dist/cjs/commands/local.js.map +1 -0
- package/dist/cjs/commands/setup.d.cts +7 -0
- package/dist/cjs/commands/setup.d.ts +7 -0
- package/dist/cjs/commands/setup.js +55 -0
- package/dist/cjs/commands/setup.js.map +1 -0
- package/dist/cjs/commands/teardown.d.cts +6 -0
- package/dist/cjs/commands/teardown.d.ts +6 -0
- package/dist/cjs/commands/teardown.js +66 -0
- package/dist/cjs/commands/teardown.js.map +1 -0
- package/dist/cjs/commands/uninstall.d.cts +6 -0
- package/dist/cjs/commands/uninstall.d.ts +6 -0
- package/dist/cjs/commands/uninstall.js +148 -0
- package/dist/cjs/commands/uninstall.js.map +1 -0
- package/dist/cjs/commands/which.d.cts +7 -0
- package/dist/cjs/commands/which.d.ts +7 -0
- package/dist/cjs/commands/which.js +117 -0
- package/dist/cjs/commands/which.js.map +1 -0
- package/dist/cjs/compat.d.cts +17 -0
- package/dist/cjs/compat.d.ts +17 -0
- package/dist/cjs/compat.js +47 -1
- package/dist/cjs/compat.js.map +1 -1
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/types.d.cts +1 -0
- package/dist/cjs/types.d.ts +1 -0
- package/dist/cjs/worker.js +2 -2
- package/dist/cjs/worker.js.map +1 -1
- package/dist/esm/cli.js +23 -4
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/commands/default.d.ts +7 -0
- package/dist/esm/commands/default.js +41 -0
- package/dist/esm/commands/default.js.map +1 -0
- package/dist/esm/commands/index.d.ts +3 -0
- package/dist/esm/commands/index.js +27 -0
- package/dist/esm/commands/index.js.map +1 -0
- package/dist/esm/commands/install.d.ts +6 -0
- package/dist/esm/commands/install.js +53 -0
- package/dist/esm/commands/install.js.map +1 -0
- package/dist/esm/commands/list.d.ts +6 -0
- package/dist/esm/commands/list.js +52 -0
- package/dist/esm/commands/list.js.map +1 -0
- package/dist/esm/commands/local.d.ts +7 -0
- package/dist/esm/commands/local.js +51 -0
- package/dist/esm/commands/local.js.map +1 -0
- package/dist/esm/commands/setup.d.ts +7 -0
- package/dist/esm/commands/setup.js +39 -0
- package/dist/esm/commands/setup.js.map +1 -0
- package/dist/esm/commands/teardown.d.ts +6 -0
- package/dist/esm/commands/teardown.js +33 -0
- package/dist/esm/commands/teardown.js.map +1 -0
- package/dist/esm/commands/uninstall.d.ts +6 -0
- package/dist/esm/commands/uninstall.js +132 -0
- package/dist/esm/commands/uninstall.js.map +1 -0
- package/dist/esm/commands/which.d.ts +7 -0
- package/dist/esm/commands/which.js +101 -0
- package/dist/esm/commands/which.js.map +1 -0
- package/dist/esm/compat.d.ts +17 -0
- package/dist/esm/compat.js +39 -2
- package/dist/esm/compat.js.map +1 -1
- package/dist/esm/constants.js +2 -1
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/worker.js +2 -2
- package/dist/esm/worker.js.map +1 -1
- package/package.json +11 -4
- package/scripts/postinstall.cjs +273 -0
- package/shim/Makefile +58 -0
- package/shim/go.mod +3 -0
- package/shim/main.go +302 -0
package/shim/main.go
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"runtime"
|
|
9
|
+
"strings"
|
|
10
|
+
"syscall"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// Version resolution priority:
|
|
14
|
+
// 1. .nvurc in current or parent directories
|
|
15
|
+
// 2. .nvmrc in current or parent directories
|
|
16
|
+
// 3. ~/.nvu/default (global default)
|
|
17
|
+
|
|
18
|
+
func main() {
|
|
19
|
+
// Determine which binary we're shimming based on the executable name
|
|
20
|
+
execName := filepath.Base(os.Args[0])
|
|
21
|
+
// Remove .exe suffix on Windows
|
|
22
|
+
execName = strings.TrimSuffix(execName, ".exe")
|
|
23
|
+
|
|
24
|
+
// Check if we're running the nvu CLI itself - if so, use any available version
|
|
25
|
+
// This prevents chicken-and-egg problems where nvu can't run because the
|
|
26
|
+
// configured version isn't installed yet
|
|
27
|
+
isNvuCli := isRunningNvuCli()
|
|
28
|
+
|
|
29
|
+
var version string
|
|
30
|
+
var err error
|
|
31
|
+
|
|
32
|
+
if isNvuCli {
|
|
33
|
+
// For nvu CLI, use any available installed version
|
|
34
|
+
version, err = findAnyInstalledVersion()
|
|
35
|
+
if err != nil {
|
|
36
|
+
fmt.Fprintf(os.Stderr, "nvu shim error: no Node versions installed\n")
|
|
37
|
+
fmt.Fprintf(os.Stderr, "\nInstall Node manually first, or use system Node to run:\n")
|
|
38
|
+
fmt.Fprintf(os.Stderr, " /usr/bin/node $(which nvu) install 20\n")
|
|
39
|
+
os.Exit(1)
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
// Resolve the Node version to use
|
|
43
|
+
version, err = resolveVersion()
|
|
44
|
+
if err != nil {
|
|
45
|
+
fmt.Fprintf(os.Stderr, "nvu shim error: %s\n", err)
|
|
46
|
+
fmt.Fprintf(os.Stderr, "\nTo fix this, either:\n")
|
|
47
|
+
fmt.Fprintf(os.Stderr, " 1. Create a .nvmrc file with a version: echo 20 > .nvmrc\n")
|
|
48
|
+
fmt.Fprintf(os.Stderr, " 2. Set a global default: nvu default 20\n")
|
|
49
|
+
os.Exit(1)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Find the real binary path
|
|
54
|
+
binaryPath, err := findBinary(execName, version)
|
|
55
|
+
if err != nil {
|
|
56
|
+
fmt.Fprintf(os.Stderr, "nvu shim error: %s\n", err)
|
|
57
|
+
fmt.Fprintf(os.Stderr, "\nNode %s may not be installed. Run: nvu install %s\n", version, version)
|
|
58
|
+
os.Exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Execute the real binary, replacing this process
|
|
62
|
+
err = execBinary(binaryPath, os.Args)
|
|
63
|
+
if err != nil {
|
|
64
|
+
fmt.Fprintf(os.Stderr, "nvu shim error: failed to exec %s: %s\n", binaryPath, err)
|
|
65
|
+
os.Exit(1)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// resolveVersion determines which Node version to use
|
|
70
|
+
func resolveVersion() (string, error) {
|
|
71
|
+
// 1. Check for .nvurc or .nvmrc in current directory and parents
|
|
72
|
+
cwd, err := os.Getwd()
|
|
73
|
+
if err != nil {
|
|
74
|
+
return "", fmt.Errorf("failed to get current directory: %w", err)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
version := findVersionInParents(cwd)
|
|
78
|
+
if version != "" {
|
|
79
|
+
return version, nil
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Check global default
|
|
83
|
+
homeDir, err := os.UserHomeDir()
|
|
84
|
+
if err != nil {
|
|
85
|
+
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
defaultPath := filepath.Join(homeDir, ".nvu", "default")
|
|
89
|
+
version, err = readVersionFile(defaultPath)
|
|
90
|
+
if err == nil && version != "" {
|
|
91
|
+
return version, nil
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return "", fmt.Errorf("no Node version configured")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// findVersionInParents walks up the directory tree looking for version config files
|
|
98
|
+
func findVersionInParents(dir string) string {
|
|
99
|
+
for {
|
|
100
|
+
// Check .nvurc first (nvu-specific)
|
|
101
|
+
version, err := readVersionFile(filepath.Join(dir, ".nvurc"))
|
|
102
|
+
if err == nil && version != "" {
|
|
103
|
+
return version
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check .nvmrc (ecosystem compatible)
|
|
107
|
+
version, err = readVersionFile(filepath.Join(dir, ".nvmrc"))
|
|
108
|
+
if err == nil && version != "" {
|
|
109
|
+
return version
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Move to parent directory
|
|
113
|
+
parent := filepath.Dir(dir)
|
|
114
|
+
if parent == dir {
|
|
115
|
+
// Reached root
|
|
116
|
+
break
|
|
117
|
+
}
|
|
118
|
+
dir = parent
|
|
119
|
+
}
|
|
120
|
+
return ""
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// readVersionFile reads a version from a file, trimming whitespace
|
|
124
|
+
func readVersionFile(path string) (string, error) {
|
|
125
|
+
content, err := os.ReadFile(path)
|
|
126
|
+
if err != nil {
|
|
127
|
+
return "", err
|
|
128
|
+
}
|
|
129
|
+
version := strings.TrimSpace(string(content))
|
|
130
|
+
return version, nil
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// findBinary locates the actual binary for the given command and version
|
|
134
|
+
func findBinary(name string, version string) (string, error) {
|
|
135
|
+
homeDir, err := os.UserHomeDir()
|
|
136
|
+
if err != nil {
|
|
137
|
+
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
versionsDir := filepath.Join(homeDir, ".nvu", "versions")
|
|
141
|
+
|
|
142
|
+
// Resolve version to an installed version directory
|
|
143
|
+
resolvedVersion, err := resolveInstalledVersion(versionsDir, version)
|
|
144
|
+
if err != nil {
|
|
145
|
+
return "", err
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The binary should be at ~/.nvu/versions/<version>/bin/<name>
|
|
149
|
+
var binaryPath string
|
|
150
|
+
if runtime.GOOS == "windows" {
|
|
151
|
+
// On Windows, look for .exe or .cmd
|
|
152
|
+
binaryPath = filepath.Join(versionsDir, resolvedVersion, "bin", name+".exe")
|
|
153
|
+
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
|
154
|
+
// Try without bin/ (for node.exe which might be at root)
|
|
155
|
+
binaryPath = filepath.Join(versionsDir, resolvedVersion, name+".exe")
|
|
156
|
+
}
|
|
157
|
+
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
|
158
|
+
// Try .cmd for npm/npx
|
|
159
|
+
binaryPath = filepath.Join(versionsDir, resolvedVersion, name+".cmd")
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
binaryPath = filepath.Join(versionsDir, resolvedVersion, "bin", name)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
|
166
|
+
return "", fmt.Errorf("binary not found: %s", binaryPath)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return binaryPath, nil
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// resolveInstalledVersion finds the best matching installed version
|
|
173
|
+
func resolveInstalledVersion(versionsDir string, version string) (string, error) {
|
|
174
|
+
// Normalize version - remove 'v' prefix for comparison
|
|
175
|
+
normalizedVersion := strings.TrimPrefix(version, "v")
|
|
176
|
+
|
|
177
|
+
// Try exact matches first (with and without 'v' prefix)
|
|
178
|
+
exactMatches := []string{version, "v" + normalizedVersion, normalizedVersion}
|
|
179
|
+
for _, v := range exactMatches {
|
|
180
|
+
path := filepath.Join(versionsDir, v)
|
|
181
|
+
if info, err := os.Stat(path); err == nil && info.IsDir() {
|
|
182
|
+
return v, nil
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If no exact match, scan for partial version match (e.g., "20" matches "v20.19.6")
|
|
187
|
+
entries, err := os.ReadDir(versionsDir)
|
|
188
|
+
if err != nil {
|
|
189
|
+
return "", fmt.Errorf("failed to read versions directory: %w", err)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var bestMatch string
|
|
193
|
+
for _, entry := range entries {
|
|
194
|
+
if !entry.IsDir() {
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
dirName := entry.Name()
|
|
198
|
+
dirVersion := strings.TrimPrefix(dirName, "v")
|
|
199
|
+
|
|
200
|
+
// Check if this version starts with our target
|
|
201
|
+
if strings.HasPrefix(dirVersion, normalizedVersion+".") || dirVersion == normalizedVersion {
|
|
202
|
+
// Prefer higher versions (simple string comparison works for semver)
|
|
203
|
+
if bestMatch == "" || dirName > bestMatch {
|
|
204
|
+
bestMatch = dirName
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if bestMatch != "" {
|
|
210
|
+
return bestMatch, nil
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return "", fmt.Errorf("no installed version matching %s", version)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// execBinary replaces the current process with the target binary
|
|
217
|
+
func execBinary(binaryPath string, args []string) error {
|
|
218
|
+
// On Unix, use syscall.Exec to replace the process
|
|
219
|
+
// On Windows, we need to use exec.Command and wait
|
|
220
|
+
if runtime.GOOS == "windows" {
|
|
221
|
+
return execWindows(binaryPath, args)
|
|
222
|
+
}
|
|
223
|
+
return execUnix(binaryPath, args)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func execUnix(binaryPath string, args []string) error {
|
|
227
|
+
// Replace args[0] with the actual binary path
|
|
228
|
+
args[0] = binaryPath
|
|
229
|
+
return syscall.Exec(binaryPath, args, os.Environ())
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func execWindows(binaryPath string, args []string) error {
|
|
233
|
+
// On Windows, we can't use syscall.Exec, so we spawn and wait
|
|
234
|
+
cmd := exec.Command(binaryPath, args[1:]...)
|
|
235
|
+
cmd.Stdin = os.Stdin
|
|
236
|
+
cmd.Stdout = os.Stdout
|
|
237
|
+
cmd.Stderr = os.Stderr
|
|
238
|
+
cmd.Env = os.Environ()
|
|
239
|
+
|
|
240
|
+
err := cmd.Run()
|
|
241
|
+
if err != nil {
|
|
242
|
+
if exitError, ok := err.(*exec.ExitError); ok {
|
|
243
|
+
os.Exit(exitError.ExitCode())
|
|
244
|
+
}
|
|
245
|
+
return err
|
|
246
|
+
}
|
|
247
|
+
os.Exit(0)
|
|
248
|
+
return nil
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// isRunningNvuCli checks if we're being used to run the nvu CLI
|
|
252
|
+
func isRunningNvuCli() bool {
|
|
253
|
+
if len(os.Args) < 2 {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check if the script being run is the nvu CLI
|
|
258
|
+
script := os.Args[1]
|
|
259
|
+
|
|
260
|
+
// Check for common nvu CLI paths
|
|
261
|
+
if strings.Contains(script, "node-version-use") {
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
if strings.HasSuffix(script, "/nvu") || strings.HasSuffix(script, "\\nvu") {
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
if filepath.Base(script) == "nvu" || filepath.Base(script) == "nvu.js" {
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// findAnyInstalledVersion returns any installed Node version
|
|
275
|
+
func findAnyInstalledVersion() (string, error) {
|
|
276
|
+
homeDir, err := os.UserHomeDir()
|
|
277
|
+
if err != nil {
|
|
278
|
+
return "", err
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
versionsDir := filepath.Join(homeDir, ".nvu", "versions")
|
|
282
|
+
entries, err := os.ReadDir(versionsDir)
|
|
283
|
+
if err != nil {
|
|
284
|
+
return "", err
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Return the first installed version we find
|
|
288
|
+
for _, entry := range entries {
|
|
289
|
+
if entry.IsDir() {
|
|
290
|
+
// Verify it has a node binary
|
|
291
|
+
nodePath := filepath.Join(versionsDir, entry.Name(), "bin", "node")
|
|
292
|
+
if runtime.GOOS == "windows" {
|
|
293
|
+
nodePath = filepath.Join(versionsDir, entry.Name(), "node.exe")
|
|
294
|
+
}
|
|
295
|
+
if _, err := os.Stat(nodePath); err == nil {
|
|
296
|
+
return entry.Name(), nil
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return "", fmt.Errorf("no installed versions found")
|
|
302
|
+
}
|