@titanium/appcelerator
Version:
⭐ Axway Amplify command-line (CLI) tool for installing Appcelerator Titanium SDK
1,283 lines (1,167 loc) • 35.3 kB
JavaScript
// jscs:disable jsDoc
const { spawnSync } = require('child_process');
/**
* This code is closed source and Confidential and Proprietary to
* Appcelerator, Inc. All Rights Reserved. This code MUST not be
* modified, copied or otherwise redistributed without express
* written permission of Appcelerator. This file is licensed as
* part of the Appcelerator Platform and governed under the terms
* of the Appcelerator license agreement.
*/
var fs = require('fs'),
path = require('path'),
os = require('os'),
chalk,
urllib = require('url'),
PacAgent = require('pac-proxy-agent'),
semver = require('semver'),
debug = require('debug')('appc:util'),
spinner,
spriteIndex = 0,
cachedConfig,
sprite = '/-\\|',
execSync = require('child_process').execSync, // eslint-disable-line security/detect-child-process
stripAnsi = require('strip-ansi');
var MAX_RETRIES = exports.MAX_RETRIES = 5;
var CONN_TIMEOUT = 10000;
// NOTE: not using char-spinner because i don't want it to reset to beginning
// of line each time it starts/stops. i want to spin in place at point of cursor
/*
Testing Utilities.
*/
exports.stdout = process.stdout;
/* istanbul ignore next */
exports.exit = function (code) {
process.exit(code);
};
/* istanbul ignore next */
exports.setCachedConfig = function (val) {
cachedConfig = val;
};
/**
* start the spinner
*/
function startSpinner() {
stopSpinner();
if (!spinner && exports.stdout.isTTY && !process.env.TRAVIS) {
var count = 0;
spinner = setInterval(function () {
if (count++ > 0) {
// go back one column
exports.stdout.write('\u001b[1D');
}
var s = ++spriteIndex % sprite.length;
var c = sprite[s];
exports.stdout.write(c);
}, 50);
}
}
/**
* stop the spinner
*/
function stopSpinner() {
if (spinner) {
clearInterval(spinner);
// go back on column
exports.stdout.write('\u001b[1D');
spinner = null;
}
}
/**
* write a wait message and start spinner
* @param {string} msg - message to output
*/
function waitMessage(msg) {
exports.stdout.write(msg);
startSpinner();
}
/**
* write OK mark and stop spinner
* @param {string} msg - message to output
*/
function okMessage(msg) {
chalk = chalk || require('chalk');
stopSpinner();
msg = msg || '';
exports.stdout.write(msg + ' ' + chalk.green(isWindows() ? 'OK' : '✓') + '\n');
}
/**
* write message and stop spinner
* @param {string} msg - message to output
*/
function infoMessage(msg) {
stopSpinner();
exports.stdout.write(msg + '\n');
}
/**
* return the platform specific HOME directory
* @returns {string}
*/
function getHomeDir() {
return os.homedir();
}
/**
* return our AppC directory
* @returns {string}
*/
function getAppcDir() {
return path.join(getHomeDir(), '.appcelerator');
}
/**
* return the AppC install tag file
* @returns {string}
*/
function getInstallTag() {
return path.join(getAppcDir(), '.installing');
}
/**
* return the global cache directory in the users home folder
* @returns {string}
*/
function getCacheDir() {
return path.join(getAppcDir(), 'cache');
}
/**
* return the platform specific install directory
* @returns {string}
*/
function getInstallDir() {
return path.join(getAppcDir(), 'install');
}
/**
* return the version file
* @returns {string}
*/
function getVersionFile() {
return path.join(getInstallDir(), '.version');
}
/**
* return the config file
* @returns {string}
*/
function getConfigFile() {
return path.join(getAppcDir(), 'appc-cli.json');
}
/**
* return the private npm cache directory
* @returns {string}
*/
function getNpmCacheDirectory() {
return path.join(getAppcDir(), '.npm');
}
/**
* write out the current version file
* @param {string} version - Version to write
*/
function writeVersion(version) {
var versionFile = getVersionFile();
debug('writing version: %s to %s', version, versionFile);
if (fs.existsSync(versionFile)) {
fs.unlinkSync(versionFile);
}
fs.writeFileSync(versionFile, version);
}
/**
* return the active version (if specified) or undefined
* @returns {string|undefined}
*/
function getActiveVersion() {
var versionFile = getVersionFile();
if (fs.existsSync(versionFile)) {
return fs.readFileSync(versionFile).toString().trim();
}
}
/**
* remove version file
*/
function removeVersion() {
var versionFile = getVersionFile();
debug('remove version %s', versionFile);
if (fs.existsSync(versionFile)) {
fs.unlinkSync(versionFile);
}
}
/**
* list versions installed
* @param {object} opts - Options
* @param {object} versions - Versions of the CLI
* @returns {undefined}
*/
function listVersions(opts, versions) {
chalk = chalk || require('chalk');
if (!versions) {
exports.stdout.write(chalk.red('No versions available') + '\n');
return;
}
var activeVersion = getActiveVersion();
// If we don't find latest version from api/appc/list endpoint, then inject the latest version into the list.
var versionsList = Object.keys(versions).map(function (value, index) {
return versions[index].version || versions[index];
});
if (opts.latest && versionsList.indexOf(opts.latest) === -1) {
versionsList.push(opts.latest);
}
versions = versionsList.sort(semver.compareLoose);
versions.forEach(function (entry) {
var ver = entry.version ? entry.version : entry,
suffix = getInstallBinary(opts, ver) ? 'Installed' : 'Not Installed';
if (opts.latest === ver) {
suffix += chalk.white.bold(' (Latest)');
}
if (activeVersion && activeVersion === ver) {
suffix += chalk.red(' (Active)');
}
var date = entry.date ? ' ' + chalk.grey(pad(new Date(Date.parse(entry.date)), 15)) : '';
exports.stdout.write(chalk.yellow(pad(ver, 10)) + ' ' + chalk.cyan(pad(suffix, 40)) + date + '\n');
});
}
/**
* return json object of versions
* @param {object} opts - Options
* @param {object} versions - CLI versions
* @return {object}
*/
function getVersionJson(opts, versions) {
var activeVersion = getActiveVersion(),
obj = {
versions: [],
installed: getInstalledVersions(),
latest: opts.latest,
active: activeVersion
};
if (Array.isArray(versions)) {
if (versions[0] && versions[0].version) {
obj.versions = Object.keys(versions).map(function (value, index) {
return versions[index].version;
});
} else {
obj.versions = versions;
}
}
return obj;
}
/**
* return the current versions installed
* @returns {string[]}
*/
function getInstalledVersions() {
var installDir = getInstallDir();
if (fs.existsSync(installDir)) {
// try and resolve the latest
try {
var dirs = fs.readdirSync(installDir);
if (dirs.length) {
if (dirs.length > 1) {
// attempt to sort by latest version
dirs = dirs
.filter(function (e) {
return e[0] !== '.';
})
.sort(function (a, b) {
var av = parseInt(a.replace(/\./g, '')),
bv = parseInt(b.replace(/\./g, ''));
return bv - av;
});
}
debug('found the following version directories: %j', dirs);
return dirs;
}
} catch (E) {
debug('error reading install directory %o', E);
if (E.code === 'EACCES') {
chalk = chalk || require('chalk');
var chPer = 'Please make sure you change the permissions and re-try';
var chPerWithCmds = 'Please make sure you change the permissions using these commands:\n\n\t';
chPerWithCmds += chalk.yellow('sudo chown -R ' + process.env.USER + ' ' + installDir + '\n\tchmod -R 0700 ' + installDir);
var message = process.platform === 'win32' ? chPer : chPerWithCmds + '\n';
fail('Ooops! Your install directory (' + installDir + ') is not writable.\n' + message);
}
fail(E);
}
}
}
/**
* return the platform specific install binary path
* @param {object} opts - Options
* @param {string} theversion - version to lookup the install binary for
* @returns {string|null}
*/
function getInstallBinary(opts, theversion) {
opts = opts || {};
// first check and see if specified on command line as option
var version = theversion || (opts.version !== true ? opts.version : null) || '',
installDir = getInstallDir(),
bin = path.join(installDir, version, 'package', 'bin', 'appc'),
pkg,
dirs;
if (fs.existsSync(bin)) {
// check the package.json since we will delete it on an interrupted download attempt
pkg = path.join(installDir, version, 'package', 'package.json');
return fs.existsSync(pkg) && bin;
} else if (theversion) {
// if we specified a version and we didn't find it, return null
return null;
}
// see if we have a version set
theversion = getActiveVersion();
if (theversion) {
bin = getInstallBinary(opts, theversion);
if (!bin) {
if (!opts.version) {
chalk = chalk || require('chalk');
debug('you have specified a version (%s) that isn\'t found', theversion);
// only warn if we're not asking for this version
// invalid version specified in version file. remove it and then re-install from latest
exports.stdout.write(chalk.red('version ' + theversion + ' specified previously is no longer available.') + '\n');
}
removeVersion();
} else {
return bin;
}
}
dirs = getInstalledVersions();
if (dirs) {
for (var c = 0; c < dirs.length; c++) {
bin = path.join(installDir, dirs[c], 'package', 'bin', 'appc');
if (fs.existsSync(bin)) {
// check the package.json since we will delete it on an interrupted download attempt
pkg = path.join(installDir, dirs[c], 'package', 'package.json');
return fs.existsSync(pkg) && bin;
}
}
}
}
/**
* given a full path, makes sure that the directory exists
* @param {string} dir - Directory to ensure exists
* @return {string}
*/
function ensureDir(dir) {
var last = expandPath(dir),
parts = [];
// find the top of the root that exists
do {
parts.unshift(path.basename(last));
last = path.join(last, '..');
} while (!fs.existsSync(last));
if (!fs.existsSync(last)) {
fs.mkdirSync(last);
}
// now create the directories in order
for (var c = 0; c < parts.length; c++) {
var fp = path.join(last, parts[c]);
if (!fs.existsSync(fp)) {
fs.mkdirSync(fp);
}
last = fp;
}
return dir;
}
/**
* expand ~ in fn
* @param {string} fn - Path to expand
* @returns {string}
*/
function expandPath(fn) {
var home = getHomeDir(),
p = fn.replace(/~\/?/g, function (value) {
if (/\/$/.test(value)) {
return home + '/';
}
return home;
});
return p;
}
/**
* fail and properly exit
* @param {string} msg - Message to output
*/
function fail(msg) {
stopSpinner();
debug('fail %o', msg);
if (msg.stack && process.env.DEBUG) {
console.error(msg.stack);
}
chalk = chalk || require('chalk');
console.error('\n' + (chalk.red(msg.message || msg)));
exports.exit(1);
}
var optionRE = /^-{1,2}([\w-_]+)=?(.*)?$/;
/**
* very loose parsing of options
* @returns {object}
*/
function parseOpts() {
var args = {};
for (var c = 2; c < process.argv.length; c++) {
var arg = process.argv[c];
if (optionRE.test(arg)) {
var match = optionRE.exec(arg),
name = match[1],
value = match.length > 2 && match[2] || (process.argv[c + 1] && !/^-{1,2}/.test(process.argv[c + 1]) ? process.argv[c + 1] : null) || true;
if (value === 'true' || value === 'false') {
value = value === 'true';
}
if (name.indexOf('no-') === 0) {
name = name.substring(3);
value = false;
}
args[name] = value;
}
}
return args;
}
/**
* loose parse none options
* @param {object} opts - Options
* @returns {string[]}
*/
function parseArgs(opts) {
if (!opts) {
throw new Error('missing opts');
}
var args = [];
for (var c = 2; c < process.argv.length; c++) {
var arg = process.argv[c];
var previous = process.argv[c - 1];
if (optionRE.test(previous)) {
var previousMatch = optionRE.exec(previous);
previous = previousMatch[1];
}
if (optionRE.test(arg)) {
var match = optionRE.exec(arg),
name = match[1],
value = opts[name];
// see if a value was provided and if so, remove it too
if (value && String(process.argv[c + 1] === String(value))) {
c++;
}
continue;
} else if (opts[previous] === undefined) {
args.push(arg);
}
}
return args;
}
/**
* make a registry url
* @param {object} opts - Options
* @param {string} urlpath - Path to add to the the baseUrl
* @returns {string}
*/
function makeURL(opts, urlpath) {
if (typeof (opts) === 'string') {
urlpath = opts;
opts = {};
} else {
opts = opts || {};
}
var baseurl;
if (opts.registry) {
baseurl = opts.registry;
} else if (process.env.APPC_REGISTRY_SERVER) {
baseurl = process.env.APPC_REGISTRY_SERVER;
} else if (process.env.APPC_ENV || process.env.NODE_ENV) {
var env = process.env.APPC_ENV || process.env.NODE_ENV;
if (env === 'preproduction') {
baseurl = DEFAULT_PREPROD_REGISTRY_URL;
} else if (env === 'preprodonprod') {
baseurl = DEFAULT_PREPRODONPROD_REGISTRY_URL;
} else if (env === 'production') {
baseurl = DEFAULT_PROD_REGISTRY_URL;
}
}
if (!baseurl) {
var config = exports.readConfig();
if (config && config.registry) {
baseurl = config.registry;
} else if (config && (config.defaultEnvironment === 'preproduction' || config.environmentName === 'preproduction')) {
baseurl = DEFAULT_PREPROD_REGISTRY_URL;
} else if (config && (config.defaultEnvironment === 'preprodonprod' || config.environmentName === 'preprodonprod')) {
baseurl = DEFAULT_PREPRODONPROD_REGISTRY_URL;
} else {
baseurl = DEFAULT_PROD_REGISTRY_URL;
}
}
return urllib.resolve(baseurl, urlpath);
}
function makeRequestError(msg, code) {
var err = new Error(msg);
err.code = code;
return err;
}
// TODO: Perhaps we should include and use appc-platform-sdk env vars?
var DEFAULT_PREPROD_REGISTRY_URL = 'https://registry.axwaytest.net';
var DEFAULT_PROD_REGISTRY_URL = 'https://registry.platform.axway.com';
var DEFAULT_PREPRODONPROD_REGISTRY_URL = 'https://software-preprodonprod.appcelerator.com';
/**
* return the request library
* @return {object}
*/
function getRequest() {
return require('request');
}
/**
* make a request to location url
* @param {string|object} location - url to request or request object
* @param {function} callback - function to call when done
* @returns {request}
*/
function request(location, callback) {
var options;
if (typeof (location) === 'object') {
options = location;
location = options.url;
}
var url = urllib.parse(location),
config = readConfig(),
userAgent = 'Appcelerator CLI/' + require('../package').version + ' (' + process.platform + ')',
opts = {
url: url,
headers: {
'user-agent': userAgent,
host: url.host,
'appc-token': config && config.sid
}
};
if (options) {
opts.timeout = CONN_TIMEOUT * (options.attempts || 1);
}
if (process.env.APPC_CONFIG_PAC_FILE) {
opts.agent = new PacAgent('pac+' + process.env.APPC_CONFIG_PAC_FILE);
} else if (process.env.APPC_CONFIG_PROXY !== '') {
opts.proxy = process.env.APPC_CONFIG_PROXY;
}
if (process.env.APPC_CONFIG_CAFILE) {
opts.ca = fs.readFileSync(process.env.APPC_CONFIG_CAFILE, 'utf8');
}
if (process.env.APPC_CONFIG_STRICTSSL === 'false') {
opts.strictSSL = false;
}
var req = getRequest().get(opts);
debug('request %j', opts);
// start the request
req.on('response', function (res) {
debug('request response received');
if (req.__err) {
debug('request response callback skipped, request error already executed.');
} else {
callback(null, res, req);
}
});
// check the error
req.on('error', function (err) {
req.__err = true;
debug('request error', err);
if (err.name === 'AppCError') {
return callback(err);
}
if (err.code === 'ECONNREFUSED') {
return callback(makeRequestError('Error connecting to download server at ' + url.host + '. Make sure you are online.', err.code));
} else if (err.code === 'ENOTFOUND') {
return callback(makeRequestError('Error connecting to download server at ' + url.host + ' (not found). Make sure you are online.', err.code));
} else if (err.code === 'ECONNRESET') {
return callback(makeRequestError('Error connecting to download server at ' + url.host + ' (reset). Make sure you are online.', err.code));
}
return callback(err);
});
return req;
}
/**
* make a request a return JSON
* @param {string} location - URL to request
* @param {function} callback - Function to call when done
* @param {number} attempts - Number or attempts to make
* @return {request}
*/
function requestJSON(location, callback, attempts) {
return request(location, function (err, res, req) {
attempts = attempts || 1;
if (typeof (location) === 'object') {
location.attempts = attempts + 1;
}
debug('connection attempt %d of %d', attempts, MAX_RETRIES);
if (err) {
if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT' || err.message.indexOf('hang up') > 0) {
debug('connection error %s with message %s', err.code, err.message);
// retry again
if (attempts >= MAX_RETRIES) {
return callback(err);
}
return setTimeout(function () {
return requestJSON(location, callback, attempts + 1);
}, 500 * attempts);
}
return callback(err);
}
if (res && req.headers && req.headers['content-type'] && req.headers['content-type'].indexOf('/json') < 0) {
debug('response status code: %d with headers: %j', res.statusCode, res.headers);
// retry again
if (attempts >= MAX_RETRIES) {
return callback(err);
}
return setTimeout(function () {
return requestJSON(location, callback, attempts + 1);
}, 500 * attempts);
}
if (res.statusCode === 200) {
debug('response status code: %d with headers: %j', res.statusCode, res.headers);
var buf = '';
res.on('data', function (chunk) {
buf += chunk;
});
res.on('end', function () {
debug('attempting to parse JSON => [%s]', buf);
callback(null, JSON.parse(buf));
});
res.on('error', callback);
} else if (res.statusCode === 301 || res.statusCode === 302) {
debug('response status code: %d with headers: %j', res.statusCode, res.headers);
return requestJSON(res.headers.location, callback);
} else if (res && /^(400|404|408|500|502|503|504)$/.test(String(res.statusCode))) {
debug('response status code: %d with headers: %j', res.statusCode, res.headers);
attempts = attempts || 1;
if (attempts >= MAX_RETRIES) {
return callback(err);
}
return setTimeout(function () {
return requestJSON(location, callback, attempts + 1);
}, 500 * attempts);
} else {
return callback(new Error('Invalid response code: ' + res.statusCode + ' received from server.'));
}
});
}
/**
* right pad a string to a specific length
* @param {string} str - string to pad
* @param {number} len - number of spaces to pad
* @returns {string}
*/
function pad(str, len) {
var slen = stripAnsi(str).length;
var newstr = str;
for (var c = slen; c < len; c++) {
newstr += ' ';
}
return newstr;
}
/**
* returns true if directory is writable by user
* @param {string} dir - Directory to check
* @returns {boolean}
*/
function canWriteDir(dir) {
var del = !fs.existsSync(dir),
fn;
try {
if (del) {
ensureDir(dir);
}
if (fs.statSync(dir).isDirectory()) {
// create a temp file -- seems like the best way to handle cross platform
fn = path.join(dir, String(+new Date()) + (Math.random() * 3) + '.txt');
// console.log(fn);
fs.writeFileSync(fn, 'hi');
return true;
} else {
// not a directory but a file, sorry...
return false;
}
} catch (E) {
if (E.code === 'EACCES') {
return false;
}
console.log(E.stack, E.code);
} finally {
if (fs.existsSync(fn)) {
try {
fs.unlinkSync(fn);
} catch (ig) {
// ignore
}
}
if (del) {
try {
fs.unlinkSync(path);
} catch (ig) {
// ignore
}
}
}
}
/**
* if not writable, returns a message otherwise undefined
* @param {string} dir - Directory to check
* @param {string} name - Error name
* @returns {undefined|AppcError}
*/
function checkDirectory(dir, name) {
var message;
var errorlib = require('./error'),
chalk = chalk || require('chalk');
if (!canWriteDir(dir)) {
var chPer = 'Please make sure you change the permissions and re-try';
var chPerWithCmd = 'Please make sure you change the permissions using these commands:\n\n\t';
chPerWithCmd += chalk.yellow('sudo chown -R ' + process.env.USER + ' ' + dir + '\n\tchmod -R 0700 ' + dir);
message = process.platform === 'win32' ? chPer : chPerWithCmd + '\n';
return errorlib.createError('com.appcelerator.install.preflight.directory.unwritable', name, dir, message);
} else if (process.platform !== 'win32') {
// check the ownership of the directory too
var stat = fs.statSync(dir);
if (stat.uid !== process.getuid()) {
message = 'Please make sure you change the permissions using these commands:\n\n\t' + chalk.yellow('sudo chown -R ' + process.env.USER + ' ' + dir + '\n\tchmod -R 0700 ' + dir) + '\n';
return errorlib.createError('com.appcelerator.install.preflight.directory.ownership', name, dir, process.env.USER, message);
}
}
}
function abortMessage(name) {
// clear line and reset it
if (exports.stdout.isTTY) {
stopSpinner();
exports.stdout.clearLine();
exports.stdout.cursorTo(0);
}
exports.stdout.write(name + ' aborted.\n');
exports.exit(1);
}
function readConfig() {
if (cachedConfig) {
return cachedConfig;
}
var cf = getConfigFile();
if (!fs.existsSync(cf)) {
return null;
}
return (cachedConfig = JSON.parse(fs.readFileSync(cf)));
}
function writeConfig(config) {
cachedConfig = config;
var cf = getConfigFile();
fs.writeFileSync(cf, JSON.stringify(config, null, 2));
}
/**
* perform an update check to see if we have a new version
*
* however, some rules:
*
* - don't check each time
* - if specifying a version, skip
* - if specifying --quiet, skip
* - if no config, skip
* - if any failure in checking, skip
* - only do it once per day (or what is configured)
*
* @param {object} opts - Options
* @param {function} callback - Function to call when done
* @returns {undefined}
*/
function updateCheck(opts, callback) {
// we are specifying a version or we want quiet output, skip
if (opts.version || opts.quiet || opts.output === 'json') {
return callback();
}
// check to see if we have a config file and if we don't that's OK, return
// since we are in a setup/install
var config = readConfig();
if (!config) {
return callback();
}
chalk = chalk || require('chalk');
try {
var check = config.lastUpdateCheck;
var checkEveryMS = config.updateCheckInterval || 86400000; // once per day in MS
if (!check || check + checkEveryMS < Date.now()) {
// do the check below
debug('update check skipping, %d, %d', check, checkEveryMS);
} else {
// don't do the check
return callback();
}
} catch (E) {
// ignore errors, they will be dealt with otherwise
return callback();
}
var url = makeURL(opts, '/api/appc/list');
exports.requestJSON(url, function (err, result) {
// skip failures
if (!err && result) {
try {
var activeVersion = exports.getActiveVersion(),
resultList = result.key && result[result.key],
latest = result.latest || (resultList && (resultList.length > 0) && resultList[0].version);
debug('update check completed, latest is %s', latest);
// set the update check timestamp
config.lastUpdateCheck = Date.now();
// write out our config
writeConfig(config);
// see if we have it already
var found = exports.getInstallBinary(opts, latest);
// if not, inform the user of the update
debug('update check found %s', found);
if (!found && semver.lt(activeVersion, latest)) {
exports.stdout.write('A new update ' + chalk.yellow('(' + latest + ')') + ' is available... Download with ' + chalk.green('appc use ' + latest) + '\n');
}
} catch (E) {
// ignore
}
}
callback();
});
}
function isWindows() {
return process.platform === 'win32';
}
/**
* if a TTY is connected, clear all text on the line and reset the
* cursor to the beginning of the line
*/
function resetLine() {
if (exports.stdout.isTTY) {
exports.stdout.clearLine();
exports.stdout.cursorTo(0);
}
}
/**
* rmdirSyncRecursive method borrowed from wrench
*
* The MIT License
*
* Copyright (c) 2010 Ryan McGrath
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @param {string} _path - filepath
* @param {boolean} failSilent - Fail silently
* @returns {undefined}
*/
function rmdirSyncRecursive(_path, failSilent) {
var files;
try {
files = fs.readdirSync(_path);
} catch (err) {
if (failSilent) {
return;
}
throw new Error(err.message);
}
/* Loop through and delete everything in the sub-tree after checking it */
for (var i = 0; i < files.length; i++) {
var file = path.join(_path, files[i]);
var currFile = fs.lstatSync(file);
if (currFile.isDirectory()) {
// Recursive function back to the beginning
rmdirSyncRecursive(file);
} else if (currFile.isSymbolicLink()) {
// Unlink symlinks
if (isWindows()) {
fs.chmodSync(file, 666); // Windows needs this unless joyent/node#3006 is resolved..
}
fs.unlinkSync(file);
} else {
// Assume it's a file - perhaps a try/catch belongs here?
if (isWindows) {
fs.chmodSync(file, 666); // Windows needs this unless joyent/node#3006 is resolved..
}
fs.unlinkSync(file);
}
}
/* Now that we know everything in the sub-tree has been deleted, we can delete the main
directory. Huzzah for the shopkeep. */
return fs.rmdirSync(_path);
}
/**
* return an array of arguments appending any subsequent process args
* @param {string} args - arguments
* @param {object} opts - options
* @returns {array[]}
*/
function mergeOptsToArgs(args, _opts) {
var argv = [].concat(process.__argv.slice(3));
if (argv.length) {
for (var c = 0; c < argv.length; c++) {
var arg = argv[c];
args.push(arg);
}
}
return args;
}
/**
* returns the proxy to use, checks:
* 1. proxyServer setting from config
* 2. environmental variables (HTTP_PROXY, HTTPS_PROXY)
*
* to set proxyServer value:
* appc config set proxyServer '[proxy url]'
*
* @param {object} config - config object
* @returns {string}
*/
function getProxyServer(config) {
var proxy = null,
parsed;
if (config && config.proxyServer) {
parsed = urllib.parse(config.proxyServer);
if (/^https?:$/.test(parsed.protocol) && parsed.hostname && parsed.hostname !== 'null') {
proxy = config.proxyServer;
}
}
return proxy
|| process.env.HTTP_PROXY
|| process.env.http_proxy
|| process.env.HTTPS_PROXY
|| process.env.https_proxy
|| '';
}
/**
* return whether or not to do SSL key validation when making https requests.
*
* to set stricSSL value:
* appc config set strictSSL [false/true]
*
* @param {object} config - config object
* @return {boolean|null}
*/
function getStrictSSL(config) {
return config ? config.strictSSL : null;
}
/**
* return the path to a file containing one or multiple Certificate Authority signing certificates.
*
* to set cafile value:
* appc config set cafile '[file path, in PEM format]'
*
* @param {object} config - config object
* @return {string|null}
*/
function getCAfile(config) {
if (config && config.cafile && fs.existsSync(config.cafile)) {
return config.cafile;
}
return null;
}
/**
* write out the process.versions info stored when installing package
*
* @param {string} pkgDir - package directory
*/
function writeVersions(pkgDir) {
var versionsFile = path.join(pkgDir, '.nodeversions'),
versions = process.versions,
versionsStr = JSON.stringify(versions);
debug('writing node version: %s to %s', versionsStr, versionsFile);
if (fs.existsSync(versionsFile)) {
fs.unlinkSync(versionsFile);
}
fs.writeFileSync(versionsFile, versionsStr);
// remove old file
var oldVersionFile = path.join(pkgDir, '.nodeversion');
if (fs.existsSync(oldVersionFile)) {
fs.unlinkSync(oldVersionFile);
}
}
/**
* return the process.versions info when install the package
*
* @param {string} installBin - install binary location
* @returns {object}
*/
function readVersions(installBin) {
var versionFile = path.join(installBin, '..', '..', '..', '.nodeversions'),
versions;
if (fs.existsSync(versionFile)) {
try {
versions = JSON.parse(fs.readFileSync(versionFile));
} catch (e) {
debug('unable to read versions file.');
}
return versions;
}
}
/**
* check if the minor/major NodeJS version changed since the package was installed
*
* @param {string} installBin - path to install binary
* @return {boolean}
*/
function isNodeVersionChanged(installBin) {
var version = getPackageNodeVersion(installBin),
usedNode = version && version.split('.'),
currentNode = process.version.split('.'),
result = false;
if (usedNode && usedNode.length >= 2 && currentNode.length >= 2) {
result = !(usedNode[0] === currentNode[0] && usedNode[1] === currentNode[1]);
}
debug('node used %s, current version %s, result: %s', usedNode, currentNode, result);
return result;
}
/**
* check if the modules version changed since the package was installed
*
* @param {string} installBin - path to install binary
* @return {boolean}
*/
function isModuleVersionChanged(installBin) {
var versions = readVersions(installBin),
usedVersion = versions && versions.modules,
currentModuleVersion = process.versions && process.versions.modules,
result = false;
if (usedVersion && currentModuleVersion) {
result = (usedVersion !== currentModuleVersion);
debug('modules version used %s, current version %s, result: %s', usedVersion, currentModuleVersion, result);
} else {
result = isNodeVersionChanged(installBin);
}
return result;
}
/**
* return the NodeJS version used to install the package
*
* @param {string} installBin - path to install binary
* @return {string}
*/
function getPackageNodeVersion(installBin) {
var versions = readVersions(installBin),
usedNodeVersion = versions && versions.node;
if (usedNodeVersion) {
return usedNodeVersion;
}
var versionFile = path.join(installBin, '..', '..', '..', '.nodeversion');
if (fs.existsSync(versionFile)) {
return fs.readFileSync(versionFile).toString().trim();
}
}
function outputInfo(msg, isJSON) {
if (isJSON) {
return;
}
exports.stdout.write(msg);
}
function restartDaemon(version, installBin, quiet) {
var pkgFile = path.join(getInstallDir(), version, 'package', 'package.json');
var pkg = fs.existsSync(pkgFile) && require(pkgFile);
if (isWindows()) {
installBin = '"' + process.execPath + '" "' + installBin + '"';
}
if (pkg && 'appcd' in pkg.dependencies) {
if (!quiet) {
waitMessage('Restarting appcd ');
}
debug('stop appcd');
try {
execSync(installBin + ' appcd restart');
okMessage();
} catch (error) {
// ignore
debug('error killing the daemon');
debug(error);
}
}
}
function installPlugins (version, installBin, quiet) {
// We only need to install plugins from 8.2.0 onwards
const cleanVersion = semver.coerce(version);
if (semver.lt(cleanVersion, '8.2.0')) {
return;
}
var pkgFile = path.join(getInstallDir(), version, 'package', 'package.json');
var pkg = fs.existsSync(pkgFile) && require(pkgFile);
if (pkg && 'appcd' in pkg.dependencies) {
if (!quiet) {
waitMessage('Installing appcd plugins ');
}
debug('installing appcd plugins');
const { code, stderr, stdout } = runAppcdCommand([ 'pm', 'install', 'default' ], installBin);
debug('pm install output');
debug(stderr);
if (code) {
// ignore
debug('error installing plugins');
debug(stdout);
} else {
okMessage();
}
}
}
function runAppcdCommand (command, installBin) {
if (isWindows()) {
installBin = '"' + process.execPath + '" "' + installBin + '"';
}
debug(`running appcd ${command}`);
return spawnSync(installBin, [ 'appcd', ...command ], { shell: true });
}
function checkNodeVersion (supportedNodeRange) {
if (!semver.satisfies(process.version, supportedNodeRange)) {
chalk = chalk || require('chalk');
console.log(chalk.cyan('Appcelerator Command-Line Interface'));
console.log('Copyright (c) 2014-' + (new Date().getFullYear()) + ', Appcelerator, Inc. All Rights Reserved.');
console.log('');
console.log(chalk.red('ERROR: appc requires Node.js ' + semver.validRange(supportedNodeRange)));
console.log('Visit ' + chalk.cyan('http://nodejs.org/') + ' to download a newer version.');
console.log('');
exports.exit(1);
}
}
exports.checkNodeVersion = checkNodeVersion;
exports.getAppcDir = getAppcDir;
exports.getHomeDir = getHomeDir;
exports.getCacheDir = getCacheDir;
exports.getConfigFile = getConfigFile;
exports.getNpmCacheDirectory = getNpmCacheDirectory;
exports.ensureDir = ensureDir;
exports.expandPath = expandPath;
exports.getInstallDir = getInstallDir;
exports.listVersions = listVersions;
exports.getVersionJson = getVersionJson;
exports.getInstalledVersions = getInstalledVersions;
exports.getInstallBinary = getInstallBinary;
exports.fail = fail;
exports.parseOpts = parseOpts;
exports.parseArgs = parseArgs;
exports.writeVersion = writeVersion;
exports.getActiveVersion = getActiveVersion;
exports.makeURL = makeURL;
exports.request = request;
exports.requestJSON = requestJSON;
exports.pad = pad;
exports.waitMessage = waitMessage;
exports.okMessage = okMessage;
exports.infoMessage = infoMessage;
exports.stopSpinner = stopSpinner;
exports.startSpinner = startSpinner;
exports.canWriteDir = canWriteDir;
exports.checkDirectory = checkDirectory;
exports.abortMessage = abortMessage;
exports.updateCheck = updateCheck;
exports.isWindows = isWindows;
exports.resetLine = resetLine;
exports.rmdirSyncRecursive = rmdirSyncRecursive;
exports.getRequest = getRequest;
exports.mergeOptsToArgs = mergeOptsToArgs;
exports.getInstallTag = getInstallTag;
exports.getProxyServer = getProxyServer;
exports.getStrictSSL = getStrictSSL;
exports.getCAfile = getCAfile;
exports.readConfig = readConfig;
exports.writeVersions = writeVersions;
exports.isModuleVersionChanged = isModuleVersionChanged;
exports.getPackageNodeVersion = getPackageNodeVersion;
exports.outputInfo = outputInfo;
exports.restartDaemon = restartDaemon;
exports.installPlugins = installPlugins;