801 lines
43 KiB
JavaScript
801 lines
43 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.writeCordovaAndroidManifest = exports.getCordovaPreferences = exports.needsStaticPod = exports.getIncompatibleCordovaPlugins = exports.checkPluginDependencies = exports.logCordovaManualSteps = exports.getCordovaPlugins = exports.handleCordovaPluginsJS = exports.autoGenerateConfig = exports.removePluginFiles = exports.createEmptyCordovaJS = exports.copyCordovaJS = exports.copyPluginsJS = exports.generateCordovaPluginsJSFile = void 0;
|
|
const tslib_1 = require("tslib");
|
|
const fs_extra_1 = require("fs-extra");
|
|
const path_1 = require("path");
|
|
const plist_1 = tslib_1.__importDefault(require("plist"));
|
|
const prompts_1 = tslib_1.__importDefault(require("prompts"));
|
|
const common_1 = require("./android/common");
|
|
const colors_1 = tslib_1.__importDefault(require("./colors"));
|
|
const errors_1 = require("./errors");
|
|
const common_2 = require("./ios/common");
|
|
const log_1 = require("./log");
|
|
const plugin_1 = require("./plugin");
|
|
const node_1 = require("./util/node");
|
|
const term_1 = require("./util/term");
|
|
const xml_1 = require("./util/xml");
|
|
/**
|
|
* Build the root cordova_plugins.js file referencing each Plugin JS file.
|
|
*/
|
|
function generateCordovaPluginsJSFile(config, plugins, platform) {
|
|
const pluginModules = [];
|
|
const pluginExports = [];
|
|
plugins.map((p) => {
|
|
const pluginId = p.xml.$.id;
|
|
const jsModules = (0, plugin_1.getJSModules)(p, platform);
|
|
jsModules.map((jsModule) => {
|
|
const clobbers = [];
|
|
const merges = [];
|
|
let clobbersModule = '';
|
|
let mergesModule = '';
|
|
let runsModule = '';
|
|
let clobberKey = '';
|
|
let mergeKey = '';
|
|
if (jsModule.clobbers) {
|
|
jsModule.clobbers.map((clobber) => {
|
|
clobbers.push(clobber.$.target);
|
|
clobberKey = clobber.$.target;
|
|
});
|
|
clobbersModule = `,
|
|
"clobbers": [
|
|
"${clobbers.join('",\n "')}"
|
|
]`;
|
|
}
|
|
if (jsModule.merges) {
|
|
jsModule.merges.map((merge) => {
|
|
merges.push(merge.$.target);
|
|
mergeKey = merge.$.target;
|
|
});
|
|
mergesModule = `,
|
|
"merges": [
|
|
"${merges.join('",\n "')}"
|
|
]`;
|
|
}
|
|
if (jsModule.runs) {
|
|
runsModule = ',\n "runs": true';
|
|
}
|
|
const pluginModule = {
|
|
clobber: clobberKey,
|
|
merge: mergeKey,
|
|
// mimics Cordova's module name logic if the name attr is missing
|
|
pluginContent: `{
|
|
"id": "${pluginId + '.' + (jsModule.$.name || jsModule.$.src.match(/([^/]+)\.js/)[1])}",
|
|
"file": "plugins/${pluginId}/${jsModule.$.src}",
|
|
"pluginId": "${pluginId}"${clobbersModule}${mergesModule}${runsModule}
|
|
}`,
|
|
};
|
|
pluginModules.push(pluginModule);
|
|
});
|
|
pluginExports.push(`"${pluginId}": "${p.xml.$.version}"`);
|
|
});
|
|
return `
|
|
cordova.define('cordova/plugin_list', function(require, exports, module) {
|
|
module.exports = [
|
|
${pluginModules
|
|
.sort((a, b) => a.clobber && b.clobber // Clobbers in alpha order
|
|
? a.clobber.localeCompare(b.clobber)
|
|
: a.clobber || b.clobber // Clobbers before anything else
|
|
? b.clobber.localeCompare(a.clobber)
|
|
: a.merge.localeCompare(b.merge))
|
|
.map((e) => e.pluginContent)
|
|
.join(',\n ')}
|
|
];
|
|
module.exports.metadata =
|
|
// TOP OF METADATA
|
|
{
|
|
${pluginExports.join(',\n ')}
|
|
};
|
|
// BOTTOM OF METADATA
|
|
});
|
|
`;
|
|
}
|
|
exports.generateCordovaPluginsJSFile = generateCordovaPluginsJSFile;
|
|
/**
|
|
* Build the plugins/* files for each Cordova plugin installed.
|
|
*/
|
|
async function copyPluginsJS(config, cordovaPlugins, platform) {
|
|
const webDir = await getWebDir(config, platform);
|
|
const pluginsDir = (0, path_1.join)(webDir, 'plugins');
|
|
const cordovaPluginsJSFile = (0, path_1.join)(webDir, 'cordova_plugins.js');
|
|
await removePluginFiles(config, platform);
|
|
await Promise.all(cordovaPlugins.map(async (p) => {
|
|
const pluginId = p.xml.$.id;
|
|
const pluginDir = (0, path_1.join)(pluginsDir, pluginId, 'www');
|
|
await (0, fs_extra_1.ensureDir)(pluginDir);
|
|
const jsModules = (0, plugin_1.getJSModules)(p, platform);
|
|
await Promise.all(jsModules.map(async (jsModule) => {
|
|
const filePath = (0, path_1.join)(webDir, 'plugins', pluginId, jsModule.$.src);
|
|
await (0, fs_extra_1.copy)((0, path_1.join)(p.rootPath, jsModule.$.src), filePath);
|
|
let data = await (0, fs_extra_1.readFile)(filePath, { encoding: 'utf-8' });
|
|
data = data.trim();
|
|
// mimics Cordova's module name logic if the name attr is missing
|
|
const name = pluginId + '.' + (jsModule.$.name || (0, path_1.basename)(jsModule.$.src, (0, path_1.extname)(jsModule.$.src)));
|
|
data = `cordova.define("${name}", function(require, exports, module) { \n${data}\n});`;
|
|
data = data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi, '');
|
|
await (0, fs_extra_1.writeFile)(filePath, data, { encoding: 'utf-8' });
|
|
}));
|
|
const assets = (0, plugin_1.getAssets)(p, platform);
|
|
await Promise.all(assets.map(async (asset) => {
|
|
const filePath = (0, path_1.join)(webDir, asset.$.target);
|
|
await (0, fs_extra_1.copy)((0, path_1.join)(p.rootPath, asset.$.src), filePath);
|
|
}));
|
|
}));
|
|
await (0, fs_extra_1.writeFile)(cordovaPluginsJSFile, generateCordovaPluginsJSFile(config, cordovaPlugins, platform));
|
|
}
|
|
exports.copyPluginsJS = copyPluginsJS;
|
|
async function copyCordovaJS(config, platform) {
|
|
const cordovaPath = (0, node_1.resolveNode)(config.app.rootDir, '@capacitor/core', 'cordova.js');
|
|
if (!cordovaPath) {
|
|
(0, errors_1.fatal)(`Unable to find ${colors_1.default.strong('node_modules/@capacitor/core/cordova.js')}.\n` +
|
|
`Are you sure ${colors_1.default.strong('@capacitor/core')} is installed?`);
|
|
}
|
|
return (0, fs_extra_1.copy)(cordovaPath, (0, path_1.join)(await getWebDir(config, platform), 'cordova.js'));
|
|
}
|
|
exports.copyCordovaJS = copyCordovaJS;
|
|
async function createEmptyCordovaJS(config, platform) {
|
|
const webDir = await getWebDir(config, platform);
|
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(webDir, 'cordova.js'), '');
|
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(webDir, 'cordova_plugins.js'), '');
|
|
}
|
|
exports.createEmptyCordovaJS = createEmptyCordovaJS;
|
|
async function removePluginFiles(config, platform) {
|
|
const webDir = await getWebDir(config, platform);
|
|
const pluginsDir = (0, path_1.join)(webDir, 'plugins');
|
|
const cordovaPluginsJSFile = (0, path_1.join)(webDir, 'cordova_plugins.js');
|
|
await (0, fs_extra_1.remove)(pluginsDir);
|
|
await (0, fs_extra_1.remove)(cordovaPluginsJSFile);
|
|
}
|
|
exports.removePluginFiles = removePluginFiles;
|
|
async function autoGenerateConfig(config, cordovaPlugins, platform) {
|
|
var _a, _b, _c, _d;
|
|
let xmlDir = (0, path_1.join)(config.android.resDirAbs, 'xml');
|
|
const fileName = 'config.xml';
|
|
if (platform === 'ios') {
|
|
xmlDir = config.ios.nativeTargetDirAbs;
|
|
}
|
|
await (0, fs_extra_1.ensureDir)(xmlDir);
|
|
const cordovaConfigXMLFile = (0, path_1.join)(xmlDir, fileName);
|
|
await (0, fs_extra_1.remove)(cordovaConfigXMLFile);
|
|
const pluginEntries = [];
|
|
cordovaPlugins.map((p) => {
|
|
const currentPlatform = (0, plugin_1.getPluginPlatform)(p, platform);
|
|
if (currentPlatform) {
|
|
const configFiles = currentPlatform['config-file'];
|
|
if (configFiles) {
|
|
const configXMLEntries = configFiles.filter(function (item) {
|
|
var _a;
|
|
return (_a = item.$) === null || _a === void 0 ? void 0 : _a.target.includes(fileName);
|
|
});
|
|
configXMLEntries.map((entry) => {
|
|
if (entry.feature) {
|
|
const feature = { feature: entry.feature };
|
|
pluginEntries.push(feature);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
let accessOriginString = [];
|
|
if ((_b = (_a = config.app.extConfig) === null || _a === void 0 ? void 0 : _a.cordova) === null || _b === void 0 ? void 0 : _b.accessOrigins) {
|
|
accessOriginString = await Promise.all(config.app.extConfig.cordova.accessOrigins.map(async (host) => {
|
|
return `
|
|
<access origin="${host}" />`;
|
|
}));
|
|
}
|
|
else {
|
|
accessOriginString.push(`<access origin="*" />`);
|
|
}
|
|
const pluginEntriesString = await Promise.all(pluginEntries.map(async (item) => {
|
|
const xmlString = await (0, xml_1.writeXML)(item);
|
|
return xmlString;
|
|
}));
|
|
let pluginPreferencesString = [];
|
|
if ((_d = (_c = config.app.extConfig) === null || _c === void 0 ? void 0 : _c.cordova) === null || _d === void 0 ? void 0 : _d.preferences) {
|
|
pluginPreferencesString = await Promise.all(Object.entries(config.app.extConfig.cordova.preferences).map(async ([key, value]) => {
|
|
return `
|
|
<preference name="${key}" value="${value}" />`;
|
|
}));
|
|
}
|
|
const content = `<?xml version='1.0' encoding='utf-8'?>
|
|
<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
|
${accessOriginString.join('')}
|
|
${pluginEntriesString.join('')}
|
|
${pluginPreferencesString.join('')}
|
|
</widget>`;
|
|
await (0, fs_extra_1.writeFile)(cordovaConfigXMLFile, content);
|
|
}
|
|
exports.autoGenerateConfig = autoGenerateConfig;
|
|
async function getWebDir(config, platform) {
|
|
if (platform === 'ios') {
|
|
return config.ios.webDirAbs;
|
|
}
|
|
if (platform === 'android') {
|
|
return config.android.webDirAbs;
|
|
}
|
|
return '';
|
|
}
|
|
async function handleCordovaPluginsJS(cordovaPlugins, config, platform) {
|
|
const webDir = await getWebDir(config, platform);
|
|
await (0, fs_extra_1.mkdirp)(webDir);
|
|
if (cordovaPlugins.length > 0) {
|
|
(0, plugin_1.printPlugins)(cordovaPlugins, platform, 'cordova');
|
|
await copyCordovaJS(config, platform);
|
|
await copyPluginsJS(config, cordovaPlugins, platform);
|
|
}
|
|
else {
|
|
await removePluginFiles(config, platform);
|
|
await createEmptyCordovaJS(config, platform);
|
|
}
|
|
await autoGenerateConfig(config, cordovaPlugins, platform);
|
|
}
|
|
exports.handleCordovaPluginsJS = handleCordovaPluginsJS;
|
|
async function getCordovaPlugins(config, platform) {
|
|
const allPlugins = await (0, plugin_1.getPlugins)(config, platform);
|
|
let plugins = [];
|
|
if (platform === config.ios.name) {
|
|
plugins = await (0, common_2.getIOSPlugins)(allPlugins);
|
|
}
|
|
else if (platform === config.android.name) {
|
|
plugins = await (0, common_1.getAndroidPlugins)(allPlugins);
|
|
}
|
|
return plugins.filter((p) => (0, plugin_1.getPluginType)(p, platform) === 1 /* PluginType.Cordova */);
|
|
}
|
|
exports.getCordovaPlugins = getCordovaPlugins;
|
|
async function logCordovaManualSteps(cordovaPlugins, config, platform) {
|
|
cordovaPlugins.map((p) => {
|
|
const editConfig = (0, plugin_1.getPlatformElement)(p, platform, 'edit-config');
|
|
const configFile = (0, plugin_1.getPlatformElement)(p, platform, 'config-file');
|
|
editConfig.concat(configFile).map(async (configElement) => {
|
|
if (configElement.$ && !configElement.$.target.includes('config.xml')) {
|
|
if (platform === config.ios.name) {
|
|
if (configElement.$.target.includes('Info.plist')) {
|
|
logiOSPlist(configElement, config, p);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
exports.logCordovaManualSteps = logCordovaManualSteps;
|
|
async function logiOSPlist(configElement, config, plugin) {
|
|
var _a, _b;
|
|
let plistPath = (0, path_1.resolve)(config.ios.nativeTargetDirAbs, 'Info.plist');
|
|
if ((_a = config.app.extConfig.ios) === null || _a === void 0 ? void 0 : _a.scheme) {
|
|
plistPath = (0, path_1.resolve)(config.ios.nativeProjectDirAbs, `${(_b = config.app.extConfig.ios) === null || _b === void 0 ? void 0 : _b.scheme}-Info.plist`);
|
|
}
|
|
if (!(await (0, fs_extra_1.pathExists)(plistPath))) {
|
|
plistPath = (0, path_1.resolve)(config.ios.nativeTargetDirAbs, 'Base.lproj', 'Info.plist');
|
|
}
|
|
if (await (0, fs_extra_1.pathExists)(plistPath)) {
|
|
const xmlMeta = await (0, xml_1.readXML)(plistPath);
|
|
const data = await (0, fs_extra_1.readFile)(plistPath, { encoding: 'utf-8' });
|
|
const trimmedPlistData = data.replace(/(\t|\r|\n)/g, '');
|
|
const plistData = plist_1.default.parse(data);
|
|
const dict = xmlMeta.plist.dict.pop();
|
|
if (!dict.key.includes(configElement.$.parent)) {
|
|
let xml = buildConfigFileXml(configElement);
|
|
xml = `<key>${configElement.$.parent}</key>${getConfigFileTagContent(xml)}`;
|
|
log_1.logger.warn(`Configuration required for ${colors_1.default.strong(plugin.id)}.\n` + `Add the following to Info.plist:\n` + xml);
|
|
}
|
|
else if (configElement.array || configElement.dict) {
|
|
if (configElement.array && configElement.array.length > 0 && configElement.array[0].string) {
|
|
let xml = '';
|
|
configElement.array[0].string.map((element) => {
|
|
const d = plistData[configElement.$.parent];
|
|
if (Array.isArray(d) && !d.includes(element)) {
|
|
xml = xml.concat(`<string>${element}</string>\n`);
|
|
}
|
|
});
|
|
if (xml.length > 0) {
|
|
log_1.logger.warn(`Configuration required for ${colors_1.default.strong(plugin.id)}.\n` +
|
|
`Add the following in the existing ${colors_1.default.strong(configElement.$.parent)} array of your Info.plist:\n` +
|
|
xml);
|
|
}
|
|
}
|
|
else {
|
|
let xml = buildConfigFileXml(configElement);
|
|
xml = `<key>${configElement.$.parent}</key>${getConfigFileTagContent(xml)}`;
|
|
xml = `<plist version="1.0"><dict>${xml}</dict></plist>`;
|
|
const parseXmlToSearchable = (childElementsObj, arrayToAddTo) => {
|
|
for (const childElement of childElementsObj) {
|
|
const childElementName = childElement['#name'];
|
|
const toAdd = { name: childElementName };
|
|
if (childElementName === 'key' || childElementName === 'string') {
|
|
toAdd.value = childElement['_'];
|
|
}
|
|
else {
|
|
if (childElement['$']) {
|
|
toAdd.attrs = { ...childElement['$'] };
|
|
}
|
|
if (childElement['$$']) {
|
|
toAdd.children = [];
|
|
parseXmlToSearchable(childElement['$$'], toAdd['children']);
|
|
}
|
|
}
|
|
arrayToAddTo.push(toAdd);
|
|
}
|
|
};
|
|
const existingElements = (0, xml_1.parseXML)(trimmedPlistData, {
|
|
explicitChildren: true,
|
|
trim: true,
|
|
preserveChildrenOrder: true,
|
|
});
|
|
const parsedExistingElements = [];
|
|
const rootKeyOfExistingElements = Object.keys(existingElements)[0];
|
|
const rootOfExistingElementsToAdd = { name: rootKeyOfExistingElements, children: [] };
|
|
if (existingElements[rootKeyOfExistingElements]['$']) {
|
|
rootOfExistingElementsToAdd.attrs = {
|
|
...existingElements[rootKeyOfExistingElements]['$'],
|
|
};
|
|
}
|
|
parseXmlToSearchable(existingElements[rootKeyOfExistingElements]['$$'], rootOfExistingElementsToAdd['children']);
|
|
parsedExistingElements.push(rootOfExistingElementsToAdd);
|
|
const requiredElements = (0, xml_1.parseXML)(xml, {
|
|
explicitChildren: true,
|
|
trim: true,
|
|
preserveChildrenOrder: true,
|
|
});
|
|
const parsedRequiredElements = [];
|
|
const rootKeyOfRequiredElements = Object.keys(requiredElements)[0];
|
|
const rootOfRequiredElementsToAdd = { name: rootKeyOfRequiredElements, children: [] };
|
|
if (requiredElements[rootKeyOfRequiredElements]['$']) {
|
|
rootOfRequiredElementsToAdd.attrs = {
|
|
...requiredElements[rootKeyOfRequiredElements]['$'],
|
|
};
|
|
}
|
|
parseXmlToSearchable(requiredElements[rootKeyOfRequiredElements]['$$'], rootOfRequiredElementsToAdd['children']);
|
|
parsedRequiredElements.push(rootOfRequiredElementsToAdd);
|
|
const doesContainElements = (requiredElementsArray, existingElementsArray) => {
|
|
for (const requiredElement of requiredElementsArray) {
|
|
if (requiredElement.name === 'key' || requiredElement.name === 'string') {
|
|
let foundMatch = false;
|
|
for (const existingElement of existingElementsArray) {
|
|
if (existingElement.name === requiredElement.name &&
|
|
(existingElement.value === requiredElement.value ||
|
|
/^[$].{1,}$/.test(requiredElement.value.trim()))) {
|
|
foundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
let foundMatch = false;
|
|
for (const existingElement of existingElementsArray) {
|
|
if (existingElement.name === requiredElement.name) {
|
|
if ((requiredElement.children !== undefined) === (existingElement.children !== undefined)) {
|
|
if (doesContainElements(requiredElement.children, existingElement.children)) {
|
|
foundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
if (!doesContainElements(parsedRequiredElements, parsedExistingElements)) {
|
|
logPossibleMissingItem(configElement, plugin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
logPossibleMissingItem(configElement, plugin);
|
|
}
|
|
}
|
|
function logPossibleMissingItem(configElement, plugin) {
|
|
let xml = buildConfigFileXml(configElement);
|
|
xml = getConfigFileTagContent(xml);
|
|
xml = removeOuterTags(xml);
|
|
log_1.logger.warn(`Configuration might be missing for ${colors_1.default.strong(plugin.id)}.\n` +
|
|
`Add the following to the existing ${colors_1.default.strong(configElement.$.parent)} entry of Info.plist:\n` +
|
|
xml);
|
|
}
|
|
function buildConfigFileXml(configElement) {
|
|
return (0, xml_1.buildXmlElement)(configElement, 'config-file');
|
|
}
|
|
function getConfigFileTagContent(str) {
|
|
return str.replace(/<config-file.+">|<\/config-file>/g, '');
|
|
}
|
|
function removeOuterTags(str) {
|
|
const start = str.indexOf('>') + 1;
|
|
const end = str.lastIndexOf('<');
|
|
return str.substring(start, end);
|
|
}
|
|
async function checkPluginDependencies(plugins, platform, failOnMissingDeps = false) {
|
|
const pluginDeps = new Map();
|
|
const cordovaPlugins = plugins.filter((p) => (0, plugin_1.getPluginType)(p, platform) === 1 /* PluginType.Cordova */);
|
|
const incompatible = plugins.filter((p) => (0, plugin_1.getPluginType)(p, platform) === 2 /* PluginType.Incompatible */);
|
|
await Promise.all(cordovaPlugins.map(async (p) => {
|
|
let allDependencies = [];
|
|
allDependencies = allDependencies.concat((0, plugin_1.getPlatformElement)(p, platform, 'dependency'));
|
|
if (p.xml['dependency']) {
|
|
allDependencies = allDependencies.concat(p.xml['dependency']);
|
|
}
|
|
allDependencies = allDependencies.filter((dep) => !getIncompatibleCordovaPlugins(platform).includes(dep.$.id) &&
|
|
incompatible.filter((p) => p.id === dep.$.id || p.xml.$.id === dep.$.id).length === 0);
|
|
if (allDependencies) {
|
|
await Promise.all(allDependencies.map(async (dep) => {
|
|
var _a;
|
|
let plugin = dep.$.id;
|
|
let version = dep.$.version;
|
|
if (plugin.includes('@') && plugin.indexOf('@') !== 0) {
|
|
[plugin, version] = plugin.split('@');
|
|
}
|
|
if (cordovaPlugins.filter((p) => p.id === plugin || p.xml.$.id === plugin).length === 0) {
|
|
if ((_a = dep.$.url) === null || _a === void 0 ? void 0 : _a.startsWith('http')) {
|
|
plugin = dep.$.url;
|
|
version = dep.$.commit;
|
|
}
|
|
const deps = pluginDeps.get(p.id) || [];
|
|
deps.push(`${plugin}${version ? colors_1.default.weak(` (${version})`) : ''}`);
|
|
pluginDeps.set(p.id, deps);
|
|
}
|
|
}));
|
|
}
|
|
}));
|
|
if (pluginDeps.size > 0) {
|
|
let msg = `${colors_1.default.failure(colors_1.default.strong('Plugins are missing dependencies.'))}\n` +
|
|
`Cordova plugin dependencies must be installed in your project (e.g. w/ ${colors_1.default.input('npm install')}).\n`;
|
|
for (const [plugin, deps] of pluginDeps.entries()) {
|
|
msg += `\n ${colors_1.default.strong(plugin)} is missing dependencies:\n` + deps.map((d) => ` - ${d}`).join('\n');
|
|
}
|
|
if (failOnMissingDeps) {
|
|
(0, errors_1.fatal)(`${msg}\n`);
|
|
}
|
|
log_1.logger.warn(`${msg}\n`);
|
|
}
|
|
}
|
|
exports.checkPluginDependencies = checkPluginDependencies;
|
|
function getIncompatibleCordovaPlugins(platform) {
|
|
const pluginList = [
|
|
'cordova-plugin-splashscreen',
|
|
'cordova-plugin-ionic-webview',
|
|
'cordova-plugin-crosswalk-webview',
|
|
'cordova-plugin-wkwebview-engine',
|
|
'cordova-plugin-console',
|
|
'cordova-plugin-music-controls',
|
|
'cordova-plugin-add-swift-support',
|
|
'cordova-plugin-ionic-keyboard',
|
|
'cordova-plugin-braintree',
|
|
'@ionic-enterprise/filesystem',
|
|
'@ionic-enterprise/keyboard',
|
|
'@ionic-enterprise/splashscreen',
|
|
'cordova-support-google-services',
|
|
];
|
|
if (platform === 'ios') {
|
|
pluginList.push('cordova-plugin-statusbar', '@ionic-enterprise/statusbar', 'SalesforceMobileSDK-CordovaPlugin');
|
|
}
|
|
if (platform === 'android') {
|
|
pluginList.push('cordova-plugin-compat');
|
|
}
|
|
return pluginList;
|
|
}
|
|
exports.getIncompatibleCordovaPlugins = getIncompatibleCordovaPlugins;
|
|
function needsStaticPod(plugin) {
|
|
return useFrameworks(plugin);
|
|
}
|
|
exports.needsStaticPod = needsStaticPod;
|
|
function useFrameworks(plugin) {
|
|
const podspecs = (0, plugin_1.getPlatformElement)(plugin, 'ios', 'podspec');
|
|
const frameworkPods = podspecs.filter((podspec) => podspec.pods.filter((pods) => pods.$ && pods.$['use-frameworks'] === 'true').length > 0);
|
|
return frameworkPods.length > 0;
|
|
}
|
|
async function getCordovaPreferences(config) {
|
|
var _a, _b, _c, _d, _e;
|
|
const configXml = (0, path_1.join)(config.app.rootDir, 'config.xml');
|
|
let cordova = {};
|
|
if (await (0, fs_extra_1.pathExists)(configXml)) {
|
|
cordova.preferences = {};
|
|
const xmlMeta = await (0, xml_1.readXML)(configXml);
|
|
if (xmlMeta.widget.preference) {
|
|
xmlMeta.widget.preference.map((pref) => {
|
|
cordova.preferences[pref.$.name] = pref.$.value;
|
|
});
|
|
}
|
|
}
|
|
if (cordova.preferences && Object.keys(cordova.preferences).length > 0) {
|
|
if ((0, term_1.isInteractive)()) {
|
|
const answers = await (0, log_1.logPrompt)(`${colors_1.default.strong(`Cordova preferences can be automatically ported to ${colors_1.default.strong(config.app.extConfigName)}.`)}\n` +
|
|
`Keep in mind: Not all values can be automatically migrated from ${colors_1.default.strong('config.xml')}. There may be more work to do.\n` +
|
|
`More info: ${colors_1.default.strong('https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor')}`, {
|
|
type: 'confirm',
|
|
name: 'confirm',
|
|
message: `Migrate Cordova preferences from config.xml?`,
|
|
initial: true,
|
|
});
|
|
if (answers.confirm) {
|
|
if ((_b = (_a = config.app.extConfig) === null || _a === void 0 ? void 0 : _a.cordova) === null || _b === void 0 ? void 0 : _b.preferences) {
|
|
const answers = await (0, prompts_1.default)([
|
|
{
|
|
type: 'confirm',
|
|
name: 'confirm',
|
|
message: `${config.app.extConfigName} already contains Cordova preferences. Overwrite?`,
|
|
},
|
|
], { onCancel: () => process.exit(1) });
|
|
if (!answers.confirm) {
|
|
cordova = (_c = config.app.extConfig) === null || _c === void 0 ? void 0 : _c.cordova;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
cordova = (_d = config.app.extConfig) === null || _d === void 0 ? void 0 : _d.cordova;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
cordova = (_e = config.app.extConfig) === null || _e === void 0 ? void 0 : _e.cordova;
|
|
}
|
|
return cordova;
|
|
}
|
|
exports.getCordovaPreferences = getCordovaPreferences;
|
|
async function writeCordovaAndroidManifest(cordovaPlugins, config, platform, cleartext) {
|
|
var _a;
|
|
const manifestPath = (0, path_1.join)(config.android.cordovaPluginsDirAbs, 'src', 'main', 'AndroidManifest.xml');
|
|
const rootXMLEntries = [];
|
|
const applicationXMLEntries = [];
|
|
const applicationXMLAttributes = [];
|
|
let prefsArray = [];
|
|
cordovaPlugins.map(async (p) => {
|
|
const editConfig = (0, plugin_1.getPlatformElement)(p, platform, 'edit-config');
|
|
const configFile = (0, plugin_1.getPlatformElement)(p, platform, 'config-file');
|
|
prefsArray = prefsArray.concat((0, plugin_1.getAllElements)(p, platform, 'preference'));
|
|
editConfig.concat(configFile).map(async (configElement) => {
|
|
var _a, _b;
|
|
if (configElement.$ &&
|
|
(((_a = configElement.$.target) === null || _a === void 0 ? void 0 : _a.includes('AndroidManifest.xml')) ||
|
|
((_b = configElement.$.file) === null || _b === void 0 ? void 0 : _b.includes('AndroidManifest.xml')))) {
|
|
const keys = Object.keys(configElement).filter((k) => k !== '$');
|
|
keys.map((k) => {
|
|
configElement[k].map(async (e) => {
|
|
const xmlElement = (0, xml_1.buildXmlElement)(e, k);
|
|
const pathParts = getPathParts(configElement.$.parent || configElement.$.target);
|
|
if (pathParts.length > 1) {
|
|
if (pathParts.pop() === 'application') {
|
|
if (configElement.$.mode && configElement.$.mode === 'merge' && xmlElement.startsWith('<application')) {
|
|
Object.keys(e.$).map((ek) => {
|
|
applicationXMLAttributes.push(`${ek}="${e.$[ek]}"`);
|
|
});
|
|
}
|
|
else if (!applicationXMLEntries.includes(xmlElement) &&
|
|
!contains(applicationXMLEntries, xmlElement, k)) {
|
|
applicationXMLEntries.push(xmlElement);
|
|
}
|
|
}
|
|
else {
|
|
const manifestPathOfCapApp = (0, path_1.join)(config.android.appDirAbs, 'src', 'main', 'AndroidManifest.xml');
|
|
const manifestContentTrimmed = (await (0, fs_extra_1.readFile)(manifestPathOfCapApp))
|
|
.toString()
|
|
.trim()
|
|
.replace(/\n|\t|\r/g, '')
|
|
.replace(/[\s]{1,}</g, '<')
|
|
.replace(/>[\s]{1,}/g, '>')
|
|
.replace(/[\s]{2,}/g, ' ');
|
|
const requiredManifestContentTrimmed = xmlElement
|
|
.trim()
|
|
.replace(/\n|\t|\r/g, '')
|
|
.replace(/[\s]{1,}</g, '<')
|
|
.replace(/>[\s]{1,}/g, '>')
|
|
.replace(/[\s]{2,}/g, ' ');
|
|
const pathPartList = getPathParts(configElement.$.parent || configElement.$.target);
|
|
const doesXmlManifestContainRequiredInfo = (requiredElements, existingElements, pathTarget) => {
|
|
const findElementsToSearchIn = (existingElements, pathTarget) => {
|
|
const parts = [...pathTarget];
|
|
const elementsToSearchNextIn = [];
|
|
for (const existingElement of existingElements) {
|
|
if (existingElement.name === pathTarget[0]) {
|
|
if (existingElement.children) {
|
|
for (const el of existingElement.children) {
|
|
elementsToSearchNextIn.push(el);
|
|
}
|
|
}
|
|
else {
|
|
elementsToSearchNextIn.push(existingElement);
|
|
}
|
|
}
|
|
}
|
|
if (elementsToSearchNextIn.length === 0) {
|
|
return [];
|
|
}
|
|
else {
|
|
parts.splice(0, 1);
|
|
if (parts.length <= 0) {
|
|
return elementsToSearchNextIn;
|
|
}
|
|
else {
|
|
return findElementsToSearchIn(elementsToSearchNextIn, parts);
|
|
}
|
|
}
|
|
};
|
|
const parseXmlToSearchable = (childElementsObj, arrayToAddTo) => {
|
|
for (const childElementKey of Object.keys(childElementsObj)) {
|
|
for (const occurannceOfElement of childElementsObj[childElementKey]) {
|
|
const toAdd = { name: childElementKey };
|
|
if (occurannceOfElement['$']) {
|
|
toAdd.attrs = { ...occurannceOfElement['$'] };
|
|
}
|
|
if (occurannceOfElement['$$']) {
|
|
toAdd.children = [];
|
|
parseXmlToSearchable(occurannceOfElement['$$'], toAdd['children']);
|
|
}
|
|
arrayToAddTo.push(toAdd);
|
|
}
|
|
}
|
|
};
|
|
const doesElementMatch = (requiredElement, existingElement) => {
|
|
var _a;
|
|
if (requiredElement.name !== existingElement.name) {
|
|
return false;
|
|
}
|
|
if ((requiredElement.attrs !== undefined) !== (existingElement.attrs !== undefined)) {
|
|
return false;
|
|
}
|
|
else {
|
|
if (requiredElement.attrs !== undefined) {
|
|
const requiredELementAttrKeys = Object.keys(requiredElement.attrs);
|
|
for (const key of requiredELementAttrKeys) {
|
|
if (!/^[$].{1,}$/.test(requiredElement.attrs[key].trim())) {
|
|
if (requiredElement.attrs[key] !== existingElement.attrs[key]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((requiredElement.children !== undefined) !== (existingElement.children !== undefined) &&
|
|
((_a = requiredElement.children) === null || _a === void 0 ? void 0 : _a.length) !== 0) {
|
|
return false;
|
|
}
|
|
else {
|
|
if (requiredElement.children !== undefined) {
|
|
// each req element is in existing element
|
|
for (const requiredElementItem of requiredElement.children) {
|
|
let foundRequiredElement = false;
|
|
for (const existingElementItem of existingElement.children) {
|
|
const foundRequiredElementIn = doesElementMatch(requiredElementItem, existingElementItem);
|
|
if (foundRequiredElementIn) {
|
|
foundRequiredElement = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundRequiredElement) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (requiredElement.children === undefined && existingElement.children === undefined) {
|
|
return true;
|
|
}
|
|
else {
|
|
let foundRequiredElement = false;
|
|
for (const existingElementItem of existingElement.children) {
|
|
const foundRequiredElementIn = doesElementMatch(requiredElement, existingElementItem);
|
|
if (foundRequiredElementIn) {
|
|
foundRequiredElement = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundRequiredElement) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
const parsedExistingElements = [];
|
|
const rootKeyOfExistingElements = Object.keys(existingElements)[0];
|
|
const rootOfExistingElementsToAdd = { name: rootKeyOfExistingElements, children: [] };
|
|
if (existingElements[rootKeyOfExistingElements]['$']) {
|
|
rootOfExistingElementsToAdd.attrs = {
|
|
...existingElements[rootKeyOfExistingElements]['$'],
|
|
};
|
|
}
|
|
parseXmlToSearchable(existingElements[rootKeyOfExistingElements]['$$'], rootOfExistingElementsToAdd['children']);
|
|
parsedExistingElements.push(rootOfExistingElementsToAdd);
|
|
const parsedRequiredElements = [];
|
|
const rootKeyOfRequiredElements = Object.keys(requiredElements)[0];
|
|
const rootOfRequiredElementsToAdd = { name: rootKeyOfRequiredElements, children: [] };
|
|
if (requiredElements[rootKeyOfRequiredElements]['$']) {
|
|
rootOfRequiredElementsToAdd.attrs = {
|
|
...requiredElements[rootKeyOfRequiredElements]['$'],
|
|
};
|
|
}
|
|
if (requiredElements[rootKeyOfRequiredElements]['$$'] !== undefined) {
|
|
parseXmlToSearchable(requiredElements[rootKeyOfRequiredElements]['$$'], rootOfRequiredElementsToAdd['children']);
|
|
}
|
|
parsedRequiredElements.push(rootOfRequiredElementsToAdd);
|
|
const elementsToSearch = findElementsToSearchIn(parsedExistingElements, pathTarget);
|
|
for (const requiredElement of parsedRequiredElements) {
|
|
let foundMatch = false;
|
|
for (const existingElement of elementsToSearch) {
|
|
const doesContain = doesElementMatch(requiredElement, existingElement);
|
|
if (doesContain) {
|
|
foundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundMatch) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
if (!doesXmlManifestContainRequiredInfo((0, xml_1.parseXML)(requiredManifestContentTrimmed, {
|
|
explicitChildren: true,
|
|
trim: true,
|
|
}), (0, xml_1.parseXML)(manifestContentTrimmed, {
|
|
explicitChildren: true,
|
|
trim: true,
|
|
}), pathPartList)) {
|
|
log_1.logger.warn(`Android Configuration required for ${colors_1.default.strong(p.id)}.\n` +
|
|
`Add the following to AndroidManifest.xml:\n` +
|
|
xmlElement);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (!rootXMLEntries.includes(xmlElement) && !contains(rootXMLEntries, xmlElement, k)) {
|
|
rootXMLEntries.push(xmlElement);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
const cleartextString = 'android:usesCleartextTraffic="true"';
|
|
const cleartextValue = (cleartext || ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext)) && !applicationXMLAttributes.includes(cleartextString)
|
|
? cleartextString
|
|
: '';
|
|
let content = `<?xml version='1.0' encoding='utf-8'?>
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:amazon="http://schemas.amazon.com/apk/res/android">
|
|
<application ${applicationXMLAttributes.join('\n')} ${cleartextValue}>
|
|
${applicationXMLEntries.join('\n')}
|
|
</application>
|
|
${rootXMLEntries.join('\n')}
|
|
</manifest>`;
|
|
content = content.replace(new RegExp('$PACKAGE_NAME'.replace('$', '\\$&'), 'g'), '${applicationId}');
|
|
for (const preference of prefsArray) {
|
|
content = content.replace(new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'), preference.$.default);
|
|
}
|
|
if (await (0, fs_extra_1.pathExists)(manifestPath)) {
|
|
await (0, fs_extra_1.writeFile)(manifestPath, content);
|
|
}
|
|
}
|
|
exports.writeCordovaAndroidManifest = writeCordovaAndroidManifest;
|
|
function getPathParts(path) {
|
|
const rootPath = 'manifest';
|
|
path = path.replace('/*', rootPath);
|
|
const parts = path.split('/').filter((part) => part !== '');
|
|
if (parts.length > 1 || parts.includes(rootPath)) {
|
|
return parts;
|
|
}
|
|
return [rootPath, path];
|
|
}
|
|
function contains(entries, obj, k) {
|
|
const element = (0, xml_1.parseXML)(obj);
|
|
for (const entry of entries) {
|
|
const current = (0, xml_1.parseXML)(entry);
|
|
if (element &&
|
|
current &&
|
|
current[k] &&
|
|
element[k] &&
|
|
current[k].$ &&
|
|
element[k].$ &&
|
|
element[k].$['android:name'] === current[k].$['android:name']) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|