ionic
Version:
A tool for creating and developing Ionic Framework mobile apps.
418 lines (350 loc) • 11.4 kB
JavaScript
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var Q = require('q');
var archiver = require('archiver');
var request = require('request');
var requestProgess = require('request-progress');
var Security = require('./security');
var Upload = require('./upload');
var events = require('./events');
var IonicResources = require('./resources');
var Utils = require('./utils');
var settings = require('./settings');
var log = require('./logging').logger;
var Package = module.exports;
events.on('package-start', function() {
var logLevel = log.level;
log.level = 'error';
events.on('package-post-default-resources', function() {
log.level = logLevel;
});
events.on('package-pre-prepare-resources', function() {
log.level = 'error';
});
events.on('package-post-prepare-resources', function() {
log.level = logLevel;
});
});
Package.buildAndroidDebug = function(appDirectory, jar, appId, options) {
return build(appDirectory, jar, appId, undefined, {
platform: 'android',
build_mode: 'debug' // eslint-disable-line camelcase
}, options);
};
Package.buildAndroidRelease = function(appDirectory, jar, appId, profile, options) {
return build(appDirectory, jar, appId, profile, {
platform: 'android',
build_mode: 'release' // eslint-disable-line camelcase
}, options);
};
Package.buildIOS = function(appDirectory, jar, appId, profile, buildMode, options) {
return build(appDirectory, jar, appId, profile, {
platform: 'ios',
build_mode: buildMode // eslint-disable-line camelcase
}, options);
};
Package.listBuilds = function(appId, jar) {
var q = Q.defer();
request.get({
url: settings.IONIC_DASH_API + 'apps/' + appId + '/package/builds/',
qs: { app_id: appId }, // eslint-disable-line camelcase
useQuerystring: true,
proxy: Utils.getProxy(),
headers: {
cookie: jar.map(function(c) {
return c.key + '=' + encodeURIComponent(c.value);
}).join('; '),
X_CSRFToken: Utils.retrieveCsrfToken(jar), // eslint-disable-line camelcase
'Content-Type': 'application/json'
}
}, function(err, response, body) {
if (err) {
log.error('Error:', err);
return q.reject(err);
}
if (parseInt(response.statusCode, 10) !== 200) {
log.error('Body:', body);
return q.reject('Error in package service.');
}
q.resolve(JSON.parse(body));
});
return q.promise;
};
Package.getBuild = function(appId, jar, buildId, extraQueryParams) {
var q = Q.defer();
extraQueryParams = extraQueryParams || {};
request.get({
url: settings.IONIC_DASH_API + 'apps/' + appId + '/package/builds/' + buildId,
qs: _.extend({ app_id: appId }, extraQueryParams), // eslint-disable-line camelcase
useQuerystring: true,
proxy: Utils.getProxy(),
headers: {
cookie: jar.map(function(c) {
return c.key + '=' + encodeURIComponent(c.value);
}).join('; '),
X_CSRFToken: Utils.retrieveCsrfToken(jar), // eslint-disable-line camelcase
'Content-Type': 'application/json'
}
}, function(err, response, body) {
if (err) {
log.error('Error:', err);
return q.reject(err);
}
if (parseInt(response.statusCode, 10) !== 200) {
log.error('Body:', body);
return q.reject('Error in package service.');
}
return q.resolve(JSON.parse(body));
});
return q.promise;
};
Package.downloadBuild = function(appId, jar, buildId, downloadDir) {
return Package.getBuild(appId, jar, buildId, { fields: ['url'] })
.then(function(body) {
var q = Q.defer();
var build = body.data;
var filename;
var filepath;
if (build.status !== 'SUCCESS' || !build.url) {
return Q.reject(new Error('Cannot download! Build "' + buildId + '" did not finish.'));
}
filename = build.name + '.' + Package.determineFileExtensionByPlatform(build.platform);
filepath = path.join(downloadDir, filename);
requestProgess(request({ url: build.url }))
.on('progress', function(state) {
q.notify(state);
})
.on('error', function(err) {
q.reject(err);
})
.pipe(fs.createWriteStream(filepath))
.on('error', function(err) {
q.reject(err);
})
.on('close', function() {
q.resolve(filepath);
});
return q.promise;
});
};
Package.determineFileExtensionByPlatform = function(platform) {
switch (platform) {
case 'android':
return 'apk';
case 'ios':
return 'ipa';
}
throw new Error('Unknown platform: ' + platform);
};
function build(appDirectory, jar, appId, profileTag, formDataExtra, options) {
events.emit('package-start');
var uploadUrl;
var projectZipPath;
var projectZipId;
var promise;
if (typeof profileTag === 'undefined') {
promise = Q();
} else {
promise = Security.getProfile(appId, jar, profileTag)
.then(function(body) {
if (typeof body.data.credentials[formDataExtra.platform] === 'undefined') {
return Q.reject('Selected security profile does not have credentials for the selected platform.');
}
}, function(err) {
return Q.reject(err);
});
}
return promise
.then(function() {
log.info('Uploading your app to Ionic...');
return Upload.doUpload(appDirectory, jar, 'Ionic Package Upload');
})
.then(function(upload) {
if (!upload || typeof upload !== 'object' || !upload.url) {
return Q.reject('Unexpected response format: ' + upload);
}
events.emit('package-post-upload');
log.info('Upload complete!');
uploadUrl = upload.url;
})
.then(function() {
if (typeof options.noresources === 'undefined') {
return IonicResources.generate(appDirectory, { default: true, platforms: ['all'] });
}
})
.then(null, function(err) {
if (err !== 'RESOURCES_EXISTS') {
return Q.reject(err);
}
})
.then(function() {
if (typeof options.noresources === 'undefined') {
events.emit('package-post-default-resources');
log.info('Preparing your resources...');
events.emit('package-pre-prepare-resources');
return IonicResources.generate(appDirectory, { platforms: ['all'] });
}
})
.then(function() {
var q = Q.defer();
var zipFilename = 'project.zip';
var files = [
'config.xml',
'package.json',
IonicResources.Settings.resourceDir + '/**'
];
events.emit('package-post-prepare-resources');
for (var i in files) {
if (files.hasOwnProperty(i)) {
var f;
var pos = files[i].indexOf('/');
if (pos >= 0) {
f = files[i].substring(0, pos);
} else {
f = files[i];
}
try {
fs.statSync(path.join(appDirectory, f));
} catch (e) {
if (e.code === 'ENOENT') {
return Q.reject("The '" + f + "' file or directory does not exist, and package requires it.");
} else {
return Q.reject(e);
}
}
}
}
var zip = fs.createWriteStream(zipFilename);
var archive = archiver('zip');
archive.pipe(zip);
archive.bulk([{
expand: true,
cwd: appDirectory,
src: files
}]);
zip.on('close', function() {
q.resolve(zipFilename);
});
archive.finalize(function(err) {
if (err) {
q.reject('Error while creating project zip: ' + err);
}
});
return q.promise;
})
.then(function(zipPath) {
projectZipPath = zipPath;
return sendProjectZip(appId, jar);
})
.then(function(body) {
projectZipId = body.data.id;
log.info('Uploading your project to Ionic...');
return uploadProjectZip(
body.data.presigned_post.url,
body.data.presigned_post.fields,
projectZipPath
);
})
.then(function() {
var formData = {
zip_url: uploadUrl, // eslint-disable-line camelcase
project_id: projectZipId // eslint-disable-line camelcase
};
if (typeof profileTag !== 'undefined') {
_.extend(formData, { security_profile_tag: profileTag }); // eslint-disable-line camelcase
}
log.info('Submitting your app to Ionic Package...');
return sendToPackageService(appId, jar, _.extend(formData, formDataExtra));
})
.then(function(body) {
var buildId = body.data.id;
events.emit('package-post-submit');
log.info('Your app has been successfully submitted to Ionic Package!'.green);
log.info('Build ID:', buildId);
log.info('We are now packaging your app.');
fs.unlinkSync(projectZipPath);
return buildId;
});
}
function sendProjectZip(appId, jar) {
var q = Q.defer();
request.post({
url: settings.IONIC_DASH_API + 'apps/' + appId + '/package/projects/',
qs: { app_id: appId }, // eslint-disable-line camelcase
useQuerystring: true,
proxy: Utils.getProxy(),
headers: {
cookie: jar.map(function(c) {
return c.key + '=' + encodeURIComponent(c.value);
}).join('; '),
X_CSRFToken: Utils.retrieveCsrfToken(jar), // eslint-disable-line camelcase
'Content-Type': 'application/json'
}
}, function(err, response, body) {
if (err) {
log.error('Error:', err);
return q.reject(err);
}
if (parseInt(response.statusCode, 10) !== 200) {
log.error('Body:', body);
return q.reject('Error in package service.');
}
return q.resolve(JSON.parse(body));
});
return q.promise;
}
function uploadProjectZip(url, formData, projectZipPath) {
var q = Q.defer();
_.extend(formData, {
file: fs.createReadStream(projectZipPath)
});
request.post({
url: url,
formData: formData,
proxy: Utils.getProxy()
}, function(err, response, body) {
if (err) {
log.error('Error:', err);
return q.reject(err);
}
if (parseInt(response.statusCode, 10) !== 204) {
log.error('Body:', body);
return q.reject('Error in package service.');
}
return q.resolve();
});
return q.promise;
}
function sendToPackageService(appId, jar, formData) {
var q = Q.defer();
request.post({
url: settings.IONIC_DASH_API + 'apps/' + appId + '/package/builds/',
qs: { app_id: appId }, // eslint-disable-line camelcase
useQuerystring: true,
formData: formData,
proxy: Utils.getProxy(),
headers: {
cookie: jar.map(function(c) {
return c.key + '=' + encodeURIComponent(c.value);
}).join('; '),
X_CSRFToken: Utils.retrieveCsrfToken(jar), // eslint-disable-line camelcase
'Content-Type': 'application/json'
}
}, function(err, response, body) {
if (err) {
log.error('Error:', err);
return q.reject(err);
}
if (parseInt(response.statusCode, 10) === 400) {
log.error('Body:', body);
return q.reject('Bad request to package service.');
}
if (parseInt(response.statusCode, 10) !== 202) {
log.error('Body:', body);
return q.reject('Error in package service.');
}
return q.resolve(JSON.parse(body));
});
return q.promise;
}