ionic
Version:
A tool for creating and developing Ionic Framework mobile apps.
1,194 lines (939 loc) • 33.9 kB
JavaScript
require('colors');
var fs = require('fs');
var os = require('os');
var request = require('request');
var path = require('path');
var shelljs = require('shelljs');
var parseUrl = require('url').parse;
var argv = require('optimist').boolean(['no-cordova', 'sass', 'list']).argv;
var prompt = require('prompt');
var Q = require('q');
var open = require('open');
var xml2js = require('xml2js');
var ProgressBar = require('progress');
var IonicProject = require('./project');
var IonicStore = require('./store').IonicStore;
var Utils = require('./utils');
var Cordova = require('./cordova');
var Hooks = require('./hooks');
var ioLib = require('./io-config');
var log = require('./logging').logger;
var IonicResources = require('./resources');
var State = require('./state');
var seedType = 'ionic-starter';
var Start = module.exports;
var IONIC_DASH = 'https://apps.ionic.io';
var IONIC_CREATOR_API_URL = 'https://creator.ionic.io/api/v1';
var DEFAULT_APP = {
plugins: [
'cordova-plugin-device',
'cordova-plugin-console',
'cordova-plugin-whitelist',
'cordova-plugin-splashscreen',
'cordova-plugin-statusbar',
'ionic-plugin-keyboard'
],
sass: false
};
var V2_STARTERS = {
blank: { repoName: 'ionic2-starter-blank' },
tabs: { repoName: 'ionic2-starter-tabs' },
sidemenu: { repoName: 'ionic2-starter-sidemenu' },
conference: { repoName: 'ionic-conference-app' },
tutorial: { repoName: 'ionic2-starter-tutorial' }
};
Start.runSpawnCommand = function runSpawnCommand(cmd, args) {
var q = Q.defer();
var command = cmd + args.join(' ');
var spawn = require('cross-spawn-async');
log.debug('Running exec command:', command);
var spawned = spawn(cmd, args, { stdio: 'inherit' });
spawned.on('error', function(err) {
Utils.fail('Unable to run spawn command' + err);
});
spawned.on('exit', function(code) {
log.debug('Spawn command completed');
if (code !== 0) {
return q.reject('There was an error with the spawned command: ' + command);
}
return q.resolve();
});
return q.promise;
};
// Options for startApp:
// {
// appDirectory: 'IonicApp',
// appName: 'Test',
// packageName: 'com.ionic.test,
// isCordovaProject: true,
// template: 'tabs',
// targetPath: '/User/Path/Development/'
// }
Start.startApp = function startApp(options) {
if (typeof options != 'object' || typeof options == 'undefined') {
throw new Error('You cannot start an app without options');
}
if (typeof options.targetPath == 'undefined' || options.targetPath === '.') {
throw new Error('Invalid target path, you may not specify \'.\' as an app name');
}
ioLib.warnMissingData();
var createMessage = ['Creating Ionic app in folder ', options.targetPath, ' based on ',
options.template.bold, ' project'].join('');
var errorWithStart = false;
log.info(createMessage);
return Start.fetchWrapper(options)
.then(function() {
return Start.fetchSeed(options);
})
.then(function() {
if (!options.skipNpm) {
log.info('Installing npm packages...');
return Start.runSpawnCommand('npm', ['install']);
}
})
.then(function() {
return Start.loadAppSetup(options);
})
.then(function(appSetup) {
if (options.isCordovaProject && !options.isGui) {
return Start.initCordova(options, appSetup);
} else if (options.isCordovaProject && options.isGui) {
return Start.initCordovaFromGui(options, appSetup);
}
})
.catch(function(ex) {
errorWithStart = true;
throw ex;
})
.then(function() {
if (options.isCordovaProject) {
// Check if iOS - if so, add platform iOS
return Start.addDefaultPlatforms(options);
}
})
.catch(function(ex) {
if (errorWithStart) {
log.error('Error with start', ex.stack);
throw ex;
}
// Might be a default error - ios already added. Ignore it.
})
.then(function() {
if (options.isCordovaProject) {
return Start.updateConfigXml(options.targetPath, options.packageName,
options.appName, options.ios, options.android);
}
})
.then(function() {
return Start.finalize(options);
})
.catch(function(err) {
log.error('Error Initializing app: %s', err, {});
throw err;
})
.fin(function() {
return 'Completed successfully';
});
};
Start.addDefaultPlatforms = function addDefaultPlatforms(options) {
var currOs = os.type().toLowerCase();
if (currOs === 'darwin') {
log.info('\nAdding in iOS application by default');
// Try to install default platform
return Cordova.addPlatform(options.targetPath, 'ios', true)
.then(function() {
return IonicResources.copyIconFilesIntoResources(options.targetPath, false);
})
.then(function() {
return IonicResources.addIonicIcons(options.targetPath, 'ios');
});
} else {
return Q();
}
};
Start.fetchWrapper = function fetchWrapper(options) {
var q = Q.defer();
var wrapperRepoName = options.v2 ? 'ionic2-app-base' : 'ionic-app-base';
var wrapperBranchName = options.wrapperBranchName ? options.wrapperBranchName : 'master';
var repoUrl = 'https://github.com/driftyco/' + wrapperRepoName + '/archive/' + wrapperBranchName + '.zip';
Utils.fetchArchive(options.targetPath, repoUrl)
.then(function() {
var repoFolderName = wrapperRepoName + '-' + wrapperBranchName;
// Copy contents of starter template into base, overwriting if already exists
shelljs.cp('-R', options.targetPath + '/' + repoFolderName + '/.', options.targetPath);
shelljs.rm('-rf', options.targetPath + '/' + repoFolderName + '/');
shelljs.cd(options.targetPath);
if (!options.isCordovaProject) {
// remove any cordova files/directories if they only want the www
var cordovaFiles = ['hooks/', 'platforms/', 'plugins/', 'config.xml'];
for (var x = 0; x < cordovaFiles.length; x += 1) {
shelljs.rm('-rf', options.targetPath + '/' + cordovaFiles[x]);
}
}
q.resolve();
}, function(err) {
q.reject(err);
}).catch(function(err) {
q.reject('Error: Unable to fetch wrapper repo: ' + err);
});
return q.promise;
};
// Tested
Start.fetchSeed = function(options) {
// Codepen: http://codepen.io/ionic/pen/GpCst
if (/\/\/codepen.io\//i.test(options.template)) {
seedType = 'codepen';
return Start.fetchCodepen(options);
}
if (/plnkr.co\//i.test(options.template)) {
seedType = 'plnkr';
return Start.fetchPlnkr(options);
}
if (/creator:/i.test(options.template)) {
seedType = 'creator';
return Start.fetchCreatorApp(options);
}
// Github URL: http://github.com/myrepo/
if (/\/\/github.com\//i.test(options.template)) {
seedType = 'github';
return Start.fetchGithubStarter(options, options.template);
}
// Fix for downloading zip files: https://github.com/driftyco/ionic-cli/issues/526
if (options.zipFileDownload) {
return Start.fetchZipStarter(options);
}
// Local Directory: /User/starterapp
if ((options.template.indexOf('/') > -1 || options.template.indexOf('\\') > -1) &&
(options.template.indexOf('http://') === -1 && options.template.indexOf('https://') === -1)) {
// TODO: seedType - how to pass back?
seedType = 'local';
return Start.fetchLocalStarter(options);
}
// Ionic Github Repo
seedType = 'ionic-starter';
return Start.fetchIonicStarter(options);
};
// Not Tested
Start.loadAppSetup = function loadAppSetup(options) {
var appSetup = DEFAULT_APP;
var appJsonPath = path.join(options.targetPath, 'www', 'app.json');
if (fs.existsSync(appJsonPath)) {
try {
appSetup = JSON.parse(fs.readFileSync(appJsonPath));
shelljs.rm('-rf', appJsonPath);
} catch (e) {
log.error('app.json error: %s', e, {});
}
}
return appSetup;
};
// Not Tested
Start.fetchCreatorApp = function(options) {
var cookies = new IonicStore('cookies').get('https://apps.ionic.io');
var sessionId;
if (cookies) {
cookies.forEach(function(cookie) {
if (cookie.key === 'sessionid') {
sessionId = cookie.value;
}
});
}
if (!sessionId) {
log.error('\nPlease log in before starting a creator project. Run:\n\nionic login\n\n');
process.exit(1);
} else {
return fetchCreatorApplication();
}
function fetchCreatorApplication() {
var AdmZip = require('adm-zip');
// var self = this;
var appId = options.template.split(':')[1];
var downloadUrl = IONIC_CREATOR_API_URL + path.join('/creator/' + appId +
'/download-start/cordova?sid=' + sessionId);
var wwwPath = path.join(options.targetPath, 'www/');
log.info('\nDownloading Creator Project:'.bold, downloadUrl);
var q = Q.defer();
var proxy = Utils.getProxy();
request({ url: downloadUrl, proxy: proxy, encoding: null }, function(err, res, body) {
if (!err && res && parseInt(res.statusCode, 10) === 200) {
var tmpFolder = os.tmpdir();
var tempZipFilePath = path.join(tmpFolder, 'ionic-creator-' + new Date().getTime() + '.zip');
try {
fs.writeFileSync(tempZipFilePath, body);
var zip = new AdmZip(tempZipFilePath);
zip.extractAllTo(wwwPath);
q.resolve();
} catch (e) {
log.error(e);
q.reject(e);
}
} else {
q.reject(res);
}
});
return Q.all([q]);
}
};
Start.fetchCodepen = function(options) {
var codepenUrl = options.template.split('?')[0].split('#')[0];
var wwwPath = path.join(options.targetPath, 'www');
if (codepenUrl[codepenUrl.length - 1] === '/') {
codepenUrl = codepenUrl.substr(0, codepenUrl.length - 1);
}
log.info('Downloading Codepen: ', codepenUrl);
var qHTML = Q.defer();
var qCSS = Q.defer();
var qJS = Q.defer();
var proxy = process.env.PROXY || process.env.http_proxy || null;
request({ url: codepenUrl + '.html', proxy: proxy }, function(err, res, html) {
if (!err && res && parseInt(res.statusCode, 10) === 200) {
html = html || '';
if (html.indexOf('<!DOCTYPE html>') < 0) {
html = '<!DOCTYPE html>\n' + html;
}
var resources = ' <link href="css/style.css" rel="stylesheet">\n' +
' <script src="js/app.js"></script>\n';
if (options.isCordovaProject) {
resources += ' <script src="cordova.js"></script>\n';
}
resources += ' </head>';
html = html.replace(/<\/head>/i, '\n' + resources);
html = Start.convertTemplates(html);
fs.writeFileSync(path.join(wwwPath, 'index.html'), html, 'utf8');
}
qHTML.resolve();
});
request({ url: codepenUrl + '.css', proxy: proxy }, function(err, res, css) {
if (!err && res && parseInt(res.statusCode, 10) === 200) {
css = css || '';
var cssPath = path.join(wwwPath, 'css');
if (!fs.existsSync(cssPath)) {
fs.mkdirSync(cssPath);
}
css = css.replace("cursor: url('http://ionicframework.com/img/finger.png'), auto;", '');
fs.writeFileSync(path.join(cssPath, 'style.css'), css, 'utf8');
}
qCSS.resolve();
});
request({ url: codepenUrl + '.js', proxy: proxy }, function(err, res, js) {
if (!err && res && parseInt(res.statusCode, 10) === 200) {
js = js || '';
var jsPath = path.join(wwwPath, 'js');
if (!fs.existsSync(jsPath)) {
fs.mkdirSync(jsPath);
}
fs.writeFileSync(path.join(jsPath, 'app.js'), js, 'utf8');
}
qJS.resolve();
});
return Q.all([qHTML.promise, qCSS.promise, qJS.promise]);
};
Start.convertTemplates = function(html, targetPath) {
var templates = [];
try {
var scripts = html.match(/<script [\s\S]*?<\/script>/gi);
scripts.forEach(function(scriptElement) {
if (scriptElement.indexOf('text/ng-template') > -1) {
var lines = scriptElement.split('\n');
for (var x = 0; x < lines.length; x += 1) {
try {
if (lines[x].substr(0, 6) === ' ') {
lines[x] = lines[x].substr(6);
}
} catch (e) {} // eslint-disable-line no-empty
}
var data = lines.join('\n');
var id = data.match(/ id=["|'](.*?)["|']/i)[0];
id = id.replace(/'/g, '"').split('"')[1];
data = data.replace(/<script [\s\S]*?>/gi, '');
data = data.replace(/<\/script>/gi, '');
data = data.trim();
templates.push({
path: id,
scriptElement: scriptElement,
html: data
});
}
});
} catch (e) {} // eslint-disable-line no-empty
try {
templates.forEach(function(tmpl) {
var tmpPath = path.join(targetPath, 'www', path.dirname(tmpl.path));
if (!fs.existsSync(tmpPath)) {
fs.mkdirSync(tmpPath);
}
tmpPath = path.join(targetPath, 'www', tmpl.path);
fs.writeFileSync(tmpPath, tmpl.html, 'utf8');
html = html.replace(tmpl.scriptElement, '');
html = html.replace(/ {4}\n {4}\n/g, '');
});
} catch (e) {} // eslint-disable-line no-empty
return html;
};
Start.fetchLocalStarter = function(options) {
var q = Q.defer();
try {
shelljs.cd('..');
var localStarterPath = path.resolve(options.template);
if (!fs.existsSync(localStarterPath)) {
Utils.fail('Unable to find local starter template: ' + localStarterPath);
q.reject();
return q.promise;
}
log.info('\nCopying files to www from:'.bold, localStarterPath);
// Move the content of this repo into the www folder
shelljs.cp('-Rf', path.join(localStarterPath, '*'), path.join(options.targetPath, 'www'));
q.resolve();
} catch (e) {
q.reject(e);
}
shelljs.cd(options.targetPath);
return q.promise;
};
Start.fetchIonicStarter = function(options) {
var repoName;
if (options.v2) {
var starter = V2_STARTERS[options.template];
if (!starter) {
throw new Error('No starter template named "' + options.template + '"');
}
repoName = starter.repoName;
} else {
// Get the starter project repo name:
repoName = 'ionic-starter-' + options.template;
}
// Get the URL for the starter project repo:
var repoUrl = 'https://github.com/driftyco/' + repoName;
return Start.fetchGithubStarter(options, repoUrl);
};
Start.fetchGithubStarter = function(options, repoUrl) {
var q = Q.defer();
// https://github.com/driftyco/ionic-starter-tabs/
var urlParse = parseUrl(repoUrl);
var pathSplit = urlParse.pathname.replace(/\//g, ' ').trim().split(' ');
if (!urlParse.hostname || urlParse.hostname.toLowerCase() !== 'github.com' || pathSplit.length !== 2) {
log.error('Invalid Github URL: ' + repoUrl);
log.error('Example of a valid URL: https://github.com/driftyco/ionic-starter-tabs/');
Utils.fail('');
q.reject();
return q.promise;
}
var starterRepoName = pathSplit[1];
var starterBranchName = options.starterBranchName ? options.starterBranchName : 'master';
var repoFolderName = starterRepoName + '-' + starterBranchName;
// ensure there's an ending /
if (repoUrl.substr(repoUrl.length - 1) !== '/') {
repoUrl += '/';
}
repoUrl += 'archive/' + starterBranchName + '.zip';
Utils.fetchArchive(options.targetPath, repoUrl).then(function() {
try {
if (options.v2) {
shelljs.cp('-R', options.targetPath + '/' + repoFolderName + '/.', options.targetPath);
} else {
// Move the content of this repo into the www folder
shelljs.cp('-Rf', options.targetPath + '/' + repoFolderName + '/.', 'www');
}
// Clean up start template folder
shelljs.rm('-rf', options.targetPath + '/' + repoFolderName + '/');
q.resolve();
} catch (e) {
q.reject(e);
}
}).catch(function() {
log.error('Please verify you are using a valid URL or a valid ionic starter.');
log.error('View available starter templates: `ionic start --list`');
log.error('More info available at: \nhttp://ionicframework.com/getting-started/\nhttps://github.com/driftyco/ionic-cli');
return Utils.fail('');
});
return q.promise;
};
Start.fetchZipStarter = function fetchZipStarter(options) {
var q = Q.defer();
var repoFolderName = 'zipFileDownload';
log.info('Fetching ZIP from url:', options.zipFileDownload.bold, 'to: ', options.targetPath);
Utils.fetchArchive(options.targetPath, options.zipFileDownload)
.then(function() {
try {
// Move the content of this repo into the www folder
shelljs.cp('-Rf', options.targetPath + '/' + repoFolderName + '/.', '/.');
// Clean up start template folder
shelljs.rm('-rf', options.targetPath + '/' + repoFolderName + '/');
q.resolve();
} catch (e) {
q.reject(e);
}
}).catch(function(err) {
log.error(err);
log.error('Please verify you are using a valid URL or a valid ionic starter.');
log.error('View available starter templates: `ionic templates`');
log.error('More info available at: \nhttp://ionicframework.com/getting-started/\nhttps://github.com/driftyco/ionic-cli');
return Utils.fail('');
});
return q.promise;
};
Start.fetchPlnkr = function fetchPlnkr(options) {
var q = Q.defer();
var plnkrUrl = options.template.split('?')[0].split('#')[0];
var plnkrId = null;
// Given any of these urls - we need to extract the ID
// http://embed.plnkr.co/dFvL8n/preview
// http://run.plnkr.co/plunks/dFvL8n/#/tabs/friends
// http://api.plnkr.co/plunks/dFvL8n
// http://embed.plnkr.co/BZrnKPlCJt93orQp58H3/preview
// To download, we want http://api.plnkr.co/plunks/dFvL8n.zip
if (plnkrUrl[plnkrUrl.length - 1] === '/') {
plnkrUrl = plnkrUrl.substr(0, plnkrUrl.length - 1);
}
var plnkrSplit = plnkrUrl.split('/');
// api link - need zip on end.
if (plnkrUrl.indexOf('embed.plnkr.co') !== -1) {
plnkrId = plnkrSplit[3];
} else if (plnkrUrl.indexOf('run.plnkr.co') !== -1 || plnkrUrl.indexOf('api.plnkr.co') !== -1) {
plnkrId = plnkrSplit[plnkrSplit.length - 1];
if (plnkrId.indexOf('.zip') !== -1) {
plnkrId = plnkrId.replace('.zip', '');
}
}
plnkrUrl = ['http://api.plnkr.co/plunks/', plnkrId, '.zip'].join('');
log.info('\nDownloading Plnkr url:', plnkrUrl);
var extractPath = path.join(options.targetPath, 'plnkr');
Utils.fetchArchive(extractPath, plnkrUrl)
.then(function() {
try {
// Move the content of this repo into the www folder
var copyDir = [extractPath, '/*'].join('');
shelljs.cp('-Rf', copyDir, 'www');
// Clean up start template folder
shelljs.rm('-rf', extractPath + '/');
q.resolve();
} catch (e) {
q.reject(e);
}
});
return q.promise;
};
// New initCordova method intended for GUI - to use cordova-lib commands instead of CLI.
Start.initCordovaFromGui = function initCordovaFromGui(options, appSetup) {
var q = Q.defer();
log.debug('Initializing Cordova for Gui');
try {
if (options.isCordovaProject) {
Hooks.setHooksPermission(options.targetPath);
log.info('Update Config.xml');
appSetup.bower = appSetup.bower ? appSetup.bower : [];
var promises = [];
// // add plugins
for (var x = 0; x < appSetup.plugins.length; x += 1) {
promises.push(Cordova.addPlugin(options.targetPath, appSetup.plugins[x], null, true));
}
// platform add android with --android flag
if (options.android) {
promises.push(Cordova.addPlatform(options.targetPath, 'android', true));
}
// platform add ios with --android flag
if (options.ios) {
promises.push(Cordova.addPlatform(options.targetPath, 'ios', true));
}
log.info('Initializing cordova project');
Q.all(promises)
.then(function() {
q.resolve();
})
.catch(function(ex) {
q.reject(ex);
throw ex;
});
q.resolve();
} else {
q.resolve();
}
} catch (ex) {
log.debug('Exception caught in initCordova: %s', ex, {});
log.debug('Exception details: %s', ex.stack, {});
q.reject(ex);
}
return q.promise;
};
Start.initCordovaNoCli = function initCordova(options, appSetup) {
try {
// console.log('running initCordovaNoCli');
var promises = [];
// add plugins
for (var x = 0; x < appSetup.plugins.length; x += 1) {
promises.push(Cordova.addPlugin(options.targetPath, appSetup.plugins[x], null, true));
}
// platform add android with --android flag
if (options.android) {
promises.push(Cordova.addPlatform(options.targetPath, 'android', true));
}
// platform add ios with --android flag
if (options.ios) {
promises.push(Cordova.addPlatform(options.targetPath, 'ios', true));
}
log.info('Initializing cordova project without CLI');
return Q.all(promises);
} catch (ex) {
Utils.fail(ex);
}
};
Start.initCordova = function(options, appSetup) {
var q;
try {
if (!options.isCordovaProject) {
log.info('not a cordova project, no cordova options to initialize');
return Q.resolve();
}
Hooks.setHooksPermission(options.targetPath);
log.info('\nAdding initial native plugins');
appSetup.bower = appSetup.bower ? appSetup.bower : [];
if (!Utils.cordovaInstalled()) {
// console.log('utils cordova not installed');
return Start.initCordovaNoCli(options, appSetup);
}
q = Q.defer();
var cmds = [];
// add plugins
for (var x = 0; x < appSetup.plugins.length; x += 1) {
cmds.push('cordova plugin add --save ' + appSetup.plugins[x]);
}
if (appSetup.bower) {
// add bower packages
for (var y = 0; y < appSetup.bower.length; y += 1) {
cmds.push('ionic add ' + appSetup.bower[y]);
}
}
// platform add android with --android flag
if (options.android) {
cmds.push('ionic platform add android');
}
// platform add ios with --android flag
if (options.ios) {
cmds.push('ionic platform add ios');
}
// 30 ticks
// 7 plugins
// 2 seconds per plugin
// total time: 7 * 2 = 14seconds
// Need to get through 30 ticks in 14000 ms, so
// 14000 / 30
var bar = new ProgressBar('[:bar] :percent :etas', { total: 30, complete: '=', incomplete: ' ' });
var timer = setInterval(function() {
bar.tick();
if (bar.complete) {
clearInterval(timer);
}
}, (cmds.length * 3000) / 30);
require('child_process').exec(cmds.join(' && '),
function(err, stdout, stderr) {
bar.tick(30);
if (err) {
log.error(err, stderr);
Utils.fail('Unable to add plugins. Perhaps your version of Cordova is too old. ' +
'Try updating (npm install -g cordova), removing this project folder, and trying again:', stderr);
q.reject(stderr);
} else {
q.resolve(stdout);
}
});
// Start.updateConfigXml(options.targetPath, options.packageName, options.appName, options.ios, options.android);
} catch (ex) {
log.debug('Exception caught in initCordova: %s', ex, {});
log.debug('Exception details: %s', ex.stack, {});
q.reject(ex);
}
return q.promise;
};
Start.updateConfigXml = function(targetPath, packageName, appName) {
var q = Q.defer();
try {
var configXmlPath = targetPath + '/config.xml';
var configString = fs.readFileSync(configXmlPath, { encoding: 'utf-8' });
var parseString = xml2js.parseString;
parseString(configString, function(err, jsonConfig) {
if (err) {
Utils.fail('Error parsing config.xml: ' + err);
}
if (!packageName) {
var directoryName = path.basename(targetPath);
packageName = directoryName + (directoryName !== 'tmp' ? Math.round((Math.random() * 899999) + 100000) : '');
packageName = 'com.ionicframework.' + packageName.replace(/\./g, '');
}
jsonConfig.widget.$.id = packageName
.replace(/ /g, '')
.replace(/-/g, '')
.replace(/_/g, '')
.toLowerCase()
.trim();
jsonConfig.widget.name = [appName];
var xmlBuilder = new xml2js.Builder();
configString = xmlBuilder.buildObject(jsonConfig);
fs.writeFile(configXmlPath, configString, 'utf8', function(err) {
if (err) {
Utils.fail('Error saving config.xml file: ' + err);
} else {
q.resolve();
}
});
});
} catch (e) {
// return self.ionic.fail('Error updating config.xml file: ' + e);
log.error('Error updating config.xml file: %s', e.stack);
}
return q.promise;
};
Start.updateLibFiles = function(libPath) {
// create a symlink if the path exists locally
var libSymlinkPath = path.resolve(libPath);
if (fs.existsSync(libSymlinkPath)) {
// rename the existing lib/ionic directory before creating symlink
var wwwIonicCssPath = path.resolve('www/lib/ionic/css');
if (fs.existsSync(wwwIonicCssPath)) {
shelljs.mv(wwwIonicCssPath, path.resolve('www/lib/ionic/css_local'));
}
var wwwIonicJsPath = path.resolve('www/lib/ionic/js');
if (fs.existsSync(wwwIonicJsPath)) {
shelljs.mv(wwwIonicJsPath, path.resolve('www/lib/ionic/js_local'));
}
var wwwIonicFontsPath = path.resolve('www/lib/ionic/fonts');
if (fs.existsSync(wwwIonicFontsPath)) {
shelljs.mv(wwwIonicFontsPath, path.resolve('www/lib/ionic/fonts_local'));
}
var libCssSymlinkPath = path.join(libSymlinkPath, 'css');
log.info('Create www/lib/ionic/css symlink to ' + libCssSymlinkPath);
fs.symlinkSync(libCssSymlinkPath, wwwIonicCssPath);
var libJsSymlinkPath = path.join(libSymlinkPath, 'js');
log.info('Create www/lib/ionic/js symlink to ' + libJsSymlinkPath);
fs.symlinkSync(libJsSymlinkPath, wwwIonicJsPath);
var libFontsSymlinkPath = path.join(libSymlinkPath, 'fonts');
log.info('Create www/lib/ionic/fonts symlink to ' + libFontsSymlinkPath);
fs.symlinkSync(libFontsSymlinkPath, wwwIonicFontsPath);
libPath = 'lib/ionic';
}
if (libPath === 'lib/ionic' && (seedType === 'ionic-starter' || /ionic-starter/i.test(this.template))) {
// don't bother if its still is the default which comes with the starters
return;
}
// path did not exist locally, so manually switch out the path in the html
var libFiles = [
'ionic.css',
'ionic.min.css',
'ionic.js',
'ionic.min.js',
'ionic.bundle.js',
'ionic.bundle.min.js',
'ionic-angular.js',
'ionic-angular.min.js'
];
function isLibFile(tag) {
if (tag) {
tag = tag.toLowerCase();
for (var x = 0; x < libFiles.length; x += 1) {
if (tag.indexOf(libFiles[x]) > -1) {
return true;
}
}
}
}
function changeLibPath(originalUrl) {
var splt = originalUrl.split('/');
var newUrl = [libPath];
var filename = splt[splt.length - 1];
if (filename.indexOf('.css') > -1) {
newUrl.push('css');
} else if (filename.indexOf('.js') > -1) {
newUrl.push('js');
}
newUrl.push(filename);
return newUrl.join('/');
}
function replaceResource(html, originalTag) {
originalTag = originalTag.replace(/'/g, '"');
var splt = originalTag.split('"');
var newTagArray = [];
for (var x = 0; x < splt.length; x += 1) {
if (isLibFile(splt[x])) {
newTagArray.push(changeLibPath(splt[x]));
} else {
newTagArray.push(splt[x]);
}
}
var newTag = newTagArray.join('"');
return html.replace(originalTag, newTag);
}
function getLibTags(html) {
var resourceTags = [];
var libTags = [];
try {
resourceTags = resourceTags.concat(html.match(/<script (.*?)>/gi));
} catch (e) {} // eslint-disable-line no-empty
try {
resourceTags = resourceTags.concat(html.match(/<link (.*?)>/gi));
} catch (e) {} // eslint-disable-line no-empty
for (var x = 0; x < resourceTags.length; x += 1) {
if (isLibFile(resourceTags[x])) {
libTags.push(resourceTags[x]);
}
}
return libTags;
}
try {
log.info('Replacing Ionic lib references with ' + libPath);
var indexPath = path.join(this.targetPath, 'www', 'index.html');
var html = fs.readFileSync(indexPath, 'utf8');
var libTags = getLibTags(html);
for (var x = 0; x < libTags.length; x += 1) {
var originalTag = libTags[x];
html = replaceResource(html, originalTag);
}
fs.writeFileSync(indexPath, html, 'utf8');
} catch (e) {} // eslint-disable-line no-empty
};
Start.promptLogin = function() {
var q = Q.defer();
var ionicConfig = new IonicStore('ionic.config');
if (ionicConfig) {
// Check if we already asked
var didPrompt = ionicConfig.get('accountPrompt');
if (didPrompt === 'y') {
log.info('\n' + 'New!'.green.bold +
' Add push notifications, live app updates, and more with Ionic Cloud!'.bold);
log.info(' ' + IONIC_DASH + '/signup\n');
return;
}
ionicConfig.set('accountPrompt', 'y');
ionicConfig.save();
}
log.info('\nCreate an Ionic Cloud account to add features like User Authentication, ' +
'Push Notifications, Live Updating, iOS builds, and more?');
var promptProperties = {
shouldCreate: {
name: 'shouldCreate',
description: '(Y/n):'.yellow.bold
}
};
prompt.override = argv;
prompt.message = '';
prompt.delimiter = '';
prompt.start();
prompt.get({ properties: promptProperties }, function(err, promptResult) {
if (err) {
q.reject(err);
return log.error(err);
}
var areYouSure = promptResult.shouldCreate.toLowerCase().trim();
if (areYouSure.toLowerCase() !== 'n') {
// If they want to, let's open a thing
open(IONIC_DASH + '/signup');
q.resolve(true);
return;
}
// They didnt want to set up account.
q.resolve(false);
});
return q.promise;
};
Start.finalize = function(options) {
try {
var packageFilePath = path.join(options.targetPath, 'package.json');
var packageData = require(packageFilePath);
packageData.name = encodeURIComponent(options.appName.toLowerCase().replace(/\s+/g, '-'));
// v2 projects should all be named the same in
// order to point to the npm package for errors
if (options.v2) {
packageData.name = 'ionic-hello-world';
}
packageData.description = options.appName + ': An Ionic project';
fs.writeFileSync(packageFilePath, JSON.stringify(packageData, null, 2), 'utf8');
} catch (e) {
log.error('There was an error finalizing the package.json file. %s', e, {});
}
try {
// create the ionic.project file and
// set the app name
var project = IonicProject.create(options.targetPath, options.appName);
project.set('name', options.appName);
if (options.ionicAppId) {
project.set('app_id', options.ionicAppId);
}
if (options.v2) {
project.set('v2', true);
project.set('typescript', true);
}
project.save(options.targetPath);
log.debug('Saved project file');
} catch (e) {
log.error('Error saving project file');
}
try {
// update the app name in the bower.json file
var ionicBower = require('./bower').IonicBower;
ionicBower.setAppName(options.appName);
} catch (e) {} // eslint-disable-line no-empty
try {
// remove the README file in the root because it
// doesn't make sense because its the README for the repo
// and not helper text while developing an app
fs.unlinkSync(options.targetPath + '/README.md');
} catch (e) {} // eslint-disable-line no-empty
try {
// remove the README file in the www root because it
// doesn't make sense because its the README for the repo
// and not helper text while developing an app
fs.unlinkSync(options.targetPath + '/www/README.md');
} catch (e) {} // eslint-disable-line no-empty
if (options.isCordovaProject) {
State.saveState(process.cwd(), { plugins: true });
}
// Start.printQuickHelp();
// Start.ionic.printNewsUpdates(true).then(function() {
// self.promptLogin();
// });
};
Start.printQuickHelp = function(options) {
log.info('\n♬ ♫ ♬ ♫ Your Ionic app is ready to go! ♬ ♫ ♬ ♫'.bold);
log.info('\nSome helpful tips:'.bold);
log.info('\nRun your app in the browser (great for initial development):'.bold);
log.info(' ionic serve');
log.info('\nRun on a device or simulator:'.bold);
log.info(' ionic run ios[android,browser]');
log.info('\nTest and share your app on device with Ionic View:'.bold);
log.info(' http://view.ionic.io');
log.info('\nBuild better Enterprise apps with expert Ionic support:'.bold);
log.info(' http://ionic.io/enterprise');
};
Start.promptForOverwrite = function promptForOverwrite(targetPath) {
var q = Q.defer();
log.warn('Directory already exists:', targetPath.cyan);
log.info('Would you like to overwrite the directory with this new project?');
var promptProperties = {
areYouSure: {
name: 'areYouSure',
description: '(yes/no):',
required: true
}
};
prompt.colors = false;
prompt.override = argv;
prompt.message = '';
prompt.delimiter = '';
prompt.start();
prompt.get({ properties: promptProperties }, function(err, promptResult) {
if (err && err.message !== 'canceled') {
q.reject(err);
return log.error(err);
} else if (err && err.message === 'canceled') {
return q.resolve(false);
}
var areYouSure = promptResult.areYouSure.toLowerCase().trim();
if (areYouSure === 'yes' || areYouSure === 'y') {
shelljs.rm('-rf', targetPath);
// Empty line
log.info('');
q.resolve(true);
} else {
q.resolve(false);
}
});
return q.promise;
};