UNPKG

20.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const plugin_1 = require("./plugin");
4const fs_1 = require("./util/fs");
5const path_1 = require("path");
6const common_1 = require("./common");
7const fs_extra_1 = require("fs-extra");
8const common_2 = require("./android/common");
9const common_3 = require("./ios/common");
10const copy_1 = require("./tasks/copy");
11const inquirer = require("inquirer");
12const plist = require('plist');
13const chalk = require('chalk');
14/**
15 * Build the root cordova_plugins.js file referencing each Plugin JS file.
16 */
17function generateCordovaPluginsJSFile(config, plugins, platform) {
18 let pluginModules = [];
19 let pluginExports = [];
20 plugins.map((p) => {
21 const pluginId = p.xml.$.id;
22 const jsModules = plugin_1.getJSModules(p, platform);
23 jsModules.map((jsModule) => {
24 let clobbers = [];
25 let merges = [];
26 let clobbersModule = '';
27 let mergesModule = '';
28 let runsModule = '';
29 let clobberKey = '';
30 let mergeKey = '';
31 if (jsModule.clobbers) {
32 jsModule.clobbers.map((clobber) => {
33 clobbers.push(clobber.$.target);
34 clobberKey = clobber.$.target;
35 });
36 clobbersModule = `,
37 "clobbers": [
38 "${clobbers.join('",\n "')}"
39 ]`;
40 }
41 if (jsModule.merges) {
42 jsModule.merges.map((merge) => {
43 merges.push(merge.$.target);
44 mergeKey = merge.$.target;
45 });
46 mergesModule = `,
47 "merges": [
48 "${merges.join('",\n "')}"
49 ]`;
50 }
51 if (jsModule.runs) {
52 runsModule = ',\n "runs": true';
53 }
54 const pluginModule = {
55 clobber: clobberKey,
56 merge: mergeKey,
57 // mimics Cordova's module name logic if the name attr is missing
58 pluginContent: `{
59 "id": "${pluginId + '.' + (jsModule.$.name || jsModule.$.src.match(/([^\/]+)\.js/)[1])}",
60 "file": "plugins/${pluginId}/${jsModule.$.src}",
61 "pluginId": "${pluginId}"${clobbersModule}${mergesModule}${runsModule}
62 }`
63 };
64 pluginModules.push(pluginModule);
65 });
66 pluginExports.push(`"${pluginId}": "${p.xml.$.version}"`);
67 });
68 return `
69 cordova.define('cordova/plugin_list', function(require, exports, module) {
70 module.exports = [
71 ${pluginModules
72 .sort((a, b) => (a.clobber && b.clobber) // Clobbers in alpha order
73 ? a.clobber.localeCompare(b.clobber)
74 : ((a.clobber || b.clobber) // Clobbers before anything else
75 ? b.clobber.localeCompare(a.clobber)
76 : a.merge.localeCompare(b.merge) // Merges in alpha order
77 ))
78 .map(e => e.pluginContent)
79 .join(',\n ')}
80 ];
81 module.exports.metadata =
82 // TOP OF METADATA
83 {
84 ${pluginExports.join(',\n ')}
85 };
86 // BOTTOM OF METADATA
87 });
88 `;
89}
90exports.generateCordovaPluginsJSFile = generateCordovaPluginsJSFile;
91/**
92 * Build the plugins/* files for each Cordova plugin installed.
93 */
94async function copyPluginsJS(config, cordovaPlugins, platform) {
95 const webDir = getWebDir(config, platform);
96 const pluginsDir = path_1.join(webDir, 'plugins');
97 const cordovaPluginsJSFile = path_1.join(webDir, 'cordova_plugins.js');
98 removePluginFiles(config, platform);
99 await Promise.all(cordovaPlugins.map(async (p) => {
100 const pluginId = p.xml.$.id;
101 const pluginDir = path_1.join(pluginsDir, pluginId, 'www');
102 fs_1.ensureDirSync(pluginDir);
103 const jsModules = plugin_1.getJSModules(p, platform);
104 await Promise.all(jsModules.map(async (jsModule) => {
105 const filePath = path_1.join(webDir, 'plugins', pluginId, jsModule.$.src);
106 fs_1.copySync(path_1.join(p.rootPath, jsModule.$.src), filePath);
107 let data = await fs_1.readFileAsync(filePath, 'utf8');
108 data = data.trim();
109 // mimics Cordova's module name logic if the name attr is missing
110 const name = pluginId + '.' + (jsModule.$.name || path_1.basename(jsModule.$.src, path_1.extname(jsModule.$.src)));
111 data = `cordova.define("${name}", function(require, exports, module) { \n${data}\n});`;
112 data = data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi, '');
113 await fs_1.writeFileAsync(filePath, data, 'utf8');
114 }));
115 }));
116 fs_1.writeFileAsync(cordovaPluginsJSFile, generateCordovaPluginsJSFile(config, cordovaPlugins, platform));
117}
118exports.copyPluginsJS = copyPluginsJS;
119async function copyCordovaJS(config, platform) {
120 const cordovaPath = common_1.resolveNode(config, '@capacitor/core', 'cordova.js');
121 if (!cordovaPath) {
122 common_1.logFatal(`Unable to find node_modules/@capacitor/core/cordova.js. Are you sure`, '@capacitor/core is installed? This file is currently required for Capacitor to function.');
123 return;
124 }
125 return fs_extra_1.copy(cordovaPath, path_1.join(getWebDir(config, platform), 'cordova.js'));
126}
127exports.copyCordovaJS = copyCordovaJS;
128async function createEmptyCordovaJS(config, platform) {
129 await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova.js'), '');
130 await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova_plugins.js'), '');
131}
132exports.createEmptyCordovaJS = createEmptyCordovaJS;
133function removePluginFiles(config, platform) {
134 const webDir = getWebDir(config, platform);
135 const pluginsDir = path_1.join(webDir, 'plugins');
136 const cordovaPluginsJSFile = path_1.join(webDir, 'cordova_plugins.js');
137 fs_1.removeSync(pluginsDir);
138 fs_1.removeSync(cordovaPluginsJSFile);
139}
140exports.removePluginFiles = removePluginFiles;
141async function autoGenerateConfig(config, cordovaPlugins, platform) {
142 let xmlDir = path_1.join(config.android.resDirAbs, 'xml');
143 const fileName = 'config.xml';
144 if (platform === 'ios') {
145 xmlDir = path_1.join(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName);
146 }
147 fs_1.ensureDirSync(xmlDir);
148 const cordovaConfigXMLFile = path_1.join(xmlDir, fileName);
149 fs_1.removeSync(cordovaConfigXMLFile);
150 let pluginEntries = [];
151 cordovaPlugins.map(p => {
152 const currentPlatform = plugin_1.getPluginPlatform(p, platform);
153 if (currentPlatform) {
154 const configFiles = currentPlatform['config-file'];
155 if (configFiles) {
156 const configXMLEntries = configFiles.filter(function (item) { return item.$ && item.$.target.includes(fileName); });
157 configXMLEntries.map((entry) => {
158 if (entry.feature) {
159 const feature = { feature: entry.feature };
160 pluginEntries.push(feature);
161 }
162 });
163 }
164 }
165 });
166 const pluginEntriesString = await Promise.all(pluginEntries.map(async (item) => {
167 const xmlString = await common_1.writeXML(item);
168 return xmlString;
169 }));
170 let pluginPreferencesString = [];
171 if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences) {
172 pluginPreferencesString = await Promise.all(Object.keys(config.app.extConfig.cordova.preferences).map(async (key) => {
173 return `
174 <preference name="${key}" value="${config.app.extConfig.cordova.preferences[key]}" />`;
175 }));
176 }
177 const content = `<?xml version='1.0' encoding='utf-8'?>
178<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
179 <access origin="*" />
180 ${pluginEntriesString.join('')}
181 ${pluginPreferencesString.join('')}
182</widget>`;
183 await fs_1.writeFileAsync(cordovaConfigXMLFile, content);
184}
185exports.autoGenerateConfig = autoGenerateConfig;
186function getWebDir(config, platform) {
187 if (platform === 'ios') {
188 return config.ios.webDirAbs;
189 }
190 if (platform === 'android') {
191 return config.android.webDirAbs;
192 }
193 return '';
194}
195async function handleCordovaPluginsJS(cordovaPlugins, config, platform) {
196 if (!fs_extra_1.existsSync(getWebDir(config, platform))) {
197 await copy_1.copy(config, platform);
198 }
199 if (cordovaPlugins.length > 0) {
200 plugin_1.printPlugins(cordovaPlugins, platform, 'cordova');
201 await copyCordovaJS(config, platform);
202 await copyPluginsJS(config, cordovaPlugins, platform);
203 }
204 else {
205 removePluginFiles(config, platform);
206 await createEmptyCordovaJS(config, platform);
207 }
208 await autoGenerateConfig(config, cordovaPlugins, platform);
209}
210exports.handleCordovaPluginsJS = handleCordovaPluginsJS;
211async function getCordovaPlugins(config, platform) {
212 const allPlugins = await plugin_1.getPlugins(config);
213 let plugins = [];
214 if (platform === config.ios.name) {
215 plugins = common_3.getIOSPlugins(allPlugins);
216 }
217 else if (platform === config.android.name) {
218 plugins = common_2.getAndroidPlugins(allPlugins);
219 }
220 return plugins
221 .filter(p => plugin_1.getPluginType(p, platform) === 1 /* Cordova */);
222}
223exports.getCordovaPlugins = getCordovaPlugins;
224async function logCordovaManualSteps(cordovaPlugins, config, platform) {
225 cordovaPlugins.map(p => {
226 const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
227 const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
228 editConfig.concat(configFile).map(async (configElement) => {
229 if (configElement.$ && !configElement.$.target.includes('config.xml')) {
230 if (platform === config.ios.name) {
231 if (configElement.$.target.includes('Info.plist')) {
232 logiOSPlist(configElement, config, p);
233 }
234 }
235 }
236 });
237 });
238}
239exports.logCordovaManualSteps = logCordovaManualSteps;
240async function logiOSPlist(configElement, config, plugin) {
241 const plistPath = path_1.resolve(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName, 'Info.plist');
242 const xmlMeta = await common_1.readXML(plistPath);
243 let data = await fs_1.readFileAsync(plistPath, 'utf8');
244 var plistData = plist.parse(data);
245 const dict = xmlMeta.plist.dict.pop();
246 if (!dict.key.includes(configElement.$.parent)) {
247 let xml = buildConfigFileXml(configElement);
248 xml = `<key>${configElement.$.parent}</key>${getConfigFileTagContent(xml)}`;
249 common_1.logWarn(`Plugin ${plugin.id} requires you to add \n ${xml} to your Info.plist to work`);
250 }
251 else if (configElement.array || configElement.dict) {
252 if (configElement.array && configElement.array[0] && configElement.array[0].string) {
253 var xml = '';
254 configElement.array[0].string.map((element) => {
255 if (!plistData[configElement.$.parent].includes(element)) {
256 xml = xml.concat(`<string>${element}</string>\n`);
257 }
258 });
259 if (xml.length > 0) {
260 common_1.logWarn(`Plugin ${plugin.id} requires you to add \n${xml} in the existing ${chalk.bold(configElement.$.parent)} array of your Info.plist to work`);
261 }
262 }
263 else {
264 logPossibleMissingItem(configElement, plugin);
265 }
266 }
267}
268function logPossibleMissingItem(configElement, plugin) {
269 let xml = buildConfigFileXml(configElement);
270 xml = getConfigFileTagContent(xml);
271 xml = removeOuterTags(xml);
272 common_1.logWarn(`Plugin ${plugin.id} might require you to add ${xml} in the existing ${chalk.bold(configElement.$.parent)} entry of your Info.plist to work`);
273}
274function buildConfigFileXml(configElement) {
275 return common_1.buildXmlElement(configElement, 'config-file');
276}
277function getConfigFileTagContent(str) {
278 return str.replace(/\<config-file.+\"\>|\<\/config-file>/g, '');
279}
280function removeOuterTags(str) {
281 var start = str.indexOf('>') + 1;
282 var end = str.lastIndexOf('<');
283 return str.substring(start, end);
284}
285async function checkAndInstallDependencies(config, plugins, platform) {
286 let needsUpdate = false;
287 const cordovaPlugins = plugins
288 .filter(p => plugin_1.getPluginType(p, platform) === 1 /* Cordova */);
289 const incompatible = plugins.filter(p => plugin_1.getPluginType(p, platform) === 2 /* Incompatible */);
290 await Promise.all(cordovaPlugins.map(async (p) => {
291 let allDependencies = [];
292 allDependencies = allDependencies.concat(plugin_1.getPlatformElement(p, platform, 'dependency'));
293 if (p.xml['dependency']) {
294 allDependencies = allDependencies.concat(p.xml['dependency']);
295 }
296 allDependencies = allDependencies.filter((dep) => !getIncompatibleCordovaPlugins(platform).includes(dep.$.id) && incompatible.filter(p => p.id === dep.$.id || p.xml.$.id === dep.$.id).length === 0);
297 if (allDependencies) {
298 await Promise.all(allDependencies.map(async (dep) => {
299 let plugin = dep.$.id;
300 if (plugin.includes('@')) {
301 plugin = plugin.split('@')[0];
302 }
303 if (cordovaPlugins.filter(p => p.id === plugin || p.xml.$.id === plugin).length === 0) {
304 if (dep.$.url && dep.$.url.startsWith('http')) {
305 plugin = dep.$.url;
306 }
307 common_1.logInfo(`installing missing dependency plugin ${plugin}`);
308 try {
309 await common_1.installDeps(config.app.rootDir, [plugin], config);
310 await config.updateAppPackage();
311 needsUpdate = true;
312 }
313 catch (e) {
314 common_1.log('\n');
315 common_1.logError(`couldn't install dependency plugin ${plugin}`);
316 }
317 }
318 }));
319 }
320 }));
321 return needsUpdate;
322}
323exports.checkAndInstallDependencies = checkAndInstallDependencies;
324function getIncompatibleCordovaPlugins(platform) {
325 let pluginList = ['cordova-plugin-splashscreen', 'cordova-plugin-ionic-webview', 'cordova-plugin-crosswalk-webview',
326 'cordova-plugin-wkwebview-engine', 'cordova-plugin-console', 'cordova-plugin-music-controls',
327 'cordova-plugin-add-swift-support', 'cordova-plugin-ionic-keyboard', 'cordova-plugin-braintree',
328 '@ionic-enterprise/filesystem', '@ionic-enterprise/keyboard', '@ionic-enterprise/splashscreen'];
329 if (platform === 'ios') {
330 pluginList.push('cordova-plugin-googlemaps', 'cordova-plugin-statusbar', '@ionic-enterprise/statusbar');
331 }
332 if (platform === 'android') {
333 pluginList.push('cordova-plugin-compat');
334 }
335 return pluginList;
336}
337exports.getIncompatibleCordovaPlugins = getIncompatibleCordovaPlugins;
338async function getCordovaPreferences(config) {
339 const configXml = path_1.join(config.app.rootDir, 'config.xml');
340 let cordova = {};
341 if (fs_extra_1.existsSync(configXml)) {
342 cordova.preferences = {};
343 const xmlMeta = await common_1.readXML(configXml);
344 if (xmlMeta.widget.preference) {
345 xmlMeta.widget.preference.map((pref) => {
346 cordova.preferences[pref.$.name] = pref.$.value;
347 });
348 }
349 }
350 if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences && cordova.preferences) {
351 const answer = await inquirer.prompt({
352 type: 'confirm',
353 name: 'confirm',
354 message: 'capacitor.config.json already contains cordova preferences. Overwrite with values from config.xml?'
355 });
356 if (!answer.confirm) {
357 cordova = config.app.extConfig.cordova;
358 }
359 }
360 if (config.app.extConfig && !cordova.preferences) {
361 cordova = config.app.extConfig.cordova;
362 }
363 return cordova;
364}
365exports.getCordovaPreferences = getCordovaPreferences;
366async function writeCordovaAndroidManifest(cordovaPlugins, config, platform) {
367 var _a;
368 const pluginsFolder = path_1.resolve(config.app.rootDir, 'android', config.android.assets.pluginsFolderName);
369 const manifestPath = path_1.join(pluginsFolder, 'src', 'main', 'AndroidManifest.xml');
370 let rootXMLEntries = [];
371 let applicationXMLEntries = [];
372 let applicationXMLAttributes = [];
373 cordovaPlugins.map(async (p) => {
374 const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
375 const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
376 editConfig.concat(configFile).map(async (configElement) => {
377 if (configElement.$ && (configElement.$.target && configElement.$.target.includes('AndroidManifest.xml') || configElement.$.file && configElement.$.file.includes('AndroidManifest.xml'))) {
378 const keys = Object.keys(configElement).filter(k => k !== '$');
379 keys.map(k => {
380 configElement[k].map((e) => {
381 const xmlElement = common_1.buildXmlElement(e, k);
382 const pathParts = getPathParts(configElement.$.parent || configElement.$.target);
383 if (pathParts.length > 1) {
384 if (pathParts.pop() === 'application') {
385 if (configElement.$.mode && configElement.$.mode === 'merge' && xmlElement.startsWith('<application')) {
386 Object.keys(e.$).map((ek) => {
387 applicationXMLAttributes.push(`${ek}="${e.$[ek]}"`);
388 });
389 }
390 else if (!applicationXMLEntries.includes(xmlElement) && !contains(applicationXMLEntries, xmlElement, k)) {
391 applicationXMLEntries.push(xmlElement);
392 }
393 }
394 else {
395 common_1.logInfo(`plugin ${p.id} requires to add \n ${xmlElement} to your AndroidManifest.xml to work`);
396 }
397 }
398 else {
399 if (!rootXMLEntries.includes(xmlElement) && !contains(rootXMLEntries, xmlElement, k)) {
400 rootXMLEntries.push(xmlElement);
401 }
402 }
403 });
404 });
405 }
406 });
407 });
408 let cleartext = ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext) ? 'android:usesCleartextTraffic="true"' : '';
409 let content = `<?xml version='1.0' encoding='utf-8'?>
410<manifest package="capacitor.android.plugins"
411xmlns:android="http://schemas.android.com/apk/res/android"
412xmlns:amazon="http://schemas.amazon.com/apk/res/android">
413<application ${applicationXMLAttributes.join('\n')}${cleartext}>
414${applicationXMLEntries.join('\n')}
415</application>
416${rootXMLEntries.join('\n')}
417</manifest>`;
418 content = content.replace(new RegExp(('$PACKAGE_NAME').replace('$', '\\$&'), 'g'), config.app.appId);
419 if (fs_extra_1.existsSync(manifestPath)) {
420 await fs_1.writeFileAsync(manifestPath, content);
421 }
422}
423exports.writeCordovaAndroidManifest = writeCordovaAndroidManifest;
424function getPathParts(path) {
425 const rootPath = 'manifest';
426 path = path.replace('/*', rootPath);
427 let parts = path.split('/').filter(part => part !== '');
428 if (parts.length > 1 || parts.includes(rootPath)) {
429 return parts;
430 }
431 return [rootPath, path];
432}
433function contains(a, obj, k) {
434 const element = common_1.parseXML(obj);
435 for (var i = 0; i < a.length; i++) {
436 const current = common_1.parseXML(a[i]);
437 if (element && current && current[k] && element[k] && current[k].$ && element[k].$ && element[k].$['android:name'] === current[k].$['android:name']) {
438 return true;
439 }
440 }
441 return false;
442}