123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- /**
- 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, options);
- // 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;
|