appc-cli-titanium
Version:
Titanium CLI Plugin
873 lines (770 loc) • 28.4 kB
JavaScript
/**
* 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-extra'),
path = require('path'),
exec = require('child_process').exec, // eslint-disable-line security/detect-child-process
tiappxml = require('tiapp.xml'),
util = require('./util'),
platform = require('appc-platform-sdk'),
chalk = require('chalk'),
semver = require('semver');
var TYPE = require('../appc').TYPE,
optsMap = require('../appc').OPTS_MAP;
module.exports = {
type: TYPE,
order: 100,
subtype: 'app', // this can be either 'app' or 'server'
skipACS: true,
name: 'Titanium SDK (JavaScript)',
fields: function (appc, opts) {
var questionList = [],
projTiapp;
var questions = {
projectName: {
type: 'input',
name: 'name',
message: 'What\'s the project name?',
validate: function (input) {
// validate this still works
var rx = /^[\w\u00C0-\u017F]{2,}[\w\u00C0-\u017F.-]+$/;
return rx.test(input) || 'project name is invalid';
},
flags: '-n, --name <name>',
description: 'name of the project'
},
projectFolder: {
type: 'input',
name: 'project-dir',
message: 'Where\'s the project directory?',
validate: function (input) {
var result = isInProjectDir(input);
if (result) {
projTiapp = getProjectTiapp(input);
questions.projectName.default = projTiapp && projTiapp.name;
}
return result || 'project directory does not exist';
},
flags: '-d, --project-dir <value>',
description: 'the directory containing the project [default: .]'
},
applicationId: appc.fields.id()
};
if (opts && opts.type === 'applewatch') {
if (!isInProjectDir()) {
questions.projectFolder.message = 'What\'s the Titanium project directory?';
questionList.push(questions.projectFolder);
} else {
projTiapp = getProjectTiapp();
questions.projectName.default = projTiapp && projTiapp.name;
}
questions.projectName.message = 'What\'s the WatchOS project name?';
questionList.push(questions.projectName);
} else {
questionList.push(questions.projectName);
questionList.push(questions.applicationId);
}
if (opts && opts.type === 'timodule') {
// if a --sdk flag is passed in then use that, otherwise fallback to the activeSDK the appc cli looks up
const sdkVersion = opts.sdk || opts.activeSDK;
if (sdkVersion) {
const coercedSDKVersion = semver.coerce(sdkVersion);
if (coercedSDKVersion && semver.gte(coercedSDKVersion, '9.1.0')) {
questionList.push({
type: 'list',
name: 'androidCodeBase',
message: 'Android code base language',
choices: [
{ name: 'Java', value: 'java' },
{ name: 'Kotlin', value: 'kotlin' }
],
flags: '--android-code-base <language>',
default: 'java',
description: 'Android code base language',
when: () => {
// Don't prompt --platforms was passed and doesn't include android
const platforms = opts.platforms && opts.platforms.split(',');
if (platforms && !platforms.includes('android')) {
return false;
}
return true;
}
});
questionList.push({
type: 'list',
name: 'iosCodeBase',
message: 'iOS code base language',
choices: [
{ name: 'Objective-C', value: 'objc' },
{ name: 'Swift', value: 'swift' }
],
default: 'objc',
flags: '--ios-code-base <language>',
description: 'iOS code base language',
when: () => {
// Don't prompt --platforms was passed and doesn't include and ios related value
const platforms = opts.platforms && opts.platforms.split(',');
if (platforms && (!platforms.includes('ios') && !platforms.includes('iphone') && !platforms.includes('ipad'))) {
return false;
}
return true;
}
});
}
}
}
return questionList;
},
command: function (program, appc) {
if (program.type !== 'applewatch') {
var ID = appc.fields.id();
program.option(ID.flags, ID.description);
}
program.option('-p, --platforms <platforms>', 'platforms for titanium project');
program.option('-u, --url <url>', 'url of the project');
program.option('--sdk <sdk>', 'Titanium SDK version to use to bootstrap project creation');
program.option('--template <template>', 'the name of the project template');
program.option('--no-services', 'skip registering appcelerator platform services for titanium project');
program.option('--classic', 'creates a titanium classic project');
program.option('--ng', 'create a titanium angular project');
program.option('--android-code-base <language>', 'the code base of the Android module project');
program.option('--ios-code-base <language>', 'the code base of the iOS module project');
},
execute: execute,
provision: provision,
provisioned: provisioned
};
function isInProjectDir(projectDir) {
var dir = projectDir || process.cwd(),
file = path.join(dir, 'tiapp.xml');
return fs.existsSync(file);
}
function getProjectTiapp(projectDir) {
var dir = projectDir || process.cwd(),
file = path.join(dir, 'tiapp.xml'),
tiapp;
if (fs.existsSync(file)) {
tiapp = tiappxml.load(file);
}
return tiapp;
}
function generateSample(appc, opts, callback) {
//
// generate the sample application
//
// right now this only generates the basic corporate directory application
// need to better hook up the data bindings based on what you've choosen
// for your backend
//
var templateDir = path.join(__dirname, '..', 'template', 'corpdir');
fs.copySync(templateDir, opts.projectDir);
callback();
}
function execute(appc, args, opts, callback) {
var spinner = appc.spinner,
tiPath = path.join(opts.projectDir ? path.dirname(opts.projectDir) : process.cwd(), opts.name),
workspace = path.dirname(tiPath);
opts.projectDir = opts.projectDir || tiPath;
var platforms = opts.platforms || 'all';
var type = opts.type || 'app';
if (type === 'titanium') {
type = 'app';
} else if (type === 'timodule') {
// this needs to be 'module' since it gets passed to the titanium cli
// the titanium cli syntax for defining a module is 'module' not 'timodule'
type = 'module';
}
// If we're not creating a classic or angular project, and a template has been provided that
// starts with webpack, then we want to have the titanium CLI create the default webpack
// template so that we have the initial necessary dependencies installed
var templateName = opts.template || null;
var classicTemplateName;
if (!(opts.classic || opts.ng) && templateName && templateName.includes('webpack')) {
// we're creating an alloy project that has a webpack based template, so we want to force
// ti create to create the default webpack template for us we delete template from opts so
// it doesn't get copied over by the argument translation further down
classicTemplateName = 'webpack-default';
delete opts.template;
} else if (!(opts.classic || opts.ng) && templateName) {
// we're not creating a webpack based alloy project, but we still don't want the
// template to be set during ti create
delete opts.template;
} else if (opts.classic) {
// we're creating a classic app, so lets pass it through
classicTemplateName = templateName;
}
args = [ 'create', '-t', type, '--no-banner', '--no-prompt' ];
if (type === 'applewatch') {
workspace = opts['project-dir'] ? path.resolve(opts['project-dir']) : workspace;
args.push('--name', opts.name, '--project-dir', workspace);
} else {
args.push('--platforms', platforms, '--workspace-dir', workspace);
}
// translate opts back to arguments since titanium can only be
// invoked via CLI (there's no module interface)
Object.keys(opts).forEach(function (opt) {
if (opt === 'register' || opt === 'pluginPaths' || opt === 'configFn'
|| opt === 'session' || opts === 'colors' || optsMap[opt] === undefined) {
return;
}
if (args.indexOf(optsMap[opt]) === -1) {
args.push(optsMap[opt]);
}
if (opt !== 'force' || opt !== 'services') {
args.push(opts[opt]);
}
});
if (!opts.logLevel) {
args.push('--log-level', 'error');
}
if (!opts.url) {
args.push('--url', 'unspecified');
}
if (!opts.force && fs.existsSync(path.join(opts.projectDir, 'tiapp.xml'))) {
// There is no force option, so user might want to only enable services on an existing project.
return callback();
}
if (opts.ng) {
args.push('--template', 'angular-default');
}
if (opts.type === 'timodule' && opts.androidCodeBase) {
args.push('--android-code-base', opts.androidCodeBase);
}
if (opts.type === 'timodule' && opts.iosCodeBase) {
args.push('--ios-code-base', opts.iosCodeBase);
}
if (type === 'app' && classicTemplateName) {
args.push('--template', classicTemplateName);
}
// make sure workspace exists
workspace && fs.ensureDirSync(workspace);
// launch titanium for new project
spinner.start();
util.launchTi(appc, args, { stdio: [ process.stdin, 'pipe', 'pipe' ], cwd: workspace }, function (err, ti) {
if (err) {
return callback(err);
}
var tiOutput = new RegExp('\\[(INFO)\\]\\s*(.*)');
ti.stdout.on('data', function (buf) {
var result = tiOutput.exec(buf.toString());
if (result && opts.logLevel === 'info') {
appc.log.info(chalk.green('[' + result[1] + ']'), result[2]);
} else {
appc.log.info.wrap(buf);
}
});
ti.stderr.on('data', appc.log.error.wrap);
ti.on('exit', function (code) {
if (code) {
spinner.stop();
return callback(new Error('titanium exited with non-zero exit code (' + code + ')'));
}
// get the project dir right
if (tiPath !== opts.projectDir) {
fs.removeSync(opts.projectDir);
fs.renameSync(tiPath, opts.projectDir);
}
// if a classic app, angular app, applewatch app or module, skip alloy setup
if (opts.classic || opts.ng || opts.type === 'timodule' || opts.type === 'applewatch') {
spinner.stop();
return callback();
}
// generate alloy project
var alloyBin = path.resolve(path.dirname(require.resolve('alloy')) + '/../bin/alloy'),
cmd = '"' + process.execPath + '" "' + alloyBin + '" new --force';
if (opts.testapp) {
cmd += ' --testapp ' + opts.testapp;
}
if (templateName) {
cmd += ' . ' + templateName;
}
appc.log.trace(cmd);
exec(cmd, { cwd: opts.projectDir }, function (err, stdout, stderr) {
appc.log.trace('alloy returned error=', err, 'stdout=', stdout, 'stderr=', stderr);
if (err) {
spinner.stop();
stdout && appc.log.trace(stdout);
stderr && appc.log.error(stderr);
return callback(err);
}
spinner.stop();
if (opts.sample) {
return generateSample(appc, opts, callback);
} else {
callback();
}
});
});
});
}
/**
* called to provision the application with the platform
*/
function provision(appc, opts, config, callback) {
var tiapp = path.join(opts.projectDir || path.join(process.cwd(), opts.name), 'tiapp.xml');
if (fs.existsSync(tiapp)) {
config.tiapp = tiapp;
}
return callback();
}
function getEntitlements(appc, opts, callback) {
var result = appc.commands.config.get('entitlements');
return callback(null, result);
}
function getSDKInfo(appc, opts, callback) {
if (module.exports.sdkInfo) {
return callback(null, module.exports.sdkInfo);
}
// Run ti sdk -o json command to find out the SDK location.
var cmd = '"' + process.execPath + '" "' + util.getTiBinary() + '" sdk -o json';
exec(cmd, function (err, stdout) {
if (err) {
appc.log.error(err);
return callback(err);
}
var result = JSON.parse(stdout.trim());
// Find the SDK to be used for creating this app.
var sdk = opts.sdk || result.activeSDK;
module.exports.sdkInfo = {
activeSDK: sdk,
sdkLocation: result.defaultInstallLocation
};
return callback(null, module.exports.sdkInfo);
});
}
/**
* called when a component has been provisioned
*/
function provisioned(appc, type, opts, config, callback) {
appc.log.debug('titanium provisioned called', type);
var cacheModDownloadInfo;
var isImport = opts.import || false;
if (type === 'completed' && config.tiapp) {
appc.async.series([
function (cb) {
var tiapp, ti;
if (!opts.enableServices) {
return cb();
}
tiapp = config.tiapp;
ti = tiappxml.load(tiapp);
appc.log.trace('guid: ', ti.guid);
// Check if the app exists
platform.createRequest(config.session, '/api/v1/app/' + ti.guid, function (err, result) {
appc.log.trace('check app exists err: ', err);
if (err && err.code === 404) {
// force gen new guid
isImport = true;
}
cb();
});
},
// create the App using the tiapp guid
function (cb) {
var tiapp = config.tiapp,
ti = tiappxml.load(tiapp);
appc.log.trace('sending app post', tiapp);
appc.log.debug('isImport: ', isImport);
platform.App.updateTiApp(config.session, opts.session.org_id, ti.toString(), { import: isImport }, function (err, response) {
if (err) {
return cb(err);
}
appc.log.trace('app post response', response);
var body = appc.lodash.isString(response) ? JSON.parse(response) : response;
if (!body) {
return callback(new Error('invalid response from server attempting to register the application. please re-try your request again or contact Appcelerator Support.'));
}
var ti = tiappxml.load(tiapp);
ti.guid = body.app_guid;
ti.setProperty('appc-app-id', body._id, 'string');
config.ti = ti;
config.app = body;
// save tiapp
ti.write();
cb();
});
},
// copy over bundle alloy hook
function (cb) {
if (!opts.import || opts.ng) {
return cb();
}
var projAlloyHookFile = path.resolve(path.join(path.dirname(config.tiapp), 'plugins', 'ti.alloy', 'hooks', 'alloy.js'));
var alloyHookFile = path.resolve(path.dirname(require.resolve('alloy')) + '/../hooks/alloy.js');
if (fs.existsSync(projAlloyHookFile) && fs.existsSync(alloyHookFile)) {
appc.log.trace('copy over alloy.js hook file from bundled alloy');
fs.copySync(alloyHookFile, projAlloyHookFile);
}
cb();
},
function (cb) {
getEntitlements(appc, opts, function (err, entitlements) {
if (err) {
appc.log.error('Failed to read entitlements', err);
}
opts.entitlements = entitlements;
cb();
});
},
// Sync Titanium downloads
function (cb) {
util.syncTiDownloads(appc, function (err, data) {
// if err , we should continue with the project creation.
cacheModDownloadInfo = data;
return cb();
});
},
// Get module/plugin versions data
function (cb) {
if (cacheModDownloadInfo) {
return cb();
}
util.listTiDownloads(appc, function (err, downloads) {
if (err) {
appc.log.warn('Failed to download module data: ' + err);
}
cacheModDownloadInfo = downloads;
return cb();
});
},
// setup SDK information
function (cb) {
getSDKInfo(appc, opts, function (err, sdk) {
if (err) {
appc.log.warn('Unable to get SDK information: ' + err);
}
cb();
});
},
// check if we should enable hyperloop
function (cb) {
if (opts.services !== undefined && !opts.services) {
// if they've asked for no services, dont prompt to ask if they want hyperloop
opts.enableHyperloop = false;
return cb();
}
if (opts.promptType !== 'socket-bundle' && !opts.prompt) {
opts.enableHyperloop = true;
return cb();
}
if (opts.template && opts.template.includes('webpack')) {
opts.enableHyperloop = false;
return cb();
}
appc.spinner.stop();
appc.inquirer.prompt([
{
type: 'confirm',
name: 'enableHyperloop',
message: 'Would you like to enable native API access with Hyperloop for this app?',
default: true
}
], promptOpts(opts), function (err, answer) {
if (err) {
appc.log.error(err);
return cb();
}
opts.enableHyperloop = answer.enableHyperloop;
appc.spinner.start();
return cb();
});
},
// Enable all services for the app
function (cb) {
// If the command contains '--no-services', then we shouldn't register services.
// TODO: remove this, make each service dependent on its own check
if (opts.services !== undefined && !opts.services) {
appc.log.info('Skipped registration of Appcelerator Services.');
return cb();
}
appc.async.parallel([
// enable hyperloop
function (cb) {
var platforms = util.getNormalizedPlatforms(opts);
if (platforms.length === 0 && opts.platforms !== 'all') {
return cb();
}
var tiapp = config.tiapp,
ti = tiappxml.load(tiapp),
sdk = module.exports.sdkInfo;
appc.async.waterfall([
function (next) {
if (!opts.enableHyperloop || !sdk) {
return cb();
}
var sdkLocation = sdk.sdkLocation;
if (!fs.existsSync(sdkLocation)) {
appc.log.error('SDKs are not available at ', sdkLocation);
appc.log.error('Failed to enable Hyperloop for ', config.ti.name);
return cb();
}
appc.log.trace('Using SDK from location ', sdkLocation);
return next();
},
function (next) {
appc.log.debug('Adding Hyperloop modules for ', platforms);
platforms.forEach(function (platform) {
ti.setModule('hyperloop', null, platform);
});
config.ti = ti;
ti.write();
return next();
}
], cb);
},
// enable crash if user is entitled
function (cb) {
if (!opts.entitlements
|| (opts.entitlements.partners
&& opts.entitlements.partners.indexOf('aca') === -1)) {
appc.log.debug('No entitlement, skip Performance service.');
return cb();
}
appc.log.debug('Enabling Performance service', config.ti.name, config.ti.guid);
var ACA_ID = 'com.appcelerator.aca';
var platforms = util.getNormalizedPlatforms(opts);
appc.async.waterfall([
function (next) {
// Get the SDK information and setup
getSDKInfo(appc, opts, function (err, sdk) {
if (err) {
appc.log.error(err);
return cb();
}
const sdkLocation = sdk.sdkLocation;
if (!fs.existsSync(sdkLocation)) {
appc.log.error('SDKs are not available at ', sdkLocation);
appc.log.error('Failed to register Performance service for', config.ti.name);
return cb();
}
appc.log.trace('Using SDK from location ', sdkLocation);
const info = {
sdkLocation,
activeSDK: sdk.activeSDK,
};
if (cacheModDownloadInfo.modules) {
const moduleInfo = cacheModDownloadInfo.modules.find(mod => mod.id === ACA_ID);
info.moduleInfo = moduleInfo;
}
return next(null, info);
});
},
function (environmentInformation, next) {
// Check if we have the module installed
const { sdkLocation, activeSDK, moduleInfo } = environmentInformation;
const installInformation = {
id: ACA_ID
};
const latestCompatibleMod = util.getCompatibleModuleDownloadInfo(activeSDK, moduleInfo);
let latestToDownloadVersion;
if (latestCompatibleMod) {
latestToDownloadVersion = latestCompatibleMod.version;
installInformation.url = latestCompatibleMod.url;
installInformation.version = latestCompatibleMod.version;
}
installInformation.installed = true;
platforms.forEach(function (platform) {
const modulePath = path.join(sdkLocation, 'modules', platform, ACA_ID);
const version = util.detectAddonVersion(appc, modulePath, ACA_ID, platform, activeSDK, latestToDownloadVersion);
if (version) {
installInformation[platform] = version;
} else {
installInformation.installed = false;
}
});
return next(null, installInformation, sdkLocation);
},
function (modInfo, sdkLocation, next) {
// If it's not installed, install it
if (modInfo.installed) {
return next(null, modInfo);
}
appc.log.debug('Install SDK compatible performance module ' + (modInfo.version));
// download Performance module if not installed.
util.installTiModule(appc, modInfo, sdkLocation, function () {
return next(null, modInfo);
});
},
function (modUse, next) {
// Set it in the tiapp
var tiapp = config.tiapp;
var ti = tiappxml.load(tiapp);
var modId = modUse.id;
appc.log.debug('Adding Performance modules for ', platforms);
platforms.forEach(function (platform) {
ti.setModule(modId, null, platform);
});
config.ti = ti;
ti.write();
next();
}
], cb);
},
// provisioning ACS
// we do this here instead of the letting provisioning do it by setting
// the plugin property 'skipACS' above. we do this because we want to
// register the app above using the tiapp.xml and then we link the app guid
// with ACS below
function (cb) {
appc.log.trace('calling createACSApp', config.ti.name, config.ti.guid);
appc.provisioning.createACSApp(config.session, config.ti.name, config.ti.guid, function (err, apps, usernames, passwords) {
if (err && !err.doNotFail) {
return cb(err);
} else if (err && err.doNotFail) {
return cb();
}
appc.log.trace('ACS apps created', apps);
var ti = config.ti,
meta = { acsbase: {}, keys: {}, usernames: {}, passwords: {} },
currentACSkeys = {
production: config.ti.getProperty('acs-api-key-production'),
development: config.ti.getProperty('acs-api-key-development')
};
Object.keys(apps).forEach(function (envName) {
var env = apps[envName];
var authbaseurl = env.acsAuthBaseUrl;
var baseurl = env.acsBaseUrl;
meta.acsbase[envName] = baseurl;
// set the base urls
ti.setProperty('acs-authbase-url-' + envName, authbaseurl, 'string');
ti.setProperty('acs-base-url-' + envName, baseurl, 'string');
ti.setProperty('acs-oauth-secret-' + envName, env.oauth_secret, 'string');
ti.setProperty('acs-oauth-key-' + envName, env.oauth_key, 'string');
ti.setProperty('acs-api-key-' + envName, env.key, 'string');
meta.keys[envName] = env.key;
// username / passwords
ti.setProperty('acs-username-' + envName, usernames[envName], 'string');
ti.setProperty('acs-password-' + envName, passwords[envName], 'string');
meta.usernames[envName] = usernames[envName];
meta.passwords[envName] = passwords[envName];
});
if (currentACSkeys.production && (currentACSkeys.production !== meta.keys.production)
|| currentACSkeys.development && (currentACSkeys.development !== meta.keys.development)) {
appc.spinner.stop();
return appc.inquirer.prompt([
{
type: 'confirm',
name: 'replaceACSkeys',
message: 'The app has ArrowDB (previously known as ACS) keys already defined. \n Would you like to replace them? Refer to http://appcel.us/1Gcby7G for more details.',
default: false
}
], promptOpts(opts), function (err, answers) {
if (err) {
return cb(err);
}
if (answers.replaceACSkeys) {
appc.log.trace('acs keys exist, replace acs keys');
return doUpdateTi(opts, cb);
} else {
appc.log.trace('acs keys exist, skip replacing acs keys');
return cb();
}
});
} else {
return doUpdateTi(opts, cb);
}
function doUpdateTi(opts, cb) {
// set the org information for this app
ti.setProperty('appc-org-id', config.org_id, 'string');
ti.setProperty('appc-creator-user-id', config.session.user.guid, 'string');
config.acs = meta;
// set the ti.cloud module
ti.setModule('ti.cloud', null, 'commonjs');
ti.write();
if (opts.import || opts.enableServices || opts.ng) {
return cb();
}
// write out the ti.cloud sample code
var fn;
if (opts.classic) {
// webpack projects reside in src and use main.js as their app.js equivalent
if (opts.template && opts.template.includes('webpack')) {
fn = path.join(path.dirname(config.tiapp), 'src', 'main.js');
} else {
fn = path.join(path.dirname(config.tiapp), 'Resources', 'app.js');
}
} else {
fn = path.join(path.dirname(config.tiapp), 'app', 'alloy.js');
}
fs.readFile(fn, function (err, body) {
if (err) {
return cb(err);
}
body = body.toString();
body += '\n';
body += '// added during app creation. this will automatically login to\n';
body += '// ACS for your application and then fire an event (see below)\n';
body += '// when connected or errored. if you do not use ACS in your\n';
body += '// application as a client, you should remove this block\n';
body += '(function () {\n';
body += '\tconst ACS = require(\'ti.cloud\');\n';
body += '\tconst env = Ti.App.deployType.toLowerCase() === \'production\' ? \'production\' : \'development\';\n';
// eslint-disable-next-line no-template-curly-in-string
body += '\tconst username = Ti.App.Properties.getString(`acs-username-${env}`);\n';
// eslint-disable-next-line no-template-curly-in-string
body += '\tconst password = Ti.App.Properties.getString(`acs-password-${env}`);\n';
body += '\n';
body += '\t// if not configured, just return\n';
body += '\tif (!env || !username || !password) {\n\t\treturn;\n\t}\n';
body += '\t/**\n';
body += '\t * Appcelerator Cloud (ACS) Admin User Login Logic\n';
body += '\t *\n';
body += '\t * fires login.success with the user as argument on success\n';
body += '\t * fires login.failed with the result as argument on error\n';
body += '\t */\n';
body += '\tACS.Users.login({\n';
body += '\t\tlogin: username,\n';
body += '\t\tpassword: password,\n';
body += '\t}, function (result) {\n';
body += '\t\tif (env === \'development\') {\n';
// eslint-disable-next-line no-template-curly-in-string
body += '\t\t\tTi.API.info(`ACS Login Results for environment ${env}`);\n';
body += '\t\t\tTi.API.info(result);\n';
body += '\t\t}\n';
body += '\t\tif (result && result.success && result.users && result.users.length) {\n';
body += '\t\t\tTi.App.fireEvent(\'login.success\', result.users[0], env);\n';
body += '\t\t} else {\n';
body += '\t\t\tTi.App.fireEvent(\'login.failed\', result, env);\n';
body += '\t\t}\n';
body += '\t});\n\n';
body += '}());\n';
body += '\n';
// write the contents and then finish
fs.writeFile(fn, body, cb);
});
}
});
}
], cb);
},
function (cb) {
appc.log.trace('finally update tiapp');
var tiapp = config.tiapp,
ti = tiappxml.load(tiapp);
platform.App.updateTiApp(config.session, opts.session.org_id, ti.toString(), function (err, response) {
if (err) {
appc.log.error(err);
}
if (response && response.message) {
appc.log.warn(response.message);
}
cb();
});
}
], callback);
} else {
callback();
}
}
function promptOpts(opts) {
return {
socket: opts.promptType === 'socket' || opts.promptType === 'socket-bundle',
port: opts.promptPort,
bundle: opts.promptType === 'socket-bundle'
};
}