node-version-use 1.9.9 → 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/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/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
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postinstall script for node-version-use
|
|
3
|
+
*
|
|
4
|
+
* Downloads the platform-specific shim binary and installs it to ~/.nvu/bin/
|
|
5
|
+
* This enables transparent Node version switching via the shim.
|
|
6
|
+
*
|
|
7
|
+
* Compatible with Node.js 0.8+
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var fs = require('fs');
|
|
11
|
+
var path = require('path');
|
|
12
|
+
var os = require('os');
|
|
13
|
+
var exit = require('exit');
|
|
14
|
+
var getRemote = require('get-remote');
|
|
15
|
+
|
|
16
|
+
// Polyfills for old Node versions
|
|
17
|
+
var mkdirp = require('mkdirp-classic');
|
|
18
|
+
var homedir = typeof os.homedir === 'function' ? os.homedir() : require('homedir-polyfill')();
|
|
19
|
+
|
|
20
|
+
// execSync doesn't exist in Node 0.8, use spawn
|
|
21
|
+
var spawn = require('child_process').spawn;
|
|
22
|
+
|
|
23
|
+
// Configuration
|
|
24
|
+
var GITHUB_REPO = 'kmalakoff/node-version-use';
|
|
25
|
+
var SHIM_VERSION = '1.0.2';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the platform-specific binary name
|
|
29
|
+
*/
|
|
30
|
+
function getShimBinaryName() {
|
|
31
|
+
var platform = os.platform();
|
|
32
|
+
var arch = os.arch();
|
|
33
|
+
|
|
34
|
+
var platformMap = {
|
|
35
|
+
darwin: 'darwin',
|
|
36
|
+
linux: 'linux',
|
|
37
|
+
win32: 'win32',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
var archMap = {
|
|
41
|
+
x64: 'x64',
|
|
42
|
+
arm64: 'arm64',
|
|
43
|
+
amd64: 'x64',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
var platformName = platformMap[platform];
|
|
47
|
+
var archName = archMap[arch];
|
|
48
|
+
|
|
49
|
+
if (!platformName || !archName) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var ext = platform === 'win32' ? '.exe' : '';
|
|
54
|
+
return 'nvu-shim-' + platformName + '-' + archName + ext;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the download URL for the shim binary
|
|
59
|
+
*/
|
|
60
|
+
function getDownloadUrl(binaryName) {
|
|
61
|
+
var ext = os.platform() === 'win32' ? '.zip' : '.tar.gz';
|
|
62
|
+
return 'https://github.com/' + GITHUB_REPO + '/releases/download/shim-v' + SHIM_VERSION + '/' + binaryName + ext;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Copy file (compatible with Node 0.8)
|
|
67
|
+
*/
|
|
68
|
+
function copyFileSync(src, dest) {
|
|
69
|
+
var content = fs.readFileSync(src);
|
|
70
|
+
fs.writeFileSync(dest, content);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Download a file from a URL (using get-remote for Node 0.8+ compatibility)
|
|
75
|
+
*/
|
|
76
|
+
function downloadFile(url, destPath, callback) {
|
|
77
|
+
var writeStream = fs.createWriteStream(destPath);
|
|
78
|
+
getRemote(url).pipe(writeStream, callback);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract archive and install shims (callback-based)
|
|
83
|
+
*/
|
|
84
|
+
function extractAndInstall(archivePath, destDir, binaryName, callback) {
|
|
85
|
+
var platform = os.platform();
|
|
86
|
+
|
|
87
|
+
if (platform === 'win32') {
|
|
88
|
+
// Windows: extract zip using PowerShell
|
|
89
|
+
var ps = spawn('powershell', ['-Command', "Expand-Archive -Path '" + archivePath + "' -DestinationPath '" + destDir + "' -Force"]);
|
|
90
|
+
ps.on('close', function (code) {
|
|
91
|
+
if (code !== 0) {
|
|
92
|
+
callback(new Error('Failed to extract archive'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
var extractedPath = path.join(destDir, binaryName);
|
|
96
|
+
if (fs.existsSync(extractedPath)) {
|
|
97
|
+
try {
|
|
98
|
+
copyFileSync(extractedPath, path.join(destDir, 'node.exe'));
|
|
99
|
+
copyFileSync(extractedPath, path.join(destDir, 'npm.exe'));
|
|
100
|
+
copyFileSync(extractedPath, path.join(destDir, 'npx.exe'));
|
|
101
|
+
fs.unlinkSync(extractedPath);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
callback(err);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
callback(null);
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
// Unix: extract tar.gz
|
|
111
|
+
var tar = spawn('tar', ['-xzf', archivePath, '-C', destDir]);
|
|
112
|
+
tar.on('close', function (code) {
|
|
113
|
+
if (code !== 0) {
|
|
114
|
+
callback(new Error('Failed to extract archive'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
var extractedPath = path.join(destDir, binaryName);
|
|
118
|
+
if (fs.existsSync(extractedPath)) {
|
|
119
|
+
var nodePath = path.join(destDir, 'node');
|
|
120
|
+
var npmPath = path.join(destDir, 'npm');
|
|
121
|
+
var npxPath = path.join(destDir, 'npx');
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
copyFileSync(extractedPath, nodePath);
|
|
125
|
+
copyFileSync(extractedPath, npmPath);
|
|
126
|
+
copyFileSync(extractedPath, npxPath);
|
|
127
|
+
|
|
128
|
+
fs.chmodSync(nodePath, 493); // 0755
|
|
129
|
+
fs.chmodSync(npmPath, 493);
|
|
130
|
+
fs.chmodSync(npxPath, 493);
|
|
131
|
+
|
|
132
|
+
fs.unlinkSync(extractedPath);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
callback(err);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
callback(null);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Print setup instructions
|
|
145
|
+
*/
|
|
146
|
+
function printInstructions(installed) {
|
|
147
|
+
var nvuBinPath = path.join(homedir, '.nvu', 'bin');
|
|
148
|
+
var platform = os.platform();
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log('============================================================');
|
|
152
|
+
if (installed) {
|
|
153
|
+
console.log(' nvu shims installed to ~/.nvu/bin/');
|
|
154
|
+
} else {
|
|
155
|
+
console.log(' nvu installed (shims not yet available)');
|
|
156
|
+
}
|
|
157
|
+
console.log('============================================================');
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log('To enable transparent Node version switching, add to your shell profile:');
|
|
160
|
+
console.log('');
|
|
161
|
+
|
|
162
|
+
if (platform === 'win32') {
|
|
163
|
+
console.log(' PowerShell (add to $PROFILE):');
|
|
164
|
+
console.log(' $env:PATH = "' + nvuBinPath + ';$env:PATH"');
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log(' CMD (run as administrator):');
|
|
167
|
+
console.log(' setx PATH "' + nvuBinPath + ';%PATH%"');
|
|
168
|
+
} else {
|
|
169
|
+
console.log(' # For bash (~/.bashrc):');
|
|
170
|
+
console.log(' export PATH="$HOME/.nvu/bin:$PATH"');
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log(' # For zsh (~/.zshrc):');
|
|
173
|
+
console.log(' export PATH="$HOME/.nvu/bin:$PATH"');
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(' # For fish (~/.config/fish/config.fish):');
|
|
176
|
+
console.log(' set -gx PATH $HOME/.nvu/bin $PATH');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('Then restart your terminal or source your shell profile.');
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log("Without this, 'nvu 18 npm test' still works - you just won't have");
|
|
183
|
+
console.log("transparent 'node' command override.");
|
|
184
|
+
console.log('============================================================');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get temp directory (compatible with Node 0.8)
|
|
189
|
+
*/
|
|
190
|
+
function getTmpDir() {
|
|
191
|
+
return typeof os.tmpdir === 'function' ? os.tmpdir() : process.env.TMPDIR || process.env.TMP || process.env.TEMP || '/tmp';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Main installation function
|
|
196
|
+
*/
|
|
197
|
+
function main() {
|
|
198
|
+
var binaryName = getShimBinaryName();
|
|
199
|
+
|
|
200
|
+
if (!binaryName) {
|
|
201
|
+
console.log('postinstall: Unsupported platform/architecture for shim binary.');
|
|
202
|
+
console.log('Platform: ' + os.platform() + ', Arch: ' + os.arch());
|
|
203
|
+
console.log('Shim not installed. You can still use nvu with explicit versions: nvu 18 npm test');
|
|
204
|
+
exit(0);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
var nvuDir = path.join(homedir, '.nvu');
|
|
209
|
+
var binDir = path.join(nvuDir, 'bin');
|
|
210
|
+
|
|
211
|
+
// Create directories
|
|
212
|
+
mkdirp.sync(nvuDir);
|
|
213
|
+
mkdirp.sync(binDir);
|
|
214
|
+
|
|
215
|
+
var downloadUrl = getDownloadUrl(binaryName);
|
|
216
|
+
var ext = os.platform() === 'win32' ? '.zip' : '.tar.gz';
|
|
217
|
+
var tempPath = path.join(getTmpDir(), 'nvu-shim-' + Date.now() + ext);
|
|
218
|
+
|
|
219
|
+
console.log('postinstall: Downloading shim binary for ' + os.platform() + '-' + os.arch() + '...');
|
|
220
|
+
|
|
221
|
+
downloadFile(downloadUrl, tempPath, function (downloadErr) {
|
|
222
|
+
if (downloadErr) {
|
|
223
|
+
// Clean up temp file if it exists
|
|
224
|
+
if (fs.existsSync(tempPath)) {
|
|
225
|
+
try {
|
|
226
|
+
fs.unlinkSync(tempPath);
|
|
227
|
+
} catch (_e) {}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (downloadErr.message && downloadErr.message.indexOf('404') >= 0) {
|
|
231
|
+
console.log('postinstall: Shim binaries not yet published to GitHub releases.');
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log('To build and install shims locally:');
|
|
234
|
+
console.log(' cd node_modules/node-version-use/shim');
|
|
235
|
+
console.log(' make install');
|
|
236
|
+
console.log('');
|
|
237
|
+
console.log('Or wait for the next release which will include pre-built binaries.');
|
|
238
|
+
} else {
|
|
239
|
+
console.log('postinstall warning: Failed to install shim: ' + (downloadErr.message || downloadErr));
|
|
240
|
+
console.log('You can still use nvu with explicit versions: nvu 18 npm test');
|
|
241
|
+
console.log('To install shims manually: cd node_modules/node-version-use/shim && make install');
|
|
242
|
+
}
|
|
243
|
+
printInstructions(false);
|
|
244
|
+
exit(0);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('postinstall: Extracting shim binary...');
|
|
249
|
+
|
|
250
|
+
extractAndInstall(tempPath, binDir, binaryName, function (extractErr) {
|
|
251
|
+
// Clean up temp file
|
|
252
|
+
if (fs.existsSync(tempPath)) {
|
|
253
|
+
try {
|
|
254
|
+
fs.unlinkSync(tempPath);
|
|
255
|
+
} catch (_e) {}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (extractErr) {
|
|
259
|
+
console.log('postinstall warning: Failed to extract shim: ' + (extractErr.message || extractErr));
|
|
260
|
+
console.log('You can still use nvu with explicit versions: nvu 18 npm test');
|
|
261
|
+
printInstructions(false);
|
|
262
|
+
exit(0);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log('postinstall: Shim installed successfully!');
|
|
267
|
+
printInstructions(true);
|
|
268
|
+
exit(0);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
main();
|
package/shim/Makefile
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Makefile for building nvu shim binaries for all platforms
|
|
2
|
+
|
|
3
|
+
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
|
4
|
+
BUILD_DIR := build
|
|
5
|
+
BINARY_NAME := nvu-shim
|
|
6
|
+
|
|
7
|
+
# Build flags for smaller binaries
|
|
8
|
+
LDFLAGS := -s -w -X main.version=$(VERSION)
|
|
9
|
+
|
|
10
|
+
.PHONY: all clean darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64 windows-arm64 local install release
|
|
11
|
+
|
|
12
|
+
all: darwin-arm64 darwin-x64 linux-x64 linux-arm64 windows-x64 windows-arm64
|
|
13
|
+
|
|
14
|
+
$(BUILD_DIR):
|
|
15
|
+
mkdir -p $(BUILD_DIR)
|
|
16
|
+
|
|
17
|
+
darwin-arm64: $(BUILD_DIR)
|
|
18
|
+
GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 .
|
|
19
|
+
|
|
20
|
+
darwin-x64: $(BUILD_DIR)
|
|
21
|
+
GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-x64 .
|
|
22
|
+
|
|
23
|
+
linux-x64: $(BUILD_DIR)
|
|
24
|
+
GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-x64 .
|
|
25
|
+
|
|
26
|
+
linux-arm64: $(BUILD_DIR)
|
|
27
|
+
GOOS=linux GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 .
|
|
28
|
+
|
|
29
|
+
windows-x64: $(BUILD_DIR)
|
|
30
|
+
GOOS=windows GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-win32-x64.exe .
|
|
31
|
+
|
|
32
|
+
windows-arm64: $(BUILD_DIR)
|
|
33
|
+
GOOS=windows GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME)-win32-arm64.exe .
|
|
34
|
+
|
|
35
|
+
# Build for current platform (for local testing)
|
|
36
|
+
local: $(BUILD_DIR)
|
|
37
|
+
go build -ldflags="$(LDFLAGS)" -o $(BUILD_DIR)/$(BINARY_NAME) .
|
|
38
|
+
|
|
39
|
+
# Install shims to ~/.nvu/bin for local testing
|
|
40
|
+
install: local
|
|
41
|
+
mkdir -p $(HOME)/.nvu/bin
|
|
42
|
+
cp $(BUILD_DIR)/$(BINARY_NAME) $(HOME)/.nvu/bin/node
|
|
43
|
+
cp $(BUILD_DIR)/$(BINARY_NAME) $(HOME)/.nvu/bin/npm
|
|
44
|
+
cp $(BUILD_DIR)/$(BINARY_NAME) $(HOME)/.nvu/bin/npx
|
|
45
|
+
@echo "Shims installed to ~/.nvu/bin/"
|
|
46
|
+
@echo "Add to your PATH: export PATH=\"\$$HOME/.nvu/bin:\$$PATH\""
|
|
47
|
+
|
|
48
|
+
clean:
|
|
49
|
+
rm -rf $(BUILD_DIR)
|
|
50
|
+
|
|
51
|
+
# Create release archives
|
|
52
|
+
release: all
|
|
53
|
+
cd $(BUILD_DIR) && tar -czf $(BINARY_NAME)-darwin-arm64.tar.gz $(BINARY_NAME)-darwin-arm64
|
|
54
|
+
cd $(BUILD_DIR) && tar -czf $(BINARY_NAME)-darwin-x64.tar.gz $(BINARY_NAME)-darwin-x64
|
|
55
|
+
cd $(BUILD_DIR) && tar -czf $(BINARY_NAME)-linux-x64.tar.gz $(BINARY_NAME)-linux-x64
|
|
56
|
+
cd $(BUILD_DIR) && tar -czf $(BINARY_NAME)-linux-arm64.tar.gz $(BINARY_NAME)-linux-arm64
|
|
57
|
+
cd $(BUILD_DIR) && zip $(BINARY_NAME)-win32-x64.zip $(BINARY_NAME)-win32-x64.exe
|
|
58
|
+
cd $(BUILD_DIR) && zip $(BINARY_NAME)-win32-arm64.zip $(BINARY_NAME)-win32-arm64.exe
|
package/shim/go.mod
ADDED
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
|
+
}
|