532 lines
21 KiB
JavaScript
532 lines
21 KiB
JavaScript
/**
|
|
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;
|