Тема: JS: менеджер версий Node.js
Всем привет. Разрешите представить: Node Version Manager на WSH.
Не уверен что лучшим образом подошел к решению вопроса... в общем кому интересно - добро пожаловать: ваши замечания, pull request'ы и просто комменты.
nvmx.js:
var args = (function() {
var args = WScript.Arguments, arr = [];
for (var i = 0, len = args.length; i < len; i += 1) {
arr[i] = trimLeft(args.item(i));
}
return arr;
function trimLeft(item) {
var str = trimFirst(item), substr = str.substring(0,2);
return indexOf(substr) ? substr : str;
}
function trimFirst(item) {
var s = item.substring(0,1);
if (s === '/') return item.substr(1);
if (s === '-') return item.substring(1,2) === '-' ? item.substr(2) : item.substr(1);
return item;
}
function indexOf(s) {
var arr = ['no', 'io', 'lt', 'la', 'a', 'al', 'i', 'in', 'ls', 'li', 'rm', 're', 'ui', 'un', 'u', 'us', 'h', 'he', '?'], i = arr.length;
while (i--) {
if (arr[i] === s) return true;
}
}
})();
(function() {
switch(args[0]) {
case 'h':
case 'he':
case '?':
return help();
case 'no':
case 'io':
return go(args[1], args[0]); // Wrong sequence of arguments
default:
return go(args[0], args[1]); // Wright sequence of arguments
}
})();
function go(command, arg) { // arg = platform || version || architecture || undefined
switch(command) {
case 'lt':
case 'la':
latest(arg);
case 'a':
case 'al':
all(arg);
case 'i':
case 'in':
install(arg);
case 'ls':
case 'li':
list(arg);
case 'u':
case 'us':
use(arg);
case 'rm':
case 're':
case 'ui':
case 'un':
remove(arg);
case 'off':
off();
case 'path':
path();
default:
last();
}
}
function platform(arg) {
var config = {
no: {
distUrl: 'http://nodejs.org/dist/',
latestUrl: 'http://nodejs.org/dist/latest',
name: 'node',
folder: 'nodejs',
exe: 'node.exe',
getUrl: function(name, version) {
var url = this.distUrl + version, arch = getArch();
if (arch === 'x64') url += '/x64';
return url + '/' + name + '.msi';
}
},
io: {
distUrl: 'https://iojs.org/dist/',
latestUrl: 'https://iojs.org/dist/latest',
name: 'iojs',
folder: 'iojs',
exe: 'iojs.exe',
getUrl: function(name, version) {
return this.distUrl + version + '/' + name + '.msi';
}
}
};
return config[ arg in config ? arg : 'no' ]; // default = node
}
//-----
function latest(arg) {
var obj = platform(arg), version = getLatest(obj.latestUrl);
if (!version) return err('Failed getting latest ' + obj.name + ' version');
log('Latest ' + obj.name + ' version: ' + version);
return quit(0);
}
function getLatest(path) { // latest version
var ie = getIE(path);
var elements = ie.document.getElementsByTagName('a'), i = elements.length;
var arr, text, ret;
if (i) {
while (i--) {
text = elements[i].innerText;
if (!text) continue;
arr = text.split('-');
if (isVersion(arr)) {
ie.quit();
return arr[1].split('.').splice(0,3).join('.');
}
}
}
}
function isVersion(arr) {
return arr[1] && arr[1].substring(0,1) && arr[1].substring(0,1).toLowerCase() === 'v';
}
function getIE(path) {
var ie = ie_(), i = 0, timeout = 200;
while (!ie) {
if (timeout > 5000) return err('Failed getting InternetExplorer.Application. Please try again');
if (timeout%1000 === 0) log('Please wait...');
WScript.Sleep(timeout += 200);
ie = ie_();
}
ie.visible = false;
ie.navigate(path);
while (ie.busy || ie.readyState !== 4) {
if (++i > 300) return err('Failed getting response from ' + path);
WScript.Sleep(200);
}
return ie;
function ie_() {
try {
return WScript.CreateObject('InternetExplorer.Application');
} catch(e) {
// log(e.message && e.message.toString());
}
}
}
//-----
//-----
function all(arg) { // all versions
var obj = platform(arg);
var ie = getIE(obj.distUrl);
var elements = ie.document.getElementsByTagName('a'), i = elements.length;
var arr, text;
if (i) {
log('Available ' + obj.name + ' versions:');
while (i--) {
text = elements[i].innerText;
if (!text) continue;
if (text.indexOf('-') !== -1) continue;
arr = text.split('.');
if (arr.length < 3 || arr[1] === '') continue;
if (obj.name === 'node') {
if (7 < arr[1]) log(text.substring(0, text.length - 1));
} else {
log(text.substring(0, text.length - 1));
}
}
} else {
return err('Failed getting ' + obj.name + ' available versions');
}
ie.quit();
return quit(0);
}
//-----
//-----
function install(arg) {
var obj = platform(arg);
var param = (function() {
var version = fixArg(arg);
if (!version || version === 'latest') return getLatest(obj.latestUrl); // default
return fixVersion(version);
})();
var dirName = obj.name + '-' + param + '-' + getArch();
log('Installing ' + dirName);
var remote = obj.getUrl(dirName, param);
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var local = fso.GetSpecialFolder(2) + '\\' + dirName, localmsi = local + '.msi';
log('Downloading...');
// log('Downloading \nfrom: ' + remote + '\nto:' + localmsi);
download(remote, localmsi);
log('Extracting...');
// log('Extracting to: ' + local);
WScript.CreateObject('WScript.Shell').Run('msiexec /a ' + localmsi + ' /qn TARGETDIR=' + local, 0, true);
var copyFrom = local + '\\' + obj.folder, copyTo = getCurr(fso) + '\\' + dirName;
log('Copying...');
// log('Copying \nfrom: ' + copyFrom + '\nto: ' + copyTo);
if (!fso.folderExists(copyFrom)) err('Folder ' + copyFrom + ' not found');
fso.copyFolder(copyFrom, copyTo);
var binPath = copyTo + '\\' + 'node.exe';
if (!fso.fileExists(binPath)) fso.copyFile(copyTo + '\\' + obj.exe, binPath);
log('Installed ' + dirName);
return quit(0);
function download(remote, local) {
var xhr = WScript.CreateObject('MSXML2.XMLHTTP');
xhr.open('GET', remote, 0);
xhr.send();
if (xhr.readyState !== 4) return err('Failed to download from ' + remote);
if (xhr.status !== 200) return err('Failed to download from ' + remote);
var stream = WScript.CreateObject('ADODB.Stream');
stream.mode = 3;
stream.type = 1;
stream.open()
stream.write(xhr.responseBody);
stream.saveToFile(local, 2);
}
}
//-----
//-----
function list(arg) {
var platforms = arg ? [platform(arg)] : [platform('no'), platform('io')];
var bins = [], names = [], i = platforms.length, arch = getArch();
while (i--) {
bins[i] = platforms[i].exe;
names[i] = platforms[i].name;
}
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var folders = new Enumerator(fso.GetFolder(getCurr(fso)).subFolders);
var files;
for (; !folders.atEnd(); folders.moveNext()) {
if (!isTargetFolder(folders.item().Name, arch)) continue;
files = new Enumerator(folders.item().Files);
for (; !files.atEnd(); files.moveNext()) {
if (isTargetBin(files.item().Name)) {
log(folders.item().Name)
break;
}
}
}
return quit(0);
function isTargetFolder(folderName, arch) {
var i = names.length, arr = folderName.split('-');
while (i--) {
if (names[i] === arr[0] && arch === arr[2]) return true;
}
}
function isTargetBin(fileName) {
var i = bins.length;
while (i--) {
if (bins[i] === fileName) return true;
}
}
}
//-----
//-----
function use(arg) {
if (!arg) return last(); // last used version
var arr = getPath();
var version = fixVersion(fixArg(arg));
var dirName = platform(arg).name + '-' + version + '-' + getArch();
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var current = getCurr(fso);
var folders = new Enumerator(fso.GetFolder(current).subFolders);
for (; !folders.atEnd(); folders.moveNext()) {
if (folders.item().Name === dirName) {
var file = fso.CreateTextFile(current + '\\' + 'path.txt', true);
file.write((function() {
var first = arr[0].split('\\');
if (first.slice(0, first.length - 1).join('\\') === current) arr.shift();
return current + '\\' + dirName + ';' + arr.join(';');
})());
file.close();
log('Now using ' + dirName);
return quit(9);
}
}
err('Version ' + dirName + ' not found');
}
function getPath() {
var path = WScript.CreateObject('WScript.Shell').ExpandEnvironmentStrings('%PATH%');
return !(path && path.length) ? err('Invalid %PATH%') : path.split(';');
}
//-----
//-----
function remove(arg) {
if (!arg) err('Invalid version');
var version = fixVersion(fixArg(arg));
var dirName = platform(arg).name + '-' + version + '-' + getArch();
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var folders = new Enumerator(fso.GetFolder(getCurr(fso)).subFolders);
for (; !folders.atEnd(); folders.moveNext()) {
if (folders.item().Name === dirName) {
fso.deleteFolder(folders.item().Path, true);
log('Removed ' + dirName);
return quit(0);
}
}
err('Version ' + dirName + ' not found');
}
//-----
//-----
function last() { // last used version
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var current = getCurr(fso), path = current + '\\' + 'path.txt';
if (!fso.fileExists(path)) return help();
var file = fso.OpenTextFile(path, 1), arr = file.readAll().split(';');
file.close();
if (arr.length === 0) return err('Invalid path.txt');
var first = arr[0].split('\\'), version = first.pop();
if (first.join('\\') !== current) return log('Last used version not found');
if (isVersion(version.split('-'))) {
log('Now using ' + version);
return quit(9);
}
}
//-----
//-----
function off() {
var arr = getPath();
var fso = WScript.CreateObject('Scripting.FileSystemObject');
var current = getCurr(fso);
var first = arr[0].split('\\');
log('Now nvmx is off');
if (first.slice(0, first.length - 1).join('\\') === current) {
var file = fso.CreateTextFile(current + '\\' + 'path.txt', true);
file.write(arr.slice(1).join(';'));
file.close();
return quit(9);
}
return quit(0);
}
//-----
//-----
function path() {
log(getCurr(WScript.CreateObject('Scripting.FileSystemObject')));
quit(0);
}
//-----
function fixVersion(version) {
var arr = version.split('.');
if (arr.length !== 3 || arr[2].length === 0 || arr[1].length === 0 || arr[0].length === 0) return err('Invalid version');
if (version.substring(0,1).toLowerCase() === 'v') return version;
return 'v' + version;
}
function fixArg(arg) {
return arg === 'io' || arg === 'no' ? args[2] : arg; // may be blank
}
function getArch() { // architecture
var a = args[args.length - 1]; // last arg
if (a.substring(0,1).toLowerCase() === 'x') a = a.substr(1);
if (a === '64' || a === '86') return 'x' + a;
return WScript.CreateObject('WScript.Shell').Environment('System')('PROCESSOR_ARCHITECTURE') === 'x86' ? 'x86' : 'x64';
}
function getCurr(fso) {
return fso.GetParentFolderName(fso.GetFile(WScript.ScriptFullName));
}
function help() {
var version = 'v0.1.1', desc = 'Lightweight Node Version Manager for Windows';
log(desc + ' ' + version + '\n' +
'Commands:' + '\n' +
' nvmx latest [platform] Output latest available version' + '\n' +
' nvmx all [platform] Output all available versions' + '\n' +
' nvmx install [platform] <version> [architecture] Download and extract (install) <version>' + '\n' +
' nvmx list [platform] [architecture] Output local versions' + '\n' +
' nvmx use [platform] <version> [architecture] Modify PATH to use <version>' + '\n' +
' nvmx remove [platform] <version> [architecture] Remove (uninstall) <version>' + '\n' +
' nvmx uninstall [platform] <version> [architecture] Same as remove' + '\n' +
' nvmx ? Output help message' + '\n' +
' nvmx Modify PATH to use last used version' + '\n' +
' nvmx off Modify PATH to not use nvmx' + '\n' +
' nvmx path Output path to nvmx folder' + '\n' +
'Aliases:' + '\n' +
' lt latest -lt --latest /lt /latest' + '\n' +
' a all -a --all /a /all' + '\n' +
' i install -i --install /i /install' + '\n' +
' ls list -ls --list /ls /list' + '\n' +
' u use -u --use /u /use' + '\n' +
' rm remove -rm --remove /rm /remove' + '\n' +
' ui uninstall -ui --uninstall /ui /uninstall' + '\n' +
' h help -h --help /h /help ? /?' + '\n' +
'Platforms:' + '\n' +
' node nodejs node.js' + '\n' +
' io iojs io.js' + '\n' +
'Architectures:' + '\n' +
' x64 64' + '\n' +
' x86 86' + '\n' +
'Defaults:' + '\n' +
' platform = node' + '\n' +
' architecture = os architecture');
}
function quit(code) {
WScript.Quit(code);
}
function log(s) {
WScript.Echo(s);
}
function err(s, code) {
log(s);
quit(code || 0);
}
nvmx.bat:
@echo off
cscript.exe //nologo "%~dp0nvmx.js" %*
if %errorlevel% == 9 (
call :use
exit /b
)
exit /b
:use
set /p nvmx=<%~dp0path.txt
set "path=%nvmx%"
Кстати, возвращаясь к одной из предыдущих тем форума: здесь я использую тот же код для определения разрядности оси.

