| /** |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| 'License'); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| /* |
| this file is found by cordova-lib when you attempt to |
| 'cordova platform add PATH' where path is this repo. |
| */ |
| |
| var shell = require('shelljs'); |
| var path = require('path'); |
| var fs = require('fs'); |
| |
| var cdvcmn = require('cordova-common'); |
| var CordovaLogger = cdvcmn.CordovaLogger; |
| var ConfigParser = cdvcmn.ConfigParser; |
| var ActionStack = cdvcmn.ActionStack; |
| var selfEvents = cdvcmn.events; |
| var xmlHelpers = cdvcmn.xmlHelpers; |
| var PlatformJson = cdvcmn.PlatformJson; |
| var PlatformMunger = cdvcmn.ConfigChanges.PlatformMunger; |
| var PluginInfoProvider = cdvcmn.PluginInfoProvider; |
| |
| var BrowserParser = require('./browser_parser'); |
| var PLATFORM_NAME = 'browser'; |
| |
| function setupEvents (externalEventEmitter) { |
| if (externalEventEmitter) { |
| // This will make the platform internal events visible outside |
| selfEvents.forwardEventsTo(externalEventEmitter); |
| return externalEventEmitter; |
| } |
| |
| // There is no logger if external emitter is not present, |
| // so attach a console logger |
| CordovaLogger.get().subscribe(selfEvents); |
| return selfEvents; |
| } |
| |
| function Api (platform, platformRootDir, events) { |
| |
| this.platform = platform || PLATFORM_NAME; |
| |
| // MyApp/platforms/browser |
| this.root = path.resolve(__dirname, '..'); |
| this.events = setupEvents(events); |
| this.parser = new BrowserParser(this.root); |
| this._handler = require('./browser_handler'); |
| |
| this.locations = { |
| platformRootDir: platformRootDir, |
| root: this.root, |
| www: path.join(this.root, 'www'), |
| res: path.join(this.root, 'res'), |
| platformWww: path.join(this.root, 'platform_www'), |
| configXml: path.join(this.root, 'config.xml'), |
| defaultConfigXml: path.join(this.root, 'cordova/defaults.xml'), |
| build: path.join(this.root, 'build'), |
| // NOTE: Due to platformApi spec we need to return relative paths here |
| cordovaJs: 'bin/templates/project/assets/www/cordova.js', |
| cordovaJsSrc: 'cordova-js-src' |
| }; |
| |
| this._platformJson = PlatformJson.load(this.root, platform); |
| this._pluginInfoProvider = new PluginInfoProvider(); |
| this._munger = new PlatformMunger(platform, this.root, this._platformJson, this._pluginInfoProvider); |
| } |
| |
| Api.createPlatform = function (dest, config, options, events) { |
| |
| var creator = require('../../lib/create'); |
| events = setupEvents(events); |
| |
| var name = 'HelloCordova'; |
| var id = 'io.cordova.hellocordova'; |
| if (config) { |
| name = config.name(); |
| id = config.packageName(); |
| } |
| |
| var result; |
| try { |
| // we create the project using our scripts in this platform |
| result = creator.createProject(dest, id, name, options) |
| .then(function () { |
| // after platform is created we return Api instance based on new Api.js location |
| // Api.js has been copied to the new project |
| // This is required to correctly resolve paths in the future api calls |
| var PlatformApi = require(path.resolve(dest, 'cordova/Api')); |
| return new PlatformApi('browser', dest, events); |
| }); |
| } catch (e) { |
| events.emit('error', 'createPlatform is not callable from the browser project API.'); |
| throw (e); |
| } |
| return result; |
| }; |
| |
| Api.updatePlatform = function (dest, options, events) { |
| // console.log("test-platform:Api:updatePlatform"); |
| // todo?: create projectInstance and fulfill promise with it. |
| return Promise.resolve(); |
| }; |
| |
| Api.prototype.getPlatformInfo = function () { |
| // console.log("browser-platform:Api:getPlatformInfo"); |
| // return PlatformInfo object |
| return { |
| 'locations': this.locations, |
| 'root': this.root, |
| 'name': this.platform, |
| 'version': { 'version': '1.0.0' }, // um, todo! |
| 'projectConfig': this.config |
| }; |
| }; |
| |
| Api.prototype.prepare = function (cordovaProject, options) { |
| |
| // First cleanup current config and merge project's one into own |
| var defaultConfigPath = path.join(this.locations.platformRootDir, 'cordova', |
| 'defaults.xml'); |
| var ownConfigPath = this.locations.configXml; |
| var sourceCfg = cordovaProject.projectConfig; |
| |
| // If defaults.xml is present, overwrite platform config.xml with it. |
| // Otherwise save whatever is there as defaults so it can be |
| // restored or copy project config into platform if none exists. |
| if (fs.existsSync(defaultConfigPath)) { |
| this.events.emit('verbose', 'Generating config.xml from defaults for platform "' + this.platform + '"'); |
| shell.cp('-f', defaultConfigPath, ownConfigPath); |
| } else if (fs.existsSync(ownConfigPath)) { |
| this.events.emit('verbose', 'Generating defaults.xml from own config.xml for platform "' + this.platform + '"'); |
| shell.cp('-f', ownConfigPath, defaultConfigPath); |
| } else { |
| this.events.emit('verbose', 'case 3"' + this.platform + '"'); |
| shell.cp('-f', sourceCfg.path, ownConfigPath); |
| } |
| |
| // merge our configs |
| this.config = new ConfigParser(ownConfigPath); |
| xmlHelpers.mergeXml(cordovaProject.projectConfig.doc.getroot(), |
| this.config.doc.getroot(), |
| this.platform, true); |
| this.config.write(); |
| |
| // Update own www dir with project's www assets and plugins' assets and js-files |
| this.parser.update_www(cordovaProject.locations.www); |
| |
| // Copy or Create manifest.json |
| // todo: move this to a manifest helper module |
| // output path |
| var manifestPath = path.join(this.locations.www, 'manifest.json'); |
| var srcManifestPath = path.join(cordovaProject.locations.www, 'manifest.json'); |
| if (fs.existsSync(srcManifestPath)) { |
| // just blindly copy it to our output/www |
| // todo: validate it? ensure all properties we expect exist? |
| this.events.emit('verbose', 'copying ' + srcManifestPath + ' => ' + manifestPath); |
| shell.cp('-f', srcManifestPath, manifestPath); |
| } else { |
| var manifestJson = { |
| 'background_color': '#FFF', |
| 'display': 'standalone' |
| }; |
| if (this.config) { |
| if (this.config.name()) { |
| manifestJson.name = this.config.name(); |
| } |
| if (this.config.shortName()) { |
| manifestJson.short_name = this.config.shortName(); |
| } |
| if (this.config.packageName()) { |
| manifestJson.version = this.config.packageName(); |
| } |
| if (this.config.description()) { |
| manifestJson.description = this.config.description(); |
| } |
| if (this.config.author()) { |
| manifestJson.author = this.config.author(); |
| } |
| // icons |
| var icons = this.config.getStaticResources('browser', 'icon'); |
| var manifestIcons = icons.map(function (icon) { |
| // given a tag like this : |
| // <icon src="res/ios/icon.png" width="57" height="57" density="mdpi" /> |
| /* configParser returns icons that look like this : |
| { src: 'res/ios/icon.png', |
| target: undefined, |
| density: 'mdpi', |
| platform: null, |
| width: 57, |
| height: 57 |
| } ******/ |
| /* manifest expects them to be like this : |
| { "src": "images/touch/icon-128x128.png", |
| "type": "image/png", |
| "sizes": "128x128" |
| } ******/ |
| // ?Is it worth looking at file extentions? |
| return {'src': icon.src, |
| 'type': 'image/png', |
| 'sizes': (icon.width + 'x' + icon.height)}; |
| }); |
| manifestJson.icons = manifestIcons; |
| |
| // orientation |
| // <preference name="Orientation" value="landscape" /> |
| var oriPref = this.config.getGlobalPreference('Orientation'); |
| if (oriPref) { |
| // if it's a supported value, use it |
| if (['landscape', 'portrait'].indexOf(oriPref) > -1) { |
| manifestJson.orientation = oriPref; |
| } else { // anything else maps to 'any' |
| manifestJson.orientation = 'any'; |
| } |
| } |
| |
| // get start_url |
| var contentNode = this.config.doc.find('content') || {'attrib': {'src': 'index.html'}}; // sensible default |
| manifestJson.start_url = contentNode.attrib.src; |
| |
| // now we get some values from start_url page ... |
| var startUrlPath = path.join(cordovaProject.locations.www, manifestJson.start_url); |
| if (fs.existsSync(startUrlPath)) { |
| var contents = fs.readFileSync(startUrlPath, 'utf-8'); |
| // matches <meta name="theme-color" content="#FF0044"> |
| var themeColorRegex = /<meta(?=[^>]*name="theme-color")\s[^>]*content="([^>]*)"/i; |
| var result = themeColorRegex.exec(contents); |
| var themeColor; |
| if (result && result.length >= 2) { |
| themeColor = result[1]; |
| } else { // see if there is a preference in config.xml |
| // <preference name="StatusBarBackgroundColor" value="#000000" /> |
| themeColor = this.config.getGlobalPreference('StatusBarBackgroundColor'); |
| } |
| if (themeColor) { |
| manifestJson.theme_color = themeColor; |
| } |
| } |
| } |
| fs.writeFileSync(manifestPath, JSON.stringify(manifestJson, null, 2), 'utf8'); |
| } |
| |
| // update project according to config.xml changes. |
| return this.parser.update_project(this.config, options); |
| }; |
| |
| Api.prototype.addPlugin = function (pluginInfo, installOptions) { |
| |
| // console.log(new Error().stack); |
| if (!pluginInfo) { |
| return Promise.reject(new Error('The parameter is incorrect. The first parameter ' + |
| 'should be valid PluginInfo instance')); |
| } |
| |
| installOptions = installOptions || {}; |
| installOptions.variables = installOptions.variables || {}; |
| // CB-10108 platformVersion option is required for proper plugin installation |
| installOptions.platformVersion = installOptions.platformVersion || |
| this.getPlatformInfo().version; |
| |
| var self = this; |
| var actions = new ActionStack(); |
| var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root); |
| |
| // gather all files needs to be handled during install |
| pluginInfo.getFilesAndFrameworks(this.platform) |
| .concat(pluginInfo.getAssets(this.platform)) |
| .concat(pluginInfo.getJsModules(this.platform)) |
| .forEach(function (item) { |
| actions.push(actions.createAction( |
| self._getInstaller(item.itemType), |
| [item, pluginInfo.dir, pluginInfo.id, installOptions, projectFile], |
| self._getUninstaller(item.itemType), |
| [item, pluginInfo.dir, pluginInfo.id, installOptions, projectFile])); |
| }); |
| |
| // run through the action stack |
| return actions.process(this.platform, this.root) |
| .then(function () { |
| if (projectFile) { |
| projectFile.write(); |
| } |
| |
| // Add PACKAGE_NAME variable into vars |
| if (!installOptions.variables.PACKAGE_NAME) { |
| installOptions.variables.PACKAGE_NAME = self._handler.package_name(self.root); |
| } |
| |
| self._munger |
| // Ignore passed `is_top_level` option since platform itself doesn't know |
| // anything about managing dependencies - it's responsibility of caller. |
| .add_plugin_changes(pluginInfo, installOptions.variables, /* is_top_level= */true, /* should_increment= */true) |
| .save_all(); |
| |
| var targetDir = installOptions.usePlatformWww ? |
| self.getPlatformInfo().locations.platformWww : |
| self.getPlatformInfo().locations.www; |
| |
| self._addModulesInfo(pluginInfo, targetDir); |
| }); |
| }; |
| |
| Api.prototype.removePlugin = function (plugin, uninstallOptions) { |
| // console.log("NotImplemented :: browser-platform:Api:removePlugin ",plugin, uninstallOptions); |
| |
| uninstallOptions = uninstallOptions || {}; |
| // CB-10108 platformVersion option is required for proper plugin installation |
| uninstallOptions.platformVersion = uninstallOptions.platformVersion || |
| this.getPlatformInfo().version; |
| |
| var self = this; |
| var actions = new ActionStack(); |
| var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root); |
| |
| // queue up plugin files |
| plugin.getFilesAndFrameworks(this.platform) |
| .concat(plugin.getAssets(this.platform)) |
| .concat(plugin.getJsModules(this.platform)) |
| .forEach(function (item) { |
| actions.push(actions.createAction( |
| self._getUninstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile], |
| self._getInstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile])); |
| }); |
| |
| // run through the action stack |
| return actions.process(this.platform, this.root) |
| .then(function () { |
| if (projectFile) { |
| projectFile.write(); |
| } |
| |
| self._munger |
| // Ignore passed `is_top_level` option since platform itself doesn't know |
| // anything about managing dependencies - it's responsibility of caller. |
| .remove_plugin_changes(plugin, /* is_top_level= */true) |
| .save_all(); |
| |
| var targetDir = uninstallOptions.usePlatformWww ? |
| self.getPlatformInfo().locations.platformWww : |
| self.getPlatformInfo().locations.www; |
| |
| self._removeModulesInfo(plugin, targetDir); |
| // Remove stale plugin directory |
| // TODO: this should be done by plugin files uninstaller |
| shell.rm('-rf', path.resolve(self.root, 'Plugins', plugin.id)); |
| }); |
| }; |
| |
| Api.prototype._getInstaller = function (type) { |
| var self = this; |
| return function (item, plugin_dir, plugin_id, options, project) { |
| var installer = self._handler[type]; |
| |
| if (!installer) { |
| console.log('unrecognized type ' + type); |
| |
| } else { |
| var wwwDest = options.usePlatformWww ? |
| self.getPlatformInfo().locations.platformWww : |
| self._handler.www_dir(self.root); |
| |
| if (type === 'asset') { |
| installer.install(item, plugin_dir, wwwDest); |
| } else if (type === 'js-module') { |
| installer.install(item, plugin_dir, plugin_id, wwwDest); |
| } else { |
| installer.install(item, plugin_dir, self.root, plugin_id, options, project); |
| } |
| } |
| }; |
| }; |
| |
| Api.prototype._getUninstaller = function (type) { |
| var self = this; |
| return function (item, plugin_dir, plugin_id, options, project) { |
| var installer = self._handler[type]; |
| |
| if (!installer) { |
| console.log('browser plugin uninstall: unrecognized type, skipping : ' + type); |
| |
| } else { |
| var wwwDest = options.usePlatformWww ? |
| self.getPlatformInfo().locations.platformWww : |
| self._handler.www_dir(self.root); |
| |
| if (['asset', 'js-module'].indexOf(type) > -1) { |
| return installer.uninstall(item, wwwDest, plugin_id); |
| } else { |
| return installer.uninstall(item, self.root, plugin_id, options, project); |
| } |
| |
| } |
| }; |
| }; |
| |
| /** |
| * Removes the specified modules from list of installed modules and updates |
| * platform_json and cordova_plugins.js on disk. |
| * |
| * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules |
| * needs to be added. |
| * @param {String} targetDir The directory, where updated cordova_plugins.js |
| * should be written to. |
| */ |
| Api.prototype._addModulesInfo = function (plugin, targetDir) { |
| var installedModules = this._platformJson.root.modules || []; |
| |
| var installedPaths = installedModules.map(function (installedModule) { |
| return installedModule.file; |
| }); |
| |
| var modulesToInstall = plugin.getJsModules(this.platform) |
| .filter(function (moduleToInstall) { |
| return installedPaths.indexOf(moduleToInstall.file) === -1; |
| }).map(function (moduleToInstall) { |
| var moduleName = plugin.id + '.' + (moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1]); |
| var obj = { |
| file: ['plugins', plugin.id, moduleToInstall.src].join('/'), /* eslint no-useless-escape : 0 */ |
| id: moduleName, |
| pluginId: plugin.id |
| }; |
| if (moduleToInstall.clobbers.length > 0) { |
| obj.clobbers = moduleToInstall.clobbers.map(function (o) { return o.target; }); |
| } |
| if (moduleToInstall.merges.length > 0) { |
| obj.merges = moduleToInstall.merges.map(function (o) { return o.target; }); |
| } |
| if (moduleToInstall.runs) { |
| obj.runs = true; |
| } |
| |
| return obj; |
| }); |
| |
| this._platformJson.root.modules = installedModules.concat(modulesToInstall); |
| if (!this._platformJson.root.plugin_metadata) { |
| this._platformJson.root.plugin_metadata = {}; |
| } |
| this._platformJson.root.plugin_metadata[plugin.id] = plugin.version; |
| |
| this._writePluginModules(targetDir); |
| this._platformJson.save(); |
| }; |
| /** |
| * Fetches all installed modules, generates cordova_plugins contents and writes |
| * it to file. |
| * |
| * @param {String} targetDir Directory, where write cordova_plugins.js to. |
| * Ususally it is either <platform>/www or <platform>/platform_www |
| * directories. |
| */ |
| Api.prototype._writePluginModules = function (targetDir) { |
| // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js |
| var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n'; |
| final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, ' ') + ';\n'; |
| final_contents += 'module.exports.metadata = \n'; |
| final_contents += '// TOP OF METADATA\n'; |
| final_contents += JSON.stringify(this._platformJson.root.plugin_metadata || {}, null, ' ') + '\n'; |
| final_contents += '// BOTTOM OF METADATA\n'; |
| final_contents += '});'; // Close cordova.define. |
| |
| shell.mkdir('-p', targetDir); |
| fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8'); |
| }; |
| |
| /** |
| * Removes the specified modules from list of installed modules and updates |
| * platform_json and cordova_plugins.js on disk. |
| * |
| * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules |
| * needs to be removed. |
| * @param {String} targetDir The directory, where updated cordova_plugins.js |
| * should be written to. |
| */ |
| Api.prototype._removeModulesInfo = function (plugin, targetDir) { |
| var installedModules = this._platformJson.root.modules || []; |
| var modulesToRemove = plugin.getJsModules(this.platform) |
| .map(function (jsModule) { |
| return ['plugins', plugin.id, jsModule.src].join('/'); |
| }); |
| |
| var updatedModules = installedModules |
| .filter(function (installedModule) { |
| return (modulesToRemove.indexOf(installedModule.file) === -1); |
| }); |
| |
| this._platformJson.root.modules = updatedModules; |
| if (this._platformJson.root.plugin_metadata) { |
| delete this._platformJson.root.plugin_metadata[plugin.id]; |
| } |
| |
| this._writePluginModules(targetDir); |
| this._platformJson.save(); |
| }; |
| |
| Api.prototype.build = function (buildOptions) { |
| var self = this; |
| return require('./lib/check_reqs').run() |
| .then(function () { |
| return require('./lib/build').run.call(self, buildOptions); |
| }); |
| }; |
| |
| Api.prototype.run = function (runOptions) { |
| return require('./lib/run').run(runOptions); |
| }; |
| |
| Api.prototype.clean = function (cleanOptions) { |
| return require('./lib/clean').run(cleanOptions); |
| }; |
| |
| Api.prototype.requirements = function () { |
| return require('./lib/check_reqs').run(); |
| }; |
| |
| module.exports = Api; |