UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,916 lines (1,696 loc) 54.1 kB
(function() { 'use strict'; var Q = require('q'), request = require('request'), os = require('os'), path = require('path'), fs = require('fs'), shell = require('shelljs'), crc32 = require('buffer-crc32'), nconf = require('nconf'), rimraf = require('rimraf'), exec = require('child_process').exec, async = require('async'), extend = require('extend'), crypto = require('crypto'), xml2js = require('xml2js'), lockfile = require('lockfile'), tmp = require('tmp'), Decompress = require('decompress'), zip = require('decompress-unzip'); // local imports var localProperties = require(path.join(__dirname, 'monaca', 'localProperties')); var USER_DATA_FILE = path.join( process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'], '.cordova', 'monaca.json' ); var CONFIG_FILE = path.join( process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'], '.cordova', 'monaca_config.json' ); // config var config = nconf.env() .file(path.join(__dirname, 'config.json')) .get('monaca'); /** * @class Monaca * @description * Create Monaca API object. * @param {string} [apiRoot] - Root of Monaca web API. Defaults to {@link https://ide.monaca.mobi/api}. * @example * var monaca = new Monaca(); * * monaca.login('my@email.org', 'mypassword').then( * function() { * // Login successful. Let's do some stuff! * }, * function(error) { * // Login failed! :( * } * ); */ var Monaca = function(apiRoot, options) { /** * @description * Root of Monaca web API. * @name Monaca#apiRoot * @type string * @default https://ide.monaca.mobi/api */ Object.defineProperty(this, 'apiRoot', { value: apiRoot ? apiRoot : config.default_api_root, writable: false }); /** * @description * Version of Monaca library * @name Monaca#version * @type string */ Object.defineProperty(this, 'version', { value: require(path.join(__dirname, '..', 'package.json')).version, writable: false }); /** * @description * Package name. * @name Monaca#packageName * @type string */ Object.defineProperty(this, 'packageName', { value: require(path.join(__dirname, '..', 'package.json')).name, writable: false }); this._loggedIn = false; if (options && options.hasOwnProperty("debug") && options.debug === true) { request.debug = true; } }; Monaca.prototype._loadAllData = function() { var deferred = Q.defer(); fs.exists(USER_DATA_FILE, function(exists) { if (exists) { fs.readFile(USER_DATA_FILE, function(error, data) { if (error) { deferred.reject(error); } else { try { deferred.resolve(JSON.parse(data)); } catch (err) { deferred.reject(err); } } }); } else { deferred.resolve({}); } }); return deferred.promise; }; Monaca.prototype._saveAllData = function(data) { var deferred = Q.defer(), jsonData; try { jsonData = JSON.stringify(data); } catch (error) { return deferred.reject(error); } fs.exists(path.dirname(USER_DATA_FILE), function(exists) { if (!exists) { shell.mkdir('-p', path.dirname(USER_DATA_FILE)); } fs.writeFile(USER_DATA_FILE, jsonData, function(error) { if (error) { deferred.reject(error); } else { deferred.resolve(); } }); }); return deferred.promise; }; Monaca.prototype.setData = function(key, value) { var deferred = Q.defer(); this._loadAllData().then( function(data) { data[key] = value; this._saveAllData(data).then( function() { deferred.resolve(value); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; Monaca.prototype.getData = function(key) { var deferred = Q.defer(); this._loadAllData().then( function(data) { deferred.resolve(data[key]); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; Monaca.prototype._filterFiles = function(dst, src) { for (var key in dst) { if (dst.hasOwnProperty(key)) { var d = dst[key]; if (d.type == 'dir') { delete dst[key]; } else if (dst.hasOwnProperty(key) && src.hasOwnProperty(key)) { var s = src[key]; if (d.hash === s.hash) { delete dst[key]; } } } } }; Monaca.prototype._createRequestClient = function(data) { var deferred = Q.defer(), qs = { api_token: this.tokens.api }; if (data) { extend(qs, data); } if (!this._loggedIn) { deferred.reject('Must be logged in to use this method.'); } else { this.getConfig('http_proxy').then( function(httpProxy) { var requestClient = request.defaults({ qs: qs, encoding: null, proxy: httpProxy, headers: { Cookie: this.tokens.session } }); deferred.resolve(requestClient); }.bind(this), function(error) { deferred.reject(error); } ) } return deferred.promise; } Monaca.prototype._get = function(resource, data) { var deferred = Q.defer(); this._createRequestClient(data).then( function(requestClient) { if (resource.charAt(0) !== '/') { resource = '/' + resource; } requestClient.get(this.apiRoot + resource, function(error, response, body) { if (error) { deferred.reject(error.code); } else { if (response.statusCode === 200) { deferred.resolve(body); } else { try { deferred.reject(JSON.parse(body)); } catch (e) { deferred.reject(response.statusCode); } } } } ) }.bind(this), function(error) { deferred.reject(error); } ) return deferred.promise; }; Monaca.prototype._post = function(resource, data) { var deferred = Q.defer(); this._createRequestClient().then( function(requestClient) { if (resource.charAt(0) !== '/') { resource = '/' + resource; } requestClient.post({ url: this.apiRoot + resource, formData: data }, function(error, response, body) { if (error) { deferred.reject(error.code); } else { if (response.statusCode === 200 || response.statusCode === 201) { deferred.resolve(body); } else { try { deferred.reject(JSON.parse(body)); } catch (e) { deferred.reject('Error code: ' + response.statusCode); } } } }); }.bind(this) ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Download project file and save to disk. Must be loggeed in to * use. * @param {string} projectId - Monaca project id. * @param {string} remotePath - Source file in cloud. * @param {string} localPath - Local file destination. * @return {Promise} * @example * monaca.downloadFile('SOME_PROJECT_ID', '/remote/file', '/local/file').then( * function() { * // File download successful! * }, * function(error) { * // File download failed. * } * ); */ Monaca.prototype.downloadFile = function(projectId, remotePath, localPath) { var deferred = Q.defer(); this._post('/project/' + projectId + '/file/read', { path: remotePath }).then( function(data) { var parentDir = path.dirname(localPath); fs.exists(parentDir, function(exists) { if (!exists) { shell.mkdir('-p', parentDir); } fs.writeFile(localPath, data, function(error) { if (error) { deferred.reject(error); } else { deferred.resolve(localPath); } }); }); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Upload a file from disk to the cloud. Must be logged in to use. * @param {string} projectId - Monaca project ID. * @param {string} localPath - Local source file. * @param {string} remotePath - Remote file in cloud. * @return {Promise} * @example * monaca.uploadFile('SOME_PROJECT_ID', '/local/file', '/remote/file').then( * function() { * // File upload successful! * }, * function(error) { * // File upload failed. * } * ); */ Monaca.prototype.uploadFile = function(projectId, localPath, remotePath) { var deferred = Q.defer(); fs.exists(localPath, function(exists) { if (!exists) { deferred.reject('File does not exist.'); } else { fs.readFile(localPath, function(error, data) { if (error) { deferred.reject(error); } else { this._post('/project/' + projectId + '/file/save', { path: remotePath, content: data }).then( function() { deferred.resolve(remotePath); }, function(error) { deferred.reject(error); } ); } }.bind(this)); } }.bind(this)); return deferred.promise; }; Monaca.prototype._login = function() { var deferred = Q.defer(), options; if (arguments.length === 3) { options = arguments[2]; } else if (typeof arguments[1] === 'object') { options = arguments[1]; } else { options = {}; } var form = { language: options.language || 'en', clientType: 'local', version: options.version || this.packageName + ' ' + this.version, os: os.platform() }; if (arguments.length === 1 || typeof arguments[1] === 'object') { form.token = arguments[0]; } else { form.email = arguments[0]; form.password = arguments[1]; } Q.all([this.getData('clientId'), this.getConfig('http_proxy')]).then( function(data) { var clientId = data[0], httpProxy = data[1]; if (clientId) { form.clientId = clientId; } request.post({ url: this.apiRoot + '/user/login', proxy: httpProxy, form: form }, function(error, response, body) { try { var _body = JSON.parse(body || '{}'); } catch (e) { deferred.reject("Not a JSON response"); } if (error) { deferred.reject(error.code); } else { if (response.statusCode == 200) { var d = Q.defer(); this.setData('reloginToken', _body.result.token).then( function() { this.setData('clientId', _body.result.clientId).then( function() { d.resolve(); }, function(error) { d.reject(error); } ); }.bind(this), function(error) { d.reject(error); } ); d.promise.then( function() { var headers = response.caseless.dict; this.tokens = { api: headers['x-monaca-param-api-token'], session: headers['x-monaca-param-session'] }; this.loginBody = _body.result; this._loggedIn = true; deferred.resolve(); }.bind(this), function(error) { deferred.reject(error); } ); } else { deferred.reject(_body); } } }.bind(this)); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Login to Monaca cloud using a saved relogin token. Use {@link Monaca#login} to * login the first time. * @param {object} [options] - Login parameters. * @param {string} [options.version] - App name and version to send to the Monaca API. Defaults to "monaca-lib x.y.z". * @param {string} [options.language] - Can be either "en" or "ja". Defaults to "en". * @return {Promise} * @example * monaca.relogin().then( * function() { * // Login successful! * }, * function(error) { * // Login failed! * } * ); */ Monaca.prototype.relogin = function(options) { var deferred = Q.defer(); options = options || {}; this.getData('reloginToken').then( function(reloginToken) { if (typeof(reloginToken) !== 'string' || reloginToken === '') { return deferred.reject("Not a valid relogin token."); } this._login(reloginToken, options).then( function() { deferred.resolve(); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Sign in to Monaca cloud using email and password. Will save relogin token to disk * if successful. After the relogin token has been saved, {@link Monaca#relogin} can * be used to login. * @param {string} email - A Monaca account email. * @param {string} password - Password associated with the account. * @param {object} [options] - Additional options. * @param {string} [options.version] - App name and version to send to the Monaca API. Defaults to "monaca-lib x.y.z". * @param {string} [options.language] - Can be either "en" or "ja". Defaults to "en". * @return {Promise} * @example * monaca.login('my@email.com', 'password').then( * function() { * // Login successful! * }, * function(error) { * // Login failed! * } * ); */ Monaca.prototype.login = function(email, password, options) { if (options) { return this._login(email, password, options); } else { return this._login(email, password); } }; /** * @method * @memberof Monaca * @description * Sign out from Monaca cloud. Will remove relogin token from disk and session tokens * from memory. * @return {Promise} * @example * monaca.login('my@email.com', 'password').then( * function() { * monaca.logout(); * } * ); */ Monaca.prototype.logout = function() { var deferred = Q.defer(); this.setData('reloginToken', '').then( function() { delete this.tokens; this._loggedIn = false; deferred.resolve(); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Generate a one time token for an URL. * @param {string} url * @return {Promise} */ Monaca.prototype.getSessionUrl = function(url) { var deferred = Q.defer(); this._get('/user/getSessionUrl', { url: url }).then( function(response) { deferred.resolve(JSON.parse(response).result.url); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Downloads a file from Monaca request. * * If the download is successful the promise will resolve with the * downloaded filename. * @param {string} url - URL to download from * @param {object} data - Request parameters * @param {string} filename - Filename the data will be saved to. Can be a callback function. * @return {Promise} */ Monaca.prototype.download = function(url, data, filename) { var deferred = Q.defer(); this._createRequestClient(data).then(function(requestClient) { requestClient.get(url) .on('response', function(response) { var dest = filename; if (typeof filename === 'function') { // Callback so that the caller can decide the filename from the response dest = filename(response); } if (typeof dest === 'string') { var file = fs.createWriteStream(dest); response.pipe(file); file.on('finish', function() { deferred.resolve(filename); }); file.on('error', function(error) { deferred.reject(error); }); } else { deferred.reject("Not a valid file name"); } }) }.bind(this), function(error) { return deferred.reject(error); }); return deferred.promise; } /** * @method * @memberof Monaca * @name getLatestNews * @description * Fetches latest news and status on known issues from Monaca Cloud. * @param {Object} [options] Parameters * @param {Boolean} [options.disableStatusUpdate] * @return {Promise} */ Monaca.prototype.getLatestNews = function(options) { var deferred = Q.defer(); options = options || {}; options.disableStatusUpdate = options.disableStatusUpdate ? 1 : 0; this._get('/user/info/news', options ? options : {} ).then( function(response) { deferred.resolve(JSON.parse(response)); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Fetch a list of all available projects. * @return {Promise} * @example * monaca.getProjects().then( * function(projects) { * console.log('You have ' + projects.length + ' projects!'); * }, * function(error) { * // Unable to fetch list. * } * ); */ Monaca.prototype.getProjects = function() { var deferred = Q.defer(); this._get('/user/projects').then( function(response) { deferred.resolve(JSON.parse(response).result.items); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Get project ID. * @return {Promise} */ Monaca.prototype.getProjectId = function(projectDir) { return localProperties.get(projectDir, 'project_id'); }; /** * @method * @memberof Monaca * @description * Get local project ID. * @return {Promise} */ Monaca.prototype.getLocalProjectId = function(projectDir) { var deferred = Q.defer(), absolutePath = path.resolve(projectDir); try { var projectId = crypto.createHash('sha256').update(absolutePath).digest('hex'); deferred.resolve(projectId); } catch (error) { deferred.reject(error); } return deferred.promise; }; /** * @method * @memberof Monaca * @description * Set project ID. * @return {Promise} */ Monaca.prototype.setProjectId = function(projectDir, projectId) { shell.mkdir('-p', path.join(projectDir, '.monaca')); return localProperties.set(projectDir, 'project_id', projectId); }; /** * @method * @memberof Monaca * @description * Fetch a list of files and directories for a project. * Must be logged in to use. * @param {string} projectId * @return {Promise} * @example * monaca.getProjectFiles('SOME_PROJECT_ID').then( * function(files) { * // Fetched file list! * }, * function(error) { * // Failed fetching file list! * } * ); */ Monaca.prototype.getProjectFiles = function(projectId) { var deferred = Q.defer(); this._post('/project/' + projectId + '/file/tree').then( function(response) { deferred.resolve(JSON.parse(response).result.items); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Fetch a list of files and directories for a local project. * @param {string} projectDir - Path to project. * @return {Promise} * @example * monaca.getLocalProjectFiles = function('/some/directory').then( * function(files) { * // Successfully fetched file list! * }, * function(error) { * // Failed fetching file list! * } * ); */ Monaca.prototype.getLocalProjectFiles = function(projectDir) { var deferred = Q.defer(); var getFileChecksum = function(file) { var deferred = Q.defer(); fs.readFile(file, function(error, data) { if (error) { deferred.reject(error); } else { deferred.resolve(crc32(data).toString('hex')); } }); return deferred.promise; }; fs.exists(projectDir, function(exists) { if (exists) { var files = {}, promises = []; var list = shell.ls('-RA', projectDir).filter(function(name) { return name.indexOf('node_modules') !== 0; }); list.forEach(function(file) { var obj = {}, key = path.join('/', file); // Converting Windows path delimiter to slash key = key.split(path.sep).join('/'); files[key] = obj; var absolutePath = path.join(projectDir, file); if (fs.lstatSync(absolutePath).isDirectory()) { obj.type = 'dir'; } else { obj.type = 'file'; var deferred = Q.defer(); getFileChecksum(absolutePath).then( function(checksum) { deferred.resolve([key, checksum]); }, function(error) { deferred.reject(error); } ); promises.push(deferred.promise); } }); Q.all(promises).then( function(results) { results.forEach(function(result) { var key = result[0], checksum = result[1]; files[key].hash = checksum; }); // Remove local properties file. delete files['/.monaca/local_properties.json']; deferred.resolve(files); }, function(error) { deferred.reject(error); } ); } else { deferred.reject(projectDir + ' does not exist'); } }); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Download Monaca project and save it to disk. Must be logged in to use. * Will fail if {@link destDir} already exists. The returned promise will * be notified every time a file has been copied so the progress can be * tracked. * @param {string} projectId - Monaca project ID. * @param {string} destDir - Destination directory. * @return {Promise} * @example * monaca.cloneProject(123, '/home/user/workspace/myproject').then( * function(dest) { * console.log('Project placed in: ' + dir); * }, * function(error) { * // Wasn't able to cloneProject project! :( * }, * function(file) { * var progress = 100 * file.index / file.total; * console.log('[' + progress + '%] ' + file.path); * } * ); */ Monaca.prototype.cloneProject = function(projectId, destDir) { var deferred = Q.defer(); fs.exists(destDir, function(exists) { if (exists && shell.ls(destDir).length > 0) { deferred.reject('File or directory already exists and it contains files.'); } else { var success = true; try { shell.mkdir(destDir); } catch (e) { success = false; deferred.reject(e); } if (success) { this.getProjectFiles(projectId).then( function(files) { var index = 0, promises = [], defers = {}; var totalLength = Object.keys(files) .map( function(key) { return files[key].type === 'file' ? 1 : 0; } ) .reduce( function(a, b) { return a + b; } ); Object.keys(files).forEach(function(_path) { if (files.hasOwnProperty(_path) && files[_path].type == 'file') { var d = Q.defer(); defers[_path] = d; promises.push(d.promise); this.downloadFile(projectId, _path, path.join(destDir, _path)).then( function(dest) { deferred.notify({ total: totalLength, index: index, path: dest }); defers[_path].resolve(dest); }, function(error) { defers[_path].reject(error); } ) .finally( function() { index++; } ); } }.bind(this)); Q.all(promises).then( function() { deferred.resolve(destDir); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); } } }.bind(this)); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Create a new project in the Cloud. * * Returns a promise that resolves to the project info. * @param {object} options - Parameters * @param {string} options.name - Project name * @param {string} options.description - Project description * @param {string} options.templateId - Template ID (e.g. "rss", "minimum", etc.) * @param {boolean} [options.isBuildOnly] - Set to true if the project is uploaded just for building. * @return {Promise} * @example * monaca.createProject({ * name: 'My project', * description: 'An awesome app that does awesome things.', * template: 'minimum' * }).then( * function(projectId) { * // Creation successful! * }, * function(error) { * // Creation failed! * } * ); */ Monaca.prototype.createProject = function(options) { var deferred = Q.defer(); options.isBuildOnly = options.isBuildOnly ? 1 : 0; this._post('/user/project/create', options).then( function(response) { var data; try { data = JSON.parse(response).result; } catch (error) { return deferred.reject(error); } deferred.resolve(data); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; Monaca.prototype.getProjectInfo = function(projectPath) { var deferred = Q.defer(); var guessConfigFile = function(projectPath) { var possibleFiles = ['config.xml', 'config.ios.xml', 'config.android.xml']; for (var i = 0, l = possibleFiles.length; i < l; i ++) { var configFile = path.join(projectPath, possibleFiles[i]); if (fs.existsSync(configFile)) { return configFile; } } return null; }; this.getLocalProjectId(projectPath).then( function(projectId) { var configFile = guessConfigFile(projectPath); if (configFile) { fs.readFile(configFile, function(error, data) { if (error) { deferred.reject(error); } else { xml2js.parseString(data, function(error, result) { if (error) { deferred.reject(error); } else { var project = { name: result.widget.name[0], directory: projectPath, description: result.widget.description[0], projectId: projectId }; deferred.resolve(project); } }); } }); } else { deferred.resolve({ name: 'Undefined Project Name', directory: projectPath, description: 'No description', projectId: projectId }); } }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Uploads a Monaca project to the Cloud. Will fail if the specified * directory doesn't contain a Monaca project or if the project is * not associated with the logged in user. * * Will not overwrite files if they are identical. * * If the upload is successful the promise will resolve with the project ID. * @param {string} projectDir - Project directory. * @return {Promise} * @example * monaca.uploadProject('/my/project/').then( * function(projectId) { * // Upload successful! * }, * function(error) { * // Upload failed! * }, * function(progress) { * // Track the progress * } * ); */ Monaca.prototype.uploadProject = function(projectDir) { var deferred = Q.defer(); localProperties.get(projectDir, 'project_id').then( function(projectId) { Q.all([this.getLocalProjectFiles(projectDir), this.getProjectFiles(projectId)]).then( function(files) { var localFiles = files[0], remoteFiles = files[1]; // Filter out directories and unchanged files. this._filterFiles(localFiles, remoteFiles); var fileFilter = function(fn) { // Exclude hidden files and folders. if (fn.indexOf('/.') >= 0) { return false; } // Only include files in /www, /merges and /plugins folders. return /^\/(www\/|merges\/|plugins\/|[^/]*$)/.test(fn); }; var keys = Object.keys(localFiles).filter(fileFilter); var totalLength = keys.length, currentIndex = 0, defers = [], promises = []; for (var i = 0; i < totalLength; i++) { var d = Q.defer(); defers.push(d); promises.push(d.promise); } keys.forEach(function(key) { if (localFiles.hasOwnProperty(key)) { var absolutePath = path.join(projectDir, key.substr(1)); this.uploadFile(projectId, absolutePath, key).then( function(remotePath) { deferred.notify({ path: remotePath, total: totalLength, index: currentIndex }); defers[currentIndex].resolve(); }, function(error) { defers[currentIndex].reject(error); } ) .finally( function() { currentIndex++; } ); } }.bind(this)); Q.all(promises).then( function() { deferred.resolve(projectId); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Downloads a Monaca project from the Cloud. Will fail if the * specified directory doesn't contain a Monaca project or if the * project is not associated with the logged in user. * * Will not download unchanged files. * * If the upload is successful the promise will resolve with the * project ID. * @param {string} projectDir - Project directory. * @return {Promise} * @example * monaca.downloadProject('/my/project/').then( * function(projectId) { * // Download successful! * }, * function(error) { * // Download failed! * }, * function(progress) { * // Track the progress * } * ); */ Monaca.prototype.downloadProject = function(projectDir) { var deferred = Q.defer(); localProperties.get(projectDir, 'project_id').then( function(projectId) { Q.all([this.getLocalProjectFiles(projectDir), this.getProjectFiles(projectId)]).then( function(files) { var localFiles = files[0], remoteFiles = files[1]; // Filter out directories and unchanged files. this._filterFiles(remoteFiles, localFiles); var totalLength = Object.keys(remoteFiles).length, currentIndex = 0, defers = [], promises = []; for (var i = 0; i < totalLength; i++) { var d = Q.defer(); defers.push(d); promises.push(d.promise); } Object.keys(remoteFiles).forEach(function(key) { if (remoteFiles.hasOwnProperty(key)) { var absolutePath = path.join(projectDir, key.substr(1)); this.downloadFile(projectId, key, absolutePath).then( function(remotePath) { deferred.notify({ path: remotePath, total: totalLength, index: currentIndex }); defers[currentIndex].resolve(); }, function(error) { defers[currentIndex].reject(error); } ) .finally( function() { currentIndex++; } ); } }.bind(this)); Q.all(promises).then( function() { deferred.resolve(projectId); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Builds a Monaca project. * * If the build is successful the promise will resolve to * an object containing information about the build. * @param {string} projectId - Project ID. * @param {object} params - Build parameters. * @param {string} params.platform - Target platform. Should be one of "android", "ios" or "winrt". * @param {string} [params.android_webview] - When building for Android the webview can be configured. Choose between "default" or "crosswalk" * @param {string} [params.android_arch] - Required when building for Crosswalk. Should be one of either "x86" or "arm". * @param {string} [params.framework_version] - Framework version. Defaults to 3.5. * @param {string} [params.purpose] - Type of build. Should be one of either "debug" or "release". Defaults to "debug". * @return {Promise} * @example * monaca.uploadProject('/some/project').then( * function(projectId) { * var params = { * platform: 'android', * purpose: 'debug' * }; * * monaca.buildProject(projectId, params).then( * function(result) { * // Build was successful! * }, * function(error) { * // Build failed! * }, * function(progress) { * // Track build status. * } * ); * } * ); */ Monaca.prototype.buildProject = function(projectId, params) { var deferred = Q.defer(), buildRoot = '/project/' + projectId + '/build'; params = params || {}; if (!params.framework_version) { params.framework_version = '3.5'; } if (!params.purpose) { params.purpose = 'debug'; } if (!params.platform) { deferred.reject('Must specify build platform.'); } var pollBuild = function(queueId) { var deferred = Q.defer(), counter = 0; var interval = setInterval(function() { if (counter++ == 80) { clearInterval(interval); deferred.reject('Build timed out'); } this._post(buildRoot + '/status/' + queueId).then( function(response) { var result = JSON.parse(response).result; deferred.notify(result.description); if (result.finished) { clearInterval(interval); if (result.status === 'finish') { deferred.resolve(result.description); } else { this._post(buildRoot + '/result/' + queueId).then( function(response) { deferred.reject(JSON.parse(response).result.error_message); }, function(error) { deferred.reject(error); } ); } } }.bind(this), function(error) { clearInterval(interval); deferred.reject(error); } ); }.bind(this), 1000); return deferred.promise; }.bind(this); this._post(buildRoot, params).then( function(response) { var queueId = JSON.parse(response).result.queue_id; pollBuild(queueId).then( function() { this._post(buildRoot + '/result/' + queueId).then( function(response) { deferred.resolve(JSON.parse(response).result); }, function(error) { deferred.reject(error); } ); }.bind(this), function(error) { deferred.reject(error); }, function(progress) { deferred.notify(progress); } ); }.bind(this), function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Gets a list of project templates. * * The method will resolve to list of project templates. * @return {Promise} * @example * monaca.getTemplates().then( * function(templates) { * //list of templates * }, * function(err) { * //error * }); */ Monaca.prototype.getTemplates = function() { return this._get('/user/project/templates') .then( function(response) { var data; try { data = JSON.parse(response); } catch (e) { return Q.reject(e); } if (data.status === 'ok') { return data.result.items; } else { return Q.reject(data.status); } } ); }; /** * @method * @memberof Monaca * @description * Download template from Monaca Cloud. * @param {String} templateId Template ID * @param {String} destinationDir Destionation directory * @return {Promise} */ Monaca.prototype.downloadTemplate = function(templateId, destinationDir) { var checkDirectory = function() { var deferred = Q.defer(); fs.exists(destinationDir, function(exists) { if (exists) { deferred.reject('Directory already exists'); } else { deferred.resolve(destinationDir); } }); return deferred.promise; }; var createTmpFile = function() { return Q.denodeify(tmp.file)() .then( function() { return arguments[0][0]; } ); }; var saveZipFile = function(path, data) { return Q.denodeify(fs.writeFile)(path, data) .then( function() { return path; } ); }; var unzipFile = function(data) { return createTmpFile() .then( function(path) { return saveZipFile(path, data); } ) .then( function(path) { var deferred = Q.defer(); var decompress = new Decompress() .src(path) .dest(destinationDir) .use(zip({strip: 0})); decompress.run(function(err) { if (err) { deferred.reject(err); } else { deferred.resolve(destinationDir); } }); return deferred.promise; } ); } var fetchFile = function() { return this._get('/user/project/downloadTemplate', {templateId: templateId}); }.bind(this); return checkDirectory() .then(fetchFile) .then(unzipFile); }; /** * @method * @memberof Monaca * @description * Create project from template. * @param {String} templateId Template ID * @param {String} destinationDir Destionation directory * @return {Promise} */ Monaca.prototype.createFromTemplate = function(templateId, destinationDir) { return this.downloadTemplate(templateId, destinationDir); }; /** * @method * @memberof Monaca * @description * Set config file path. * @param {String} configFile * @return {Promise} */ Monaca.prototype.setConfigFile = function(configFile) { var deferred = Q.defer(); // Parent directory must exist. var parentDir = path.dirname(configFile); fs.exists(parentDir, function(exists) { if (exists) { this._configFile = configFile; deferred.resolve(configFile); } else { deferred.reject('Unable to set config file: ' + parentDir + ' does not exist.'); } }.bind(this)); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Get current config file. * @return {Promise} */ Monaca.prototype.getConfigFile = function() { var deferred = Q.defer(); deferred.resolve(this._configFile || CONFIG_FILE); return deferred.promise; }; Monaca.prototype._ensureConfigFile = function() { var deferred = Q.defer(); // Ensure that config file exists. this.getConfigFile().then( function(configFile) { var parentDir = path.dirname(configFile); fs.exists(parentDir, function(exists) { if (!exists) { try { shell.mkdir('-p', parentDir); } catch (err) { return deferred.reject(err); } } fs.exists(configFile, function(exists) { if (!exists) { fs.writeFile(configFile, '{}', function(err) { if (err) { deferred.reject(err); } else { deferred.resolve(configFile); } }); } else { deferred.resolve(configFile); } }); }); }, function() { } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Set a config value. * @param {String} key * @param {String} value * @return {Promise} * @example * monaca.setConfig('http_proxy_host', '1.2.3.4').then( * function(value) { * console.log('Proxy host set to ' + value); * }, * function(error) { * console.log('An error has occurred: ' + error); * } * ); */ Monaca.prototype.setConfig = function(key, value) { if (typeof key === 'undefined') { throw new Error('"key" must exist.'); } else if (typeof key !== 'string') { throw new Error('"key" must be a string.'); } else if (typeof value === 'undefined') { throw new Error('"value" must exist.'); } else if (typeof value !== 'string') { throw new Error('"value" must be a string.'); } var deferred = Q.defer(); this._ensureConfigFile().then( function(configFile) { var lockFile = configFile + '.lock'; lockfile.lock(lockFile, {wait: 10000}, function(error) { if (error) { return deferred.reject(error); } var unlock = function() { lockfile.unlock(lockFile, function(error) { if (error) { console.error(error); } }); }; fs.readFile(configFile, function(error, data) { if (error) { unlock(); return deferred.reject(error); } try { var ob = JSON.parse(data); ob[key] = value; fs.writeFile(configFile, JSON.stringify(ob), function(error) { unlock(); if (error) { deferred.reject(error); } else { deferred.resolve(value); } }); } catch (err) { unlock(); deferred.reject(err); } }); }); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Remove a config value. * @param {String} key * @return {Promise} */ Monaca.prototype.removeConfig = function(key) { if (typeof key === 'undefined') { throw new Error('"key" must exist.'); } else if (typeof key !== 'string') { throw new Error('"key" must be a string.'); } var deferred = Q.defer(); this._ensureConfigFile().then( function(configFile) { var lockFile = configFile + '.lock'; lockfile.lock(lockFile, {wait: 10000}, function(error) { if (error) { return deferred.reject(error); } var unlock = function() { lockfile.unlock(lockFile, function(error) { if (error) { console.error(error); } }); }; fs.readFile(configFile, function(error, data) { if (error) { unlock(); return deferred.reject(error); } try { var ob = JSON.parse(data), value = ob[key]; delete ob[key]; fs.writeFile(configFile, JSON.stringify(ob), function(error) { unlock(); if (error) { deferred.reject(error); } else { deferred.resolve(value); } }); } catch (err) { unlock(); deferred.reject(err); } }); }); }, function(error) { deferred.reject(error); } ); return deferred.promise; }; /** * @method * @memberof Monaca * @description * Get a config value. * @param {String} key * @return {Promise} * @example * monaca.getConfig('http_proxy_host').then( * function(value) { * console.log('Proxy host is ' + value); * }, * function(error) { * console.log('Unable to get proxy host: ' + error); * } * ); */ Monaca.prototype.getConfig = function(key) { if (typeof key === 'undefined') { throw new Error('"key" must exist.'); } else if (typeof key !== 'string') { throw new Error('"key" must be a string.'); } var deferred = Q.defer(); this.getAllConfigs().then( function(settings) { deferred.resolve(settings[key]); }, function(error) { deferred.reject(error); } );