UNPKG

21 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 const assets = plugin_1.getAssets(p, platform);
116 assets.map((asset) => {
117 const filePath = path_1.join(webDir, asset.$.target);
118 fs_1.copySync(path_1.join(p.rootPath, asset.$.src), filePath);
119 });
120 }));
121 fs_1.writeFileAsync(cordovaPluginsJSFile, generateCordovaPluginsJSFile(config, cordovaPlugins, platform));
122}
123exports.copyPluginsJS = copyPluginsJS;
124async function copyCordovaJS(config, platform) {
125 const cordovaPath = common_1.resolveNode(config, '@capacitor/core', 'cordova.js');
126 if (!cordovaPath) {
127 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.');
128 return;
129 }
130 return fs_extra_1.copy(cordovaPath, path_1.join(getWebDir(config, platform), 'cordova.js'));
131}
132exports.copyCordovaJS = copyCordovaJS;
133async function createEmptyCordovaJS(config, platform) {
134 await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova.js'), '');
135 await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova_plugins.js'), '');
136}
137exports.createEmptyCordovaJS = createEmptyCordovaJS;
138function removePluginFiles(config, platform) {
139 const webDir = getWebDir(config, platform);
140 const pluginsDir = path_1.join(webDir, 'plugins');
141 const cordovaPluginsJSFile = path_1.join(webDir, 'cordova_plugins.js');
142 fs_1.removeSync(pluginsDir);
143 fs_1.removeSync(cordovaPluginsJSFile);
144}
145exports.removePluginFiles = removePluginFiles;
146async function autoGenerateConfig(config, cordovaPlugins, platform) {
147 let xmlDir = path_1.join(config.android.resDirAbs, 'xml');
148 const fileName = 'config.xml';
149 if (platform === 'ios') {
150 xmlDir = path_1.join(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName);
151 }
152 fs_1.ensureDirSync(xmlDir);
153 const cordovaConfigXMLFile = path_1.join(xmlDir, fileName);
154 fs_1.removeSync(cordovaConfigXMLFile);
155 let pluginEntries = [];
156 cordovaPlugins.map(p => {
157 const currentPlatform = plugin_1.getPluginPlatform(p, platform);
158 if (currentPlatform) {
159 const configFiles = currentPlatform['config-file'];
160 if (configFiles) {
161 const configXMLEntries = configFiles.filter(function (item) { return item.$ && item.$.target.includes(fileName); });
162 configXMLEntries.map((entry) => {
163 if (entry.feature) {
164 const feature = { feature: entry.feature };
165 pluginEntries.push(feature);
166 }
167 });
168 }
169 }
170 });
171 const pluginEntriesString = await Promise.all(pluginEntries.map(async (item) => {
172 const xmlString = await common_1.writeXML(item);
173 return xmlString;
174 }));
175 let pluginPreferencesString = [];
176 if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences) {
177 pluginPreferencesString = await Promise.all(Object.keys(config.app.extConfig.cordova.preferences).map(async (key) => {
178 return `
179 <preference name="${key}" value="${config.app.extConfig.cordova.preferences[key]}" />`;
180 }));
181 }
182 const content = `<?xml version='1.0' encoding='utf-8'?>
183<widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
184 <access origin="*" />
185 ${pluginEntriesString.join('')}
186 ${pluginPreferencesString.join('')}
187</widget>`;
188 await fs_1.writeFileAsync(cordovaConfigXMLFile, content);
189}
190exports.autoGenerateConfig = autoGenerateConfig;
191function getWebDir(config, platform) {
192 if (platform === 'ios') {
193 return config.ios.webDirAbs;
194 }
195 if (platform === 'android') {
196 return config.android.webDirAbs;
197 }
198 return '';
199}
200async function handleCordovaPluginsJS(cordovaPlugins, config, platform) {
201 if (!fs_extra_1.existsSync(getWebDir(config, platform))) {
202 await copy_1.copy(config, platform);
203 }
204 if (cordovaPlugins.length > 0) {
205 plugin_1.printPlugins(cordovaPlugins, platform, 'cordova');
206 await copyCordovaJS(config, platform);
207 await copyPluginsJS(config, cordovaPlugins, platform);
208 }
209 else {
210 removePluginFiles(config, platform);
211 await createEmptyCordovaJS(config, platform);
212 }
213 await autoGenerateConfig(config, cordovaPlugins, platform);
214}
215exports.handleCordovaPluginsJS = handleCordovaPluginsJS;
216async function getCordovaPlugins(config, platform) {
217 const allPlugins = await plugin_1.getPlugins(config);
218 let plugins = [];
219 if (platform === config.ios.name) {
220 plugins = common_3.getIOSPlugins(allPlugins);
221 }
222 else if (platform === config.android.name) {
223 plugins = common_2.getAndroidPlugins(allPlugins);
224 }
225 return plugins
226 .filter(p => plugin_1.getPluginType(p, platform) === 1 /* Cordova */);
227}
228exports.getCordovaPlugins = getCordovaPlugins;
229async function logCordovaManualSteps(cordovaPlugins, config, platform) {
230 cordovaPlugins.map(p => {
231 const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
232 const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
233 editConfig.concat(configFile).map(async (configElement) => {
234 if (configElement.$ && !configElement.$.target.includes('config.xml')) {
235 if (platform === config.ios.name) {
236 if (configElement.$.target.includes('Info.plist')) {
237 logiOSPlist(configElement, config, p);
238 }
239 }
240 }
241 });
242 });
243}
244exports.logCordovaManualSteps = logCordovaManualSteps;
245async function logiOSPlist(configElement, config, plugin) {
246 const plistPath = path_1.resolve(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName, 'Info.plist');
247 const xmlMeta = await common_1.readXML(plistPath);
248 let data = await fs_1.readFileAsync(plistPath, 'utf8');
249 var plistData = plist.parse(data);
250 const dict = xmlMeta.plist.dict.pop();
251 if (!dict.key.includes(configElement.$.parent)) {
252 let xml = buildConfigFileXml(configElement);
253 xml = `<key>${configElement.$.parent}</key>${getConfigFileTagContent(xml)}`;
254 common_1.logWarn(`Plugin ${plugin.id} requires you to add \n ${xml} to your Info.plist to work`);
255 }
256 else if (configElement.array || configElement.dict) {
257 if (configElement.array && configElement.array[0] && configElement.array[0].string) {
258 var xml = '';
259 configElement.array[0].string.map((element) => {
260 if (!plistData[configElement.$.parent].includes(element)) {
261 xml = xml.concat(`<string>${element}</string>\n`);
262 }
263 });
264 if (xml.length > 0) {
265 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`);
266 }
267 }
268 else {
269 logPossibleMissingItem(configElement, plugin);
270 }
271 }
272}
273function logPossibleMissingItem(configElement, plugin) {
274 let xml = buildConfigFileXml(configElement);
275 xml = getConfigFileTagContent(xml);
276 xml = removeOuterTags(xml);
277 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`);
278}
279function buildConfigFileXml(configElement) {
280 return common_1.buildXmlElement(configElement, 'config-file');
281}
282function getConfigFileTagContent(str) {
283 return str.replace(/\<config-file.+\"\>|\<\/config-file>/g, '');
284}
285function removeOuterTags(str) {
286 var start = str.indexOf('>') + 1;
287 var end = str.lastIndexOf('<');
288 return str.substring(start, end);
289}
290async function checkAndInstallDependencies(config, plugins, platform) {
291 let needsUpdate = false;
292 const cordovaPlugins = plugins
293 .filter(p => plugin_1.getPluginType(p, platform) === 1 /* Cordova */);
294 const incompatible = plugins.filter(p => plugin_1.getPluginType(p, platform) === 2 /* Incompatible */);
295 await Promise.all(cordovaPlugins.map(async (p) => {
296 let allDependencies = [];
297 allDependencies = allDependencies.concat(plugin_1.getPlatformElement(p, platform, 'dependency'));
298 if (p.xml['dependency']) {
299 allDependencies = allDependencies.concat(p.xml['dependency']);
300 }
301 allDependencies = allDependencies.filter((dep) => !getIncompatibleCordovaPlugins(platform).includes(dep.$.id) && incompatible.filter(p => p.id === dep.$.id || p.xml.$.id === dep.$.id).length === 0);
302 if (allDependencies) {
303 await Promise.all(allDependencies.map(async (dep) => {
304 let plugin = dep.$.id;
305 if (plugin.includes('@') && plugin.indexOf('@') !== 0) {
306 plugin = plugin.split('@')[0];
307 }
308 if (cordovaPlugins.filter(p => p.id === plugin || p.xml.$.id === plugin).length === 0) {
309 if (dep.$.url && dep.$.url.startsWith('http')) {
310 plugin = dep.$.url;
311 }
312 common_1.logInfo(`installing missing dependency plugin ${plugin}`);
313 try {
314 await common_1.installDeps(config.app.rootDir, [plugin], config);
315 await config.updateAppPackage();
316 needsUpdate = true;
317 }
318 catch (e) {
319 common_1.log('\n');
320 common_1.logError(`couldn't install dependency plugin ${plugin}`);
321 }
322 }
323 }));
324 }
325 }));
326 return needsUpdate;
327}
328exports.checkAndInstallDependencies = checkAndInstallDependencies;
329function getIncompatibleCordovaPlugins(platform) {
330 let pluginList = ['cordova-plugin-splashscreen', 'cordova-plugin-ionic-webview', 'cordova-plugin-crosswalk-webview',
331 'cordova-plugin-wkwebview-engine', 'cordova-plugin-console', 'cordova-plugin-music-controls',
332 'cordova-plugin-add-swift-support', 'cordova-plugin-ionic-keyboard', 'cordova-plugin-braintree',
333 '@ionic-enterprise/filesystem', '@ionic-enterprise/keyboard', '@ionic-enterprise/splashscreen', 'cordova-support-google-services'];
334 if (platform === 'ios') {
335 pluginList.push('cordova-plugin-statusbar', '@ionic-enterprise/statusbar');
336 }
337 if (platform === 'android') {
338 pluginList.push('cordova-plugin-compat');
339 }
340 return pluginList;
341}
342exports.getIncompatibleCordovaPlugins = getIncompatibleCordovaPlugins;
343async function getCordovaPreferences(config) {
344 const configXml = path_1.join(config.app.rootDir, 'config.xml');
345 let cordova = {};
346 if (fs_extra_1.existsSync(configXml)) {
347 cordova.preferences = {};
348 const xmlMeta = await common_1.readXML(configXml);
349 if (xmlMeta.widget.preference) {
350 xmlMeta.widget.preference.map((pref) => {
351 cordova.preferences[pref.$.name] = pref.$.value;
352 });
353 }
354 }
355 if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences && cordova.preferences) {
356 const answer = await inquirer.prompt({
357 type: 'confirm',
358 name: 'confirm',
359 message: 'capacitor.config.json already contains cordova preferences. Overwrite with values from config.xml?'
360 });
361 if (!answer.confirm) {
362 cordova = config.app.extConfig.cordova;
363 }
364 }
365 if (config.app.extConfig && !cordova.preferences) {
366 cordova = config.app.extConfig.cordova;
367 }
368 return cordova;
369}
370exports.getCordovaPreferences = getCordovaPreferences;
371async function writeCordovaAndroidManifest(cordovaPlugins, config, platform) {
372 var _a;
373 const pluginsFolder = path_1.resolve(config.app.rootDir, 'android', config.android.assets.pluginsFolderName);
374 const manifestPath = path_1.join(pluginsFolder, 'src', 'main', 'AndroidManifest.xml');
375 let rootXMLEntries = [];
376 let applicationXMLEntries = [];
377 let applicationXMLAttributes = [];
378 let prefsArray = [];
379 cordovaPlugins.map(async (p) => {
380 const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
381 const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
382 prefsArray = prefsArray.concat(plugin_1.getAllElements(p, platform, 'preference'));
383 editConfig.concat(configFile).map(async (configElement) => {
384 if (configElement.$ && (configElement.$.target && configElement.$.target.includes('AndroidManifest.xml') || configElement.$.file && configElement.$.file.includes('AndroidManifest.xml'))) {
385 const keys = Object.keys(configElement).filter(k => k !== '$');
386 keys.map(k => {
387 configElement[k].map((e) => {
388 const xmlElement = common_1.buildXmlElement(e, k);
389 const pathParts = getPathParts(configElement.$.parent || configElement.$.target);
390 if (pathParts.length > 1) {
391 if (pathParts.pop() === 'application') {
392 if (configElement.$.mode && configElement.$.mode === 'merge' && xmlElement.startsWith('<application')) {
393 Object.keys(e.$).map((ek) => {
394 applicationXMLAttributes.push(`${ek}="${e.$[ek]}"`);
395 });
396 }
397 else if (!applicationXMLEntries.includes(xmlElement) && !contains(applicationXMLEntries, xmlElement, k)) {
398 applicationXMLEntries.push(xmlElement);
399 }
400 }
401 else {
402 common_1.logInfo(`plugin ${p.id} requires to add \n ${xmlElement} to your AndroidManifest.xml to work`);
403 }
404 }
405 else {
406 if (!rootXMLEntries.includes(xmlElement) && !contains(rootXMLEntries, xmlElement, k)) {
407 rootXMLEntries.push(xmlElement);
408 }
409 }
410 });
411 });
412 }
413 });
414 });
415 let cleartextString = 'android:usesCleartextTraffic="true"';
416 let cleartext = ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext) && !applicationXMLAttributes.includes(cleartextString) ? cleartextString : '';
417 let content = `<?xml version='1.0' encoding='utf-8'?>
418<manifest package="capacitor.android.plugins"
419xmlns:android="http://schemas.android.com/apk/res/android"
420xmlns:amazon="http://schemas.amazon.com/apk/res/android">
421<application ${applicationXMLAttributes.join('\n')} ${cleartext}>
422${applicationXMLEntries.join('\n')}
423</application>
424${rootXMLEntries.join('\n')}
425</manifest>`;
426 content = content.replace(new RegExp(('$PACKAGE_NAME').replace('$', '\\$&'), 'g'), '${applicationId}');
427 prefsArray.map((preference) => {
428 content = content.replace(new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'), preference.$.default);
429 });
430 if (fs_extra_1.existsSync(manifestPath)) {
431 await fs_1.writeFileAsync(manifestPath, content);
432 }
433}
434exports.writeCordovaAndroidManifest = writeCordovaAndroidManifest;
435function getPathParts(path) {
436 const rootPath = 'manifest';
437 path = path.replace('/*', rootPath);
438 let parts = path.split('/').filter(part => part !== '');
439 if (parts.length > 1 || parts.includes(rootPath)) {
440 return parts;
441 }
442 return [rootPath, path];
443}
444function contains(a, obj, k) {
445 const element = common_1.parseXML(obj);
446 for (var i = 0; i < a.length; i++) {
447 const current = common_1.parseXML(a[i]);
448 if (element && current && current[k] && element[k] && current[k].$ && element[k].$ && element[k].$['android:name'] === current[k].$['android:name']) {
449 return true;
450 }
451 }
452 return false;
453}