nvm-vanilla 1.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 ADDED
@@ -0,0 +1,20 @@
1
+ # NVM
2
+
3
+ ## Usage
4
+
5
+ ```
6
+ nvm current
7
+ nvm (which|where) <version>
8
+ nvm (list|ls)
9
+ nvm alias <name>
10
+
11
+ nvm install <version>
12
+ nvm uninstall <version>
13
+ nvm use <version>
14
+
15
+ nvm run <version> <args>
16
+ nvm exec <version> <command>
17
+
18
+ nvm alias <name> <version>
19
+ nvm unalias <name>
20
+ ```
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "nvm-vanilla",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "nvm-vanilla": "src/bin.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "postinstall": "node src/init.js"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC"
16
+ }
package/src/bin.js ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+ const { promisify } = require('util');
7
+ const { spawnSync, execFileSync } = require('child_process');
8
+
9
+ const { init: install, use, list, detect, uninstall, alias, which } = require('./index');
10
+
11
+ const init = require('./init');
12
+
13
+ const evalCommandSet = new Set(['use', 'autoload', 'env']);
14
+
15
+ const main = async () => {
16
+ // process.stderr.write(JSON.stringify(process.argv) + '\n'); // debug
17
+
18
+ const homeDir = os.homedir();
19
+
20
+ const baseDir = path.join(homeDir, '.nvm2');
21
+
22
+ const args = process.argv.slice(2);
23
+
24
+ let evalMode = false;
25
+ if (args[0] === '--eval') {
26
+ evalMode = true;
27
+ args.shift();
28
+ }
29
+
30
+ const command = args[0];
31
+ const version = args[1];
32
+
33
+ if (evalMode ^ evalCommandSet.has(command)) return;
34
+
35
+ const checkVersion = () => {
36
+ if (version) return true;
37
+ throw 'no specific node version';
38
+ };
39
+
40
+ switch (command) {
41
+ case 'env': {
42
+ const content = await promisify(fs.readFile)(path.resolve(
43
+ __dirname,
44
+ process.platform == 'win32' ? 'nvm.ps1' : 'nvm.sh'
45
+ ), 'utf-8');
46
+ process.stdin.write(content);
47
+ break;
48
+ }
49
+ case 'use': {
50
+ await use(baseDir, version);
51
+ break;
52
+ }
53
+ case 'autoload': {
54
+ const targetVersion = await detect(baseDir);
55
+ if (targetVersion) {
56
+ await use(baseDir, targetVersion);
57
+ } else {
58
+ console.log(':');
59
+ }
60
+ break;
61
+ }
62
+ case 'which':
63
+ case 'where': {
64
+ if (!checkVersion()) return;
65
+ const dir = await which(baseDir, version);
66
+ console.log(dir);
67
+ break;
68
+ }
69
+ case 'alias': {
70
+ await alias(baseDir, args[1], args[2]);
71
+ break;
72
+ }
73
+ case 'install': {
74
+ await install(baseDir, version);
75
+ break;
76
+ }
77
+ case 'unalias':
78
+ case 'uninstall': {
79
+ if (!checkVersion()) return;
80
+ await uninstall(baseDir, version);
81
+ break;
82
+ }
83
+ case 'ls':
84
+ case 'list': {
85
+ await list(baseDir);
86
+ break;
87
+ }
88
+ case 'exec':
89
+ case 'run': {
90
+ if (!checkVersion()) return;
91
+ const env = await use(baseDir, version, false);
92
+ Object.assign(process.env, env);
93
+ const childArgs = args.slice(2);
94
+ const childCommand = command === 'run' ? 'node' : childArgs.shift();
95
+ spawnSync(childCommand, childArgs, { stdio: 'inherit' });
96
+ break;
97
+ }
98
+ case 'current': {
99
+ spawnSync('node', ['--version'], { stdio: 'inherit' });
100
+ break;
101
+ }
102
+ case 'init': {
103
+ await init();
104
+ break;
105
+ }
106
+ default:
107
+ }
108
+ };
109
+
110
+ main()
111
+ .catch((error) => {
112
+ const message = (error || {}).stack || error;
113
+ process.stderr.write(message + '\n');
114
+ process.exit(-1);
115
+ });
package/src/index.js ADDED
@@ -0,0 +1,408 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const http = require('http');
5
+ const https = require('https');
6
+ const { spawn, execFile } = require('child_process');
7
+ const { promisify } = require('util');
8
+
9
+ const getNpmCommand = () => {
10
+ return process.platform == 'win32' ? 'npm.cmd' : 'npm';
11
+ };
12
+
13
+ const npmCommand = getNpmCommand();
14
+
15
+ // https://github.com/aredridel/node-bin-setup/blob/514d4aa42b58b10971845c420d34330c2414eef9/index.js#L10-L17
16
+
17
+ process.env.npm_config_global = 'false';
18
+ process.env.npm_config_repository = '';
19
+
20
+ const getNodePackageName = () => {
21
+ const platform = process.platform == 'win32' ? 'win' : process.platform;
22
+ const arch = platform == 'win' && process.arch == 'ia32' ? 'x86' : process.arch;
23
+ const prefix = (process.platform == 'darwin' && process.arch == 'arm64') ? 'node-bin' : 'node';
24
+ return [prefix, platform, arch].join('-');
25
+ };
26
+
27
+ const nodePackageName = getNodePackageName();
28
+
29
+ const readJsonFile = (filePath) => (
30
+ promisify(fs.readFile)(filePath, 'utf8').then(JSON.parse)
31
+ );
32
+
33
+ const promisifySpawn = (command, args, options) => {
34
+ const child = spawn(command, args, {
35
+ stdio: 'inherit',
36
+ ...options,
37
+ });
38
+
39
+ return new Promise((resolve, reject) => {
40
+ child.on('exit', code => {
41
+ if (code === 0) resolve();
42
+ else reject(code);
43
+ });
44
+ });
45
+ };
46
+
47
+ const getNodeVersion = async (semanticVersion) => {
48
+ const npmViewOutput = await promisify(execFile)('npm', [
49
+ 'view',
50
+ nodePackageName + '@' + semanticVersion,
51
+ 'version',
52
+ '--json',
53
+ ])
54
+ .catch(() => { });
55
+
56
+ let nodeVersion;
57
+ try {
58
+ nodeVersion = JSON.parse(npmViewOutput.stdout).pop();
59
+ } catch (_) { }
60
+
61
+ return nodeVersion;
62
+ };
63
+
64
+ const fetchJsonFile = async (fileUrl) => {
65
+ const response = await new Promise((resolve, reject) => {
66
+ const { get } = /^http:\/\//.test(fileUrl) ? http : https;
67
+ get(fileUrl)
68
+ .on('response', resolve)
69
+ .on('error', reject);
70
+ });
71
+
72
+ const chunkList = [];
73
+
74
+ await new Promise((resolve, reject) => {
75
+ response
76
+ .on('error', reject)
77
+ .on('end', resolve)
78
+ .on('data', (chunk) => {
79
+ chunkList.push(chunk);
80
+ });
81
+ });
82
+
83
+ return JSON.parse(Buffer.concat(chunkList));
84
+ };
85
+
86
+ const getNpmVersion = async (nodeVersion) => {
87
+ const list = await Promise.race([
88
+ fetchJsonFile('https://cdn.npmmirror.com/binaries/node/index.json'),
89
+ fetchJsonFile('https://nodejs.org/dist/index.json'),
90
+ ]);
91
+
92
+ const target = list.find(item =>
93
+ item.version === nodeVersion ||
94
+ item.version === 'v' + nodeVersion
95
+ );
96
+
97
+ if (!target) throw 'npm version not found';
98
+
99
+ return target.npm;
100
+ };
101
+
102
+ const install = async (cwd, nodeVersion) => {
103
+ // await promisifySpawn(npmCommand, ['install', '--no-save', nodePackageName + '@' + version], {
104
+ // stdio: 'inherit',
105
+ // // shell: true,
106
+ // cwd,
107
+ // });
108
+
109
+ // const packageFile = path.join(cwd, 'node_modules', nodePackageName, 'package.json');
110
+ // const { version: nodeVersion } = await readJsonFile(packageFile);
111
+
112
+ const npmVersion = await getNpmVersion(nodeVersion);
113
+
114
+ await promisifySpawn(npmCommand, [
115
+ 'install',
116
+ '--no-save',
117
+ '--ignore-engines',
118
+ nodePackageName + '@' + nodeVersion,
119
+ 'npm' + '@' + npmVersion,
120
+ ], {
121
+ stdio: 'inherit',
122
+ // shell: true,
123
+ cwd,
124
+ });
125
+
126
+ return nodeVersion;
127
+ };
128
+
129
+ /*
130
+ const override = async (workDir) => {
131
+ const npmDir = path.join(workDir, 'node_modules', 'npm');
132
+ const { bin } = await readJsonFile(path.join(npmDir, 'package.json'));
133
+
134
+ const fileList = [];
135
+
136
+ if (typeof bin === 'string') {
137
+ fileList.push(bin);
138
+ } else if (bin && typeof bin === 'object') {
139
+ for (const key in bin) {
140
+ fileList.push(bin[key]);
141
+ }
142
+ }
143
+
144
+ const promiseList = fileList.map(async relativeFilePath => {
145
+ relativeFilePath = relativeFilePath.replace(/^\//, '');
146
+ const relativeLibPath = path.posix.relative(
147
+ path.join(npmDir, relativeFilePath),
148
+ workDir,
149
+ );
150
+ const prefix = `
151
+ process.env.NPM_CONFIG_PREFIX = process.env.NPM_CONFIG_PREFIX
152
+ || require("path").resolve(__filename, ${JSON.stringify(relativeLibPath)}, "prefix");
153
+ process.env.NPM_CONFIG_CACHE = process.env.NPM_CONFIG_CACHE
154
+ || require("path").resolve(__filename, ${JSON.stringify(relativeLibPath)}, "cache");
155
+ `
156
+ .trim();
157
+ const filePath = path.join(npmDir, relativeFilePath);
158
+ let content = await promisify(fs.readFile)(filePath, 'utf-8');
159
+ content = content.replace(/\n/, '\n' + prefix + '\n'); // #!/usr/bin/env node 第一行要保留
160
+ await promisify(fs.writeFile)(filePath, content, 'utf-8');
161
+ });
162
+
163
+ return Promise.all(promiseList);
164
+ };
165
+ */
166
+
167
+ const detect = async (baseDir) => {
168
+ const [
169
+ nodeVersion,
170
+ nvmrc,
171
+ defaultVersion,
172
+ ] = await Promise.all(
173
+ [
174
+ '.node-version',
175
+ '.nvmrc',
176
+ ]
177
+ .map(name => promisify(fs.readFile)(name, 'utf-8').catch(() => { }))
178
+ .concat([
179
+ baseDir && getLocalNodeVersion(baseDir, 'default').catch(() => { }),
180
+ ])
181
+ );
182
+
183
+ return String(nodeVersion || nvmrc || defaultVersion || '').trim();
184
+ };
185
+
186
+ const corrent = async (version) => {
187
+ if (!version) version = await detect();
188
+ if (!version) {
189
+ throw 'cannot detect node version';
190
+ }
191
+ return version.replace(/^v/i, '');
192
+ };
193
+
194
+ const clear = async (dir) => {
195
+ if (process.platform == 'win32') {
196
+ await promisify(execFile)('rd', ['/s', '/q', dir]);
197
+ } else {
198
+ await promisify(execFile)('rm', ['-rf', dir]);
199
+ }
200
+ };
201
+
202
+ const getLocalNodeVersion = async (baseDir, name) => {
203
+ const packageFile = path.join(baseDir, name, 'node_modules', nodePackageName, 'package.json');
204
+ try {
205
+ const { version } = await readJsonFile(packageFile);
206
+ return version;
207
+ } catch (_) {
208
+ throw `no local node version "${name}"`;
209
+ }
210
+ };
211
+
212
+ const alias = async (baseDir, version, targetVersion) => {
213
+ if (version === 'system') {
214
+ throw 'cannot alias system node version';
215
+ }
216
+
217
+ const linkDir = path.join(baseDir, version);
218
+
219
+ const resetFlag = targetVersion === 'none' && false;
220
+
221
+ if (!targetVersion) {
222
+ const nodeVersion = await getLocalNodeVersion(baseDir, version);
223
+ console.log(nodeVersion);
224
+ return;
225
+ } else if (!resetFlag) {
226
+ await getLocalNodeVersion(baseDir, targetVersion); // check
227
+ }
228
+
229
+ const sourceDir = path.join(baseDir, targetVersion);
230
+
231
+ try {
232
+ await promisify(fs.unlink)(linkDir);
233
+ } catch (_) { }
234
+
235
+ if (!resetFlag) await promisify(fs.symlink)(sourceDir, linkDir, 'dir');
236
+ };
237
+
238
+ const init = async (baseDir, version) => {
239
+ version = await corrent(version);
240
+ if (!version) return;
241
+
242
+ try {
243
+ await promisify(fs.mkdir)(baseDir);
244
+ } catch (_) { }
245
+
246
+ const nodeVersion = await getNodeVersion(version);
247
+
248
+ if (!nodeVersion) {
249
+ throw `no satisified node version "${version}"`;
250
+ }
251
+
252
+ const workDir = path.join(baseDir, nodeVersion);
253
+
254
+ try {
255
+ await promisify(fs.mkdir)(workDir);
256
+ } catch (_) {
257
+ throw `node version "${nodeVersion}" already installed`;
258
+ }
259
+
260
+ const binDir = path.join(workDir, 'bin');
261
+ const templateDir = path.join(__dirname, 'template');
262
+
263
+ const mkdirPromise = Promise.all([
264
+ // 'bin',
265
+ 'cache',
266
+ 'prefix',
267
+ ].map(
268
+ name => promisify(fs.mkdir)(path.join(workDir, name))
269
+ ))
270
+ .then(() => (
271
+ promisify(fs.mkdir)(path.join(workDir, 'prefix', 'lib'))
272
+ ));
273
+
274
+ const [
275
+ // nameList,
276
+ ] = await Promise.all([
277
+ // promisify(fs.readdir)(templateDir),
278
+ install(workDir, nodeVersion),
279
+ mkdirPromise,
280
+ ]);
281
+
282
+ // await override(workDir);
283
+
284
+ /*
285
+ const binNameList = await promisify(fs.readdir)(path.join(workDir, 'node_modules', '.bin'));
286
+
287
+ const commandNameSet = new Set(binNameList.map(_ => _.split('.')[0]));
288
+
289
+ await Promise.all(nameList.map(async name => {
290
+ const commandName = name.split('.')[0];
291
+ if (!commandNameSet.has(commandName)) return;
292
+ const targetPath = path.join(binDir, name);
293
+ await promisify(fs.copyFile)(
294
+ path.join(templateDir, name),
295
+ targetPath,
296
+ );
297
+ await promisify(fs.chmod)(targetPath, 0o755);
298
+ }));
299
+ */
300
+
301
+ if (version !== nodeVersion) {
302
+ await alias(baseDir, version, nodeVersion);
303
+ }
304
+ };
305
+
306
+ const uninstall = async (baseDir, version) => {
307
+ const workDir = path.join(baseDir, version);
308
+
309
+ try {
310
+ await clear(workDir);
311
+ } catch (_) { }
312
+ };
313
+
314
+ const which = async (baseDir, name) => {
315
+ const version = await getLocalNodeVersion(baseDir, name);
316
+ const workDir = path.join(baseDir, version, 'node_modules', '.bin');
317
+ return workDir;
318
+ };
319
+
320
+ const use = async (baseDir, version, evalFlag = true) => {
321
+ if (version !== 'system') {
322
+ version = await corrent(version);
323
+ version = await getLocalNodeVersion(baseDir, version);
324
+ }
325
+
326
+ // const workDir = path.join(baseDir, version, 'bin');
327
+
328
+ const workDir = path.join(baseDir, version, 'node_modules', '.bin');
329
+ const prefixDir = path.join(baseDir, version, 'prefix');
330
+ const cacheDir = path.join(baseDir, version, 'cache');
331
+
332
+ const resetFlag = version === 'system';
333
+
334
+ // let checkFlag = false;
335
+ // try {
336
+ // const stat = await promisify(fs.stat)(workDir);
337
+ // checkFlag = stat.isDirectory();
338
+ // } catch (_) { }
339
+
340
+ // if (!checkFlag && !resetFlag) {
341
+ // throw `node version "${version}" not installed`;
342
+ // }
343
+
344
+ let list = process.env.PATH.split(path.delimiter);
345
+
346
+ list = list.filter(item => item.indexOf(baseDir) === -1);
347
+
348
+ if (!resetFlag) list.unshift(workDir, prefixDir);
349
+
350
+ const env = {};
351
+
352
+ env.PATH = list.join(path.delimiter);
353
+
354
+ env.NPM_CONFIG_PREFIX = prefixDir;
355
+ env.NPM_CONFIG_CACHE = cacheDir;
356
+
357
+ // process.stderr.write('Now using node v' + version + '\n');
358
+
359
+ if (evalFlag) {
360
+ for (const key in env) {
361
+ const value = env[key];
362
+ let command = (key === 'PATH' || true)
363
+ ? `export ${key}=${value}`
364
+ : `export ${key}=\${${key}:-${value}}`;
365
+
366
+ if (process.platform === 'win32') {
367
+ command = (key === 'PATH' || true)
368
+ ? `set ${key}=${value}`
369
+ : `if not defined ${key} set ${key}=${value}`;
370
+ }
371
+
372
+ process.stdout.write(
373
+ command + '\n'
374
+ );
375
+
376
+ // process.stderr.write(
377
+ // command + '\n'
378
+ // );
379
+ }
380
+ }
381
+
382
+ return env;
383
+ };
384
+
385
+ const list = async (baseDir) => {
386
+ const nameList = await promisify(fs.readdir)(baseDir);
387
+
388
+ let versionList = await Promise.all(nameList.map(async name => {
389
+ const packageFile = path.join(baseDir, name, 'node_modules', nodePackageName, 'package.json');
390
+ const { version } = await readJsonFile(packageFile).catch(() => ({}));
391
+ if (!version) return;
392
+ return 'node@' + name + ' (' + version + ')';
393
+ }));
394
+
395
+ versionList = versionList.filter(Boolean);
396
+
397
+ if (versionList.length) console.log(versionList.join('\n'));
398
+ };
399
+
400
+ module.exports = {
401
+ init,
402
+ uninstall,
403
+ use,
404
+ list,
405
+ detect,
406
+ alias,
407
+ which,
408
+ };
package/src/init.js ADDED
@@ -0,0 +1,28 @@
1
+ const os = require('os');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { promisify } = require('util');
5
+
6
+ const init = async () => {
7
+ if (process.platform === 'win32') {
8
+ await promisify(fs.appendFile)(
9
+ path.resolve(os.homedir(), 'Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1'),
10
+ '\nnvm-vanilla env --eval | Out-String | Invoke-Expression',
11
+ )
12
+ } else {
13
+ await promisify(fs.appendFile)(
14
+ path.resolve(os.homedir(), '.bashrc'),
15
+ '\neval "$(nvm-vanilla env --eval)"',
16
+ );
17
+ }
18
+ };
19
+
20
+ module.exports = init;
21
+
22
+ // ${ZDOTDIR:-$HOME}/.zshrc
23
+ // $HOME/.profile
24
+ // $HOME/.bashrc
25
+
26
+ if (require.main === module) {
27
+ init();
28
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('./bin')
package/src/nvm.ps1 ADDED
@@ -0,0 +1,10 @@
1
+ function nvm {
2
+ param([string]$Command)
3
+
4
+ $result = nvm-vanilla --eval $Command 2>$null
5
+ if ($result) {
6
+ Invoke-Expression $result
7
+ } else {
8
+ nvm-vanilla $Command
9
+ }
10
+ }
package/src/nvm.sh ADDED
@@ -0,0 +1,35 @@
1
+ nvm() {
2
+ local cmd="$(command nvm-vanilla --eval "$@")"
3
+ if [ -n "$cmd" ]; then
4
+ eval $cmd
5
+ else
6
+ command nvm-vanilla "$@"
7
+ fi
8
+ }
9
+
10
+ nvm_autoload_dir=""
11
+
12
+ _nvm_autoload_hook() {
13
+ local current_dir="$PWD"
14
+
15
+ if [ "$current_dir" = "$nvm_autoload_dir" ]; then
16
+ return
17
+ fi
18
+
19
+ nvm autoload
20
+
21
+ nvm_autoload_dir="$current_dir"
22
+ }
23
+
24
+ # 根据不同 shell 设置钩子
25
+ case "$SHELL" in
26
+ *bash*)
27
+ # bash: 使用 PROMPT_COMMAND
28
+ PROMPT_COMMAND="_nvm_autoload_hook; $PROMPT_COMMAND"
29
+ ;;
30
+ *zsh*)
31
+ # zsh: 使用 chpwd 或 precmd 钩子
32
+ autoload -U add-zsh-hook
33
+ add-zsh-hook chpwd _nvm_autoload_hook
34
+ ;;
35
+ esac
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
4
+ BIN=$DIR/node_modules/.bin
5
+
6
+ $BIN/node "$@"
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
4
+ BIN=$DIR/node_modules/.bin
5
+
6
+ export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$DIR/prefix}"
7
+ export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$DIR/cache}"
8
+
9
+ $BIN/node $BIN/npm "$@"
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+
3
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
4
+ BIN=$DIR/node_modules/.bin
5
+
6
+ export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-$DIR/prefix}"
7
+ export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$DIR/cache}"
8
+
9
+ $BIN/node $BIN/npx "$@"