@capacitor/cli 9.0.0-alpha.8 → 9.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
Binary file
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__testables = exports.removeLegacyCordovaPluginsConfig = exports.setAndroidCleartext = void 0;
4
+ const fs_extra_1 = require("fs-extra");
5
+ const path_1 = require("path");
6
+ const log_1 = require("../log");
7
+ /**
8
+ * Handles opting in to cleartext (HTTP) traffic through the app module's `network-security-config.xml`.
9
+ *
10
+ * Whatever the CLI changes is tracked by a marker comment inside the config file itself, so it can be
11
+ * reverted later without touching anything the app owned before:
12
+ *
13
+ * <!-- capacitor:cleartext created ref orig=false -->
14
+ *
15
+ * created the CLI created the file (the revert deletes it)
16
+ * ref the CLI added android:networkSecurityConfig to the app manifest (the revert removes it)
17
+ * orig=... state of <base-config cleartextTrafficPermitted> before the CLI changed it:
18
+ * true | false | absent (attribute not set) | added (the CLI added the whole <base-config>)
19
+ */
20
+ const DEFAULT_CONFIG_NAME = 'network_security_config';
21
+ const MARKER_NAME = 'capacitor:cleartext';
22
+ const MARKER_REGEX = new RegExp(`[ \\t]*<!--\\s*${MARKER_NAME}([^>]*?)-->[ \\t]*\\r?\\n?`);
23
+ const BASE_CONFIG_REGEX = /<base-config\b[^>]*>/;
24
+ const CLEARTEXT_ATTR_REGEX = /(\bcleartextTrafficPermitted\s*=\s*)(["'])(.*?)\2/;
25
+ const ROOT_REGEX = /<network-security-config\b[^>]*?(\/?)>/;
26
+ const APPLICATION_REGEX = /<application\b/;
27
+ const CONFIG_REF_REGEX = /android:networkSecurityConfig\s*=\s*"@xml\/([^"]+)"/;
28
+ const LEGACY_CORDOVA_PLUGINS_CONFIG = `<?xml version="1.0" encoding="utf-8"?>
29
+ <network-security-config>
30
+ <base-config cleartextTrafficPermitted="true" />
31
+ </network-security-config>
32
+ `;
33
+ function parseMarker(xml) {
34
+ var _a;
35
+ const match = MARKER_REGEX.exec(xml);
36
+ if (!match) {
37
+ return undefined;
38
+ }
39
+ const tokens = match[1].trim().split(/\s+/);
40
+ const orig = (_a = tokens.find((t) => t.startsWith('orig='))) === null || _a === void 0 ? void 0 : _a.slice('orig='.length);
41
+ return { created: tokens.includes('created'), ref: tokens.includes('ref'), orig };
42
+ }
43
+ function buildMarker(marker) {
44
+ const parts = [MARKER_NAME];
45
+ if (marker.created) {
46
+ parts.push('created');
47
+ }
48
+ if (marker.ref) {
49
+ parts.push('ref');
50
+ }
51
+ if (marker.orig) {
52
+ parts.push(`orig=${marker.orig}`);
53
+ }
54
+ return `<!-- ${parts.join(' ')} -->`;
55
+ }
56
+ function getCleartextValue(tag) {
57
+ var _a;
58
+ return (_a = CLEARTEXT_ATTR_REGEX.exec(tag)) === null || _a === void 0 ? void 0 : _a[3];
59
+ }
60
+ function setCleartextValue(tag, value) {
61
+ const hasAttr = CLEARTEXT_ATTR_REGEX.test(tag);
62
+ if (value === undefined) {
63
+ return hasAttr ? tag.replace(new RegExp(`\\s*${CLEARTEXT_ATTR_REGEX.source}`), '') : tag;
64
+ }
65
+ if (hasAttr) {
66
+ return tag.replace(CLEARTEXT_ATTR_REGEX, `$1$2${value}$2`);
67
+ }
68
+ return tag.replace(/\s*(\/?)>$/, (_, slash) => ` cleartextTrafficPermitted="${value}"${slash ? ' />' : '>'}`);
69
+ }
70
+ function indentOf(xml, index) {
71
+ var _a, _b;
72
+ const lineStart = xml.lastIndexOf('\n', index - 1) + 1;
73
+ return (_b = (_a = /^[ \t]*/.exec(xml.slice(lineStart, index))) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : '';
74
+ }
75
+ /**
76
+ * Returns the config XML with `cleartextTrafficPermitted="true"` on its `<base-config>`.
77
+ * When `xml` is undefined a minimal config is generated. Nothing else in the file is touched.
78
+ */
79
+ function enableCleartext(xml, flags) {
80
+ var _a;
81
+ if (xml === undefined) {
82
+ return `<?xml version="1.0" encoding="utf-8"?>
83
+ <network-security-config>
84
+ ${buildMarker({ created: true, ref: flags.ref })}
85
+ <base-config cleartextTrafficPermitted="true" />
86
+ </network-security-config>
87
+ `;
88
+ }
89
+ const existing = parseMarker(xml);
90
+ const baseConfig = BASE_CONFIG_REGEX.exec(xml);
91
+ if (existing) {
92
+ // Already enabled by the CLI: only make sure the value is still true.
93
+ return baseConfig ? xml.replace(baseConfig[0], setCleartextValue(baseConfig[0], 'true')) : xml;
94
+ }
95
+ if (baseConfig) {
96
+ const original = (_a = getCleartextValue(baseConfig[0])) !== null && _a !== void 0 ? _a : 'absent';
97
+ const indent = indentOf(xml, baseConfig.index);
98
+ const marker = `${buildMarker({ created: false, ref: flags.ref, orig: original })}\n${indent}`;
99
+ return (xml.slice(0, baseConfig.index) +
100
+ marker +
101
+ setCleartextValue(baseConfig[0], 'true') +
102
+ xml.slice(baseConfig.index + baseConfig[0].length));
103
+ }
104
+ const root = ROOT_REGEX.exec(xml);
105
+ if (!root) {
106
+ throw new Error('missing <network-security-config> root element');
107
+ }
108
+ const marker = buildMarker({ created: false, ref: flags.ref, orig: 'added' });
109
+ const addition = `\n ${marker}\n <base-config cleartextTrafficPermitted="true" />`;
110
+ if (root[1] === '/') {
111
+ const open = root[0].replace(/\s*\/>$/, '>');
112
+ return xml.replace(root[0], `${open}${addition}\n</network-security-config>`);
113
+ }
114
+ const end = root.index + root[0].length;
115
+ return xml.slice(0, end) + addition + (/^\s/.test(xml.slice(end)) ? '' : '\n') + xml.slice(end);
116
+ }
117
+ /**
118
+ * Undoes `enableCleartext`. Returns `null` when the file should be deleted, `undefined` when the CLI
119
+ * never touched it, and the restored XML otherwise.
120
+ */
121
+ function revertCleartext(xml) {
122
+ const marker = parseMarker(xml);
123
+ if (!marker) {
124
+ return undefined;
125
+ }
126
+ if (marker.created) {
127
+ return null;
128
+ }
129
+ const withoutMarker = xml.replace(MARKER_REGEX, '');
130
+ const baseConfig = BASE_CONFIG_REGEX.exec(withoutMarker);
131
+ if (!baseConfig) {
132
+ return withoutMarker;
133
+ }
134
+ if (marker.orig === 'added') {
135
+ const isSelfClosing = baseConfig[0].endsWith('/>');
136
+ if (isSelfClosing) {
137
+ const indent = indentOf(withoutMarker, baseConfig.index);
138
+ return withoutMarker.replace(new RegExp(`${indent}${escapeRegex(baseConfig[0])}[ \\t]*\\r?\\n?`), '');
139
+ }
140
+ return withoutMarker.replace(baseConfig[0], setCleartextValue(baseConfig[0], 'false'));
141
+ }
142
+ const original = marker.orig === 'absent' || marker.orig === undefined ? undefined : marker.orig;
143
+ return withoutMarker.replace(baseConfig[0], setCleartextValue(baseConfig[0], original));
144
+ }
145
+ function escapeRegex(value) {
146
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
147
+ }
148
+ function addManifestRef(manifest) {
149
+ return manifest.replace(APPLICATION_REGEX, `<application android:networkSecurityConfig="@xml/${DEFAULT_CONFIG_NAME}"`);
150
+ }
151
+ function removeManifestRef(manifest) {
152
+ return manifest.replace(new RegExp(`\\s+android:networkSecurityConfig\\s*=\\s*"@xml/${DEFAULT_CONFIG_NAME}"`), '');
153
+ }
154
+ /**
155
+ * Enables or disables cleartext traffic on the app module (`android/app`), leaving any
156
+ * network-security-config the app already has intact besides `cleartextTrafficPermitted`.
157
+ */
158
+ async function setAndroidCleartext(config, enabled) {
159
+ var _a;
160
+ const manifestPath = (0, path_1.join)(config.android.srcMainDirAbs, 'AndroidManifest.xml');
161
+ if (!(await (0, fs_extra_1.pathExists)(manifestPath))) {
162
+ return;
163
+ }
164
+ const manifest = await (0, fs_extra_1.readFile)(manifestPath, 'utf-8');
165
+ const referencedName = (_a = CONFIG_REF_REGEX.exec(manifest)) === null || _a === void 0 ? void 0 : _a[1];
166
+ const configPath = (0, path_1.join)(config.android.resDirAbs, 'xml', `${referencedName !== null && referencedName !== void 0 ? referencedName : DEFAULT_CONFIG_NAME}.xml`);
167
+ const configExists = await (0, fs_extra_1.pathExists)(configPath);
168
+ if (enabled) {
169
+ if (referencedName && !configExists) {
170
+ log_1.logger.warn(`Could not enable cleartext traffic: ${manifestPath} references @xml/${referencedName}, but ${configPath} does not exist.`);
171
+ return;
172
+ }
173
+ const current = configExists ? await (0, fs_extra_1.readFile)(configPath, 'utf-8') : undefined;
174
+ const addRef = !referencedName;
175
+ let updated;
176
+ try {
177
+ updated = enableCleartext(current, { ref: addRef });
178
+ }
179
+ catch (e) {
180
+ log_1.logger.warn(`Could not enable cleartext traffic: ${configPath} is not a valid network security config (${e.message}).`);
181
+ return;
182
+ }
183
+ await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(configPath));
184
+ await (0, fs_extra_1.writeFile)(configPath, updated);
185
+ if (addRef) {
186
+ await (0, fs_extra_1.writeFile)(manifestPath, addManifestRef(manifest));
187
+ }
188
+ return;
189
+ }
190
+ if (!configExists) {
191
+ return;
192
+ }
193
+ const current = await (0, fs_extra_1.readFile)(configPath, 'utf-8');
194
+ const marker = parseMarker(current);
195
+ const reverted = revertCleartext(current);
196
+ if (reverted === undefined) {
197
+ return;
198
+ }
199
+ if (reverted === null) {
200
+ await (0, fs_extra_1.remove)(configPath);
201
+ }
202
+ else {
203
+ await (0, fs_extra_1.writeFile)(configPath, reverted);
204
+ }
205
+ if ((marker === null || marker === void 0 ? void 0 : marker.ref) && referencedName === DEFAULT_CONFIG_NAME) {
206
+ await (0, fs_extra_1.writeFile)(manifestPath, removeManifestRef(manifest));
207
+ }
208
+ }
209
+ exports.setAndroidCleartext = setAndroidCleartext;
210
+ /**
211
+ * Cleartext used to be written to the generated `capacitor-cordova-android-plugins` module, where it never
212
+ * reached Capacitor-only apps. Removes the file generated by older CLI versions.
213
+ */
214
+ async function removeLegacyCordovaPluginsConfig(config) {
215
+ const legacyPath = (0, path_1.join)(config.android.cordovaPluginsDirAbs, 'src', 'main', 'res', 'xml', `${DEFAULT_CONFIG_NAME}.xml`);
216
+ if (!(await (0, fs_extra_1.pathExists)(legacyPath))) {
217
+ return;
218
+ }
219
+ if ((await (0, fs_extra_1.readFile)(legacyPath, 'utf-8')).trim() === LEGACY_CORDOVA_PLUGINS_CONFIG.trim()) {
220
+ await (0, fs_extra_1.remove)(legacyPath);
221
+ }
222
+ }
223
+ exports.removeLegacyCordovaPluginsConfig = removeLegacyCordovaPluginsConfig;
224
+ exports.__testables = {
225
+ enableCleartext,
226
+ revertCleartext,
227
+ addManifestRef,
228
+ removeManifestRef,
229
+ parseMarker,
230
+ };
@@ -16,10 +16,11 @@ const fs_1 = require("../util/fs");
16
16
  const node_1 = require("../util/node");
17
17
  const template_1 = require("../util/template");
18
18
  const common_2 = require("./common");
19
+ const network_security_config_1 = require("./network-security-config");
19
20
  const platform = 'android';
20
21
  const debug = (0, debug_1.default)('capacitor:android:update');
21
22
  async function updateAndroid(config) {
22
- var _a;
23
+ var _a, _b;
23
24
  const plugins = await getPluginsTask(config);
24
25
  const capacitorPlugins = plugins.filter((p) => (0, plugin_1.getPluginType)(p, platform) === 0 /* PluginType.Core */);
25
26
  (0, plugin_1.printPlugins)(capacitorPlugins, 'android');
@@ -44,6 +45,9 @@ async function updateAndroid(config) {
44
45
  await handleCordovaPluginsGradle(config, cordovaPlugins);
45
46
  await (0, cordova_1.writeCordovaAndroidManifest)(cordovaPlugins, config, platform);
46
47
  }
48
+ else {
49
+ await (0, network_security_config_1.setAndroidCleartext)(config, !!((_b = config.app.extConfig.server) === null || _b === void 0 ? void 0 : _b.cleartext));
50
+ }
47
51
  const incompatibleCordovaPlugins = plugins.filter((p) => (0, plugin_1.getPluginType)(p, platform) === 2 /* PluginType.Incompatible */);
48
52
  (0, plugin_1.printPlugins)(incompatibleCordovaPlugins, platform, 'incompatible');
49
53
  await (0, common_1.checkPlatformVersions)(config, platform);
package/dist/cordova.js CHANGED
@@ -7,6 +7,7 @@ const path_1 = require("path");
7
7
  const plist_1 = tslib_1.__importDefault(require("plist"));
8
8
  const prompts_1 = tslib_1.__importDefault(require("prompts"));
9
9
  const common_1 = require("./android/common");
10
+ const network_security_config_1 = require("./android/network-security-config");
10
11
  const colors_1 = tslib_1.__importDefault(require("./colors"));
11
12
  const errors_1 = require("./errors");
12
13
  const common_2 = require("./ios/common");
@@ -754,26 +755,16 @@ async function writeCordovaAndroidManifest(cordovaPlugins, config, platform, cle
754
755
  });
755
756
  });
756
757
  const cleartextString = 'android:usesCleartextTraffic="true"';
757
- const networkSecurityConfigValue = cleartext || ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext) || applicationXMLAttributes.includes(cleartextString)
758
- ? 'android:networkSecurityConfig="@xml/network_security_config"'
759
- : '';
760
- const networkSecurityConfigDir = (0, path_1.join)(config.android.cordovaPluginsDirAbs, 'src', 'main', 'res', 'xml');
761
- const networkSecurityConfigPath = (0, path_1.join)(networkSecurityConfigDir, 'network_security_config.xml');
762
- if (networkSecurityConfigValue) {
763
- await (0, fs_extra_1.ensureDir)(networkSecurityConfigDir);
764
- await (0, fs_extra_1.writeFile)(networkSecurityConfigPath, `<?xml version="1.0" encoding="utf-8"?>
765
- <network-security-config>
766
- <base-config cleartextTrafficPermitted="true" />
767
- </network-security-config>
768
- `);
769
- }
770
- else if (await (0, fs_extra_1.pathExists)(networkSecurityConfigPath)) {
771
- await (0, fs_extra_1.remove)(networkSecurityConfigPath);
772
- }
758
+ const cleartextEnabled = !!(cleartext ||
759
+ ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext) ||
760
+ applicationXMLAttributes.includes(cleartextString));
761
+ // Cleartext is applied to the app module, since the Cordova plugins module is not linked into Capacitor-only apps
762
+ await (0, network_security_config_1.removeLegacyCordovaPluginsConfig)(config);
763
+ await (0, network_security_config_1.setAndroidCleartext)(config, cleartextEnabled);
773
764
  let content = `<?xml version='1.0' encoding='utf-8'?>
774
765
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
775
766
  xmlns:amazon="http://schemas.amazon.com/apk/res/android">
776
- <application ${applicationXMLAttributes.filter((attr) => !attr.includes('android:usesCleartextTraffic')).join('\n')} ${networkSecurityConfigValue}>
767
+ <application ${applicationXMLAttributes.filter((attr) => !attr.includes('android:usesCleartextTraffic')).join('\n')}>
777
768
  ${applicationXMLEntries.join('\n')}
778
769
  </application>
779
770
  ${rootXMLEntries.join('\n')}
@@ -528,46 +528,6 @@ export interface CapacitorConfig {
528
528
  packageOptions?: {
529
529
  [pluginId: string]: PackageOptions;
530
530
  };
531
- /**
532
- * Override which Capacitor Swift package the generated `Package.swift` depends on.
533
- *
534
- * By default the generated package pins the git tag matching the installed
535
- * `@capacitor/ios` version, which is correct for both stable and pre-release versions
536
- * and matches what CocoaPods resolves.
537
- *
538
- * Set this only when developing against an unreleased Capacitor, such as a local
539
- * checkout or a feature branch. Exactly one of `path`, `branch`, `revision`, `exact`,
540
- * or `from` must be set. `path` points at the repository root (the directory containing
541
- * `Package.swift`) and may be relative to the app.
542
- *
543
- * The `CAPACITOR_IOS_PACKAGE` environment variable takes precedence over this setting,
544
- * so a checkout can be redirected without editing committed configuration.
545
- *
546
- * Note that `branch` and `revision` builds are not reproducible and decouple the native
547
- * code from the `@capacitor/core` JavaScript bridge it is versioned against.
548
- *
549
- * @since 9.0.0
550
- * @example { "path": "../capacitor" }
551
- * @example { "branch": "next" }
552
- */
553
- capacitorPackage?: {
554
- /**
555
- * Repository to fetch from. Ignored when `path` is set.
556
- *
557
- * @default 'https://github.com/ionic-team/capacitor'
558
- */
559
- url?: string;
560
- /** Path to a local Capacitor repository root. */
561
- path?: string;
562
- /** Branch to track. */
563
- branch?: string;
564
- /** Exact commit to pin. */
565
- revision?: string;
566
- /** Exact version tag to pin. */
567
- exact?: string;
568
- /** Minimum version tag, allowing compatible upgrades. */
569
- from?: string;
570
- };
571
531
  };
572
532
  };
573
533
  };
@@ -626,6 +586,13 @@ export interface CapacitorConfig {
626
586
  *
627
587
  * On Android, all cleartext traffic is disabled by default as of API 28.
628
588
  *
589
+ * When enabled, the CLI sets `cleartextTrafficPermitted="true"` on the `<base-config>` of the app's
590
+ * `network-security-config.xml` (`android/app/src/main/res/xml/`), creating the file and the
591
+ * `android:networkSecurityConfig` manifest reference if the app has none. If the app already has its own
592
+ * network security config, only that attribute is changed. Other settings, such as a `<domain-config>`
593
+ * that disallows cleartext for specific domains, are left as they are and still apply.
594
+ * The change is reverted when the option is disabled.
595
+ *
629
596
  * This is intended for use with live-reload servers where unencrypted HTTP
630
597
  * traffic is often used.
631
598
  *
@@ -757,7 +724,7 @@ export interface PluginsConfig {
757
724
  *
758
725
  * This option is only supported on Android.
759
726
  *
760
- * `native` = (recommended) For older Chromium versions (< v140) this embeds the webview with padding and sets the `env(safe-area-inset-*)` variables to `0px`. For newer Chromium versions (>= v140) this makes sure the webview adheres to the `viewport-fit` meta tag. If set to `viewport-fit="cover"` this will make the webview edge-to-edge and the `env(safe-area-inset-*)` variables will contain the correct values. With those values you could set padding for example so make sure the webview is shown correctly.
727
+ * `native` = (recommended) For older Chromium versions (< v140) this embeds the webview with padding and sets the `env(safe-area-inset-*)` variables to `0px`. For newer Chromium versions (>= v140) this makes sure the webview adheres to the `viewport-fit` meta tag. If set to `viewport-fit="cover"` this will make the webview edge-to-edge and the `env(safe-area-inset-*)` variables will contain the correct values. With those values you could set padding for example to make sure the webview is shown correctly.
761
728
  *
762
729
  * `css` = This is the same as `native`, but it also injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
763
730
  *
package/dist/index.js CHANGED
@@ -105,24 +105,14 @@ function runProgram(config) {
105
105
  .option('--configuration <name>', 'Configuration name of the iOS Scheme')
106
106
  .option('--keystorealiaspass <keystoreAliasPass>', 'Password for the Key Alias')
107
107
  .addOption(new commander_1.Option('--androidreleasetype <androidreleasetype>', 'Android release type; APK or AAB').choices([
108
- 'AAB',
109
- 'APK',
108
+ ...telemetry_1.TELEMETRY_SAFE_CHOICES.androidreleasetype,
110
109
  ]))
111
110
  .addOption(new commander_1.Option('--signing-type <signingtype>', 'Program used to sign apps (default: jarsigner)').choices([
112
- 'apksigner',
113
- 'jarsigner',
111
+ ...telemetry_1.TELEMETRY_SAFE_CHOICES.signingType,
114
112
  ]))
115
113
  .addOption(new commander_1.Option('--xcode-team-id <xcodeTeamID>', 'The Developer team to use for building and exporting the archive'))
116
- .addOption(new commander_1.Option('--xcode-export-method <xcodeExportMethod>', 'Describes how xcodebuild should export the archive (default: app-store-connect)').choices([
117
- 'app-store-connect',
118
- 'release-testing',
119
- 'enterprise',
120
- 'debugging',
121
- 'developer-id',
122
- 'mac-application',
123
- 'validation',
124
- ]))
125
- .addOption(new commander_1.Option('--xcode-signing-style <xcodeSigningStyle>', 'The iOS signing style to use when building the app for distribution (default: automatic)').choices(['automatic', 'manual']))
114
+ .addOption(new commander_1.Option('--xcode-export-method <xcodeExportMethod>', 'Describes how xcodebuild should export the archive (default: app-store-connect)').choices([...telemetry_1.TELEMETRY_SAFE_CHOICES.xcodeExportMethod]))
115
+ .addOption(new commander_1.Option('--xcode-signing-style <xcodeSigningStyle>', 'The iOS signing style to use when building the app for distribution (default: automatic)').choices([...telemetry_1.TELEMETRY_SAFE_CHOICES.xcodeSigningStyle]))
126
116
  .addOption(new commander_1.Option('--xcode-signing-certificate <xcodeSigningCertificate>', 'A certificate name, SHA-1 hash, or automatic selector to use for signing for iOS builds'))
127
117
  .addOption(new commander_1.Option('--xcode-provisioning-profile <xcodeProvisioningProfile>', 'A provisioning profile name or UUID for iOS builds'))
128
118
  .action((0, cli_1.wrapAction)((0, telemetry_1.telemetryAction)(config, async (platform, { scheme, flavor, keystorepath, keystorepass, keystorealias, keystorealiaspass, androidreleasetype, signingType, configuration, xcodeTeamId, xcodeExportMethod, xcodeSigningStyle, xcodeSigningCertificate, xcodeProvisioningProfile, }) => {
@@ -12,7 +12,6 @@ const errors_1 = require("../errors");
12
12
  const log_1 = require("../log");
13
13
  const plugin_1 = require("../plugin");
14
14
  const copy_1 = require("../tasks/copy");
15
- const migrate_1 = require("../tasks/migrate");
16
15
  const capacitor_package_1 = require("../util/capacitor-package");
17
16
  const cordova_ios_1 = require("../util/cordova-ios");
18
17
  const fs_1 = require("../util/fs");
@@ -55,30 +54,21 @@ async function updatePluginFiles(config, plugins, deployment) {
55
54
  const validSPMPackages = await (0, spm_1.checkPluginsForPackageSwift)(config, plugins);
56
55
  const iosPlatformVersion = await (0, common_1.getCapacitorPackageVersion)(config, config.ios.name);
57
56
  const majorCapVersion = (0, semver_1.major)(iosPlatformVersion);
58
- const capacitorPackage = await (0, capacitor_package_1.resolveCapacitorPackage)(config, 'from');
57
+ const capacitorPackagePath = await (0, capacitor_package_1.resolveCapacitorPackagePath)(config);
59
58
  const iosVersion = (0, common_2.getMajoriOSVersion)(config);
60
59
  await Promise.all(validSPMPackages.map(async (plugin) => {
61
60
  const packageSwiftPath = (0, path_1.join)(plugin.rootPath, 'Package.swift');
62
- let content = await (0, fs_extra_1.readFile)(packageSwiftPath, { encoding: 'utf-8' });
61
+ const content = await (0, fs_extra_1.readFile)(packageSwiftPath, { encoding: 'utf-8' });
63
62
  const version = (0, capacitor_package_1.findCapacitorDependencyVersion)(content);
64
63
  if (version && (0, semver_1.major)(version) != majorCapVersion) {
65
64
  log_1.logger.warn(`${plugin.id} is built for Capacitor ${(0, semver_1.major)(version)}, it might cause issues`);
66
65
  }
67
- if (capacitorPackage.sourceBased) {
68
- // The plugin has to resolve to the same package identity as the app, or SPM ends up with
69
- // two packages that both vend a Capacitor product.
70
- let rewritten = (0, capacitor_package_1.rewriteCapacitorDependency)(content, capacitorPackage, plugin.rootPath);
71
- rewritten = (0, capacitor_package_1.patchPackageSwiftDeploymentTarget)(rewritten, iosVersion).content;
72
- if (rewritten !== content) {
73
- await (0, fs_extra_1.writeFile)(packageSwiftPath, rewritten);
74
- }
75
- return;
76
- }
77
- if (version && (0, semver_1.major)(version) != majorCapVersion) {
78
- const preCapVersion = (0, semver_1.prerelease)(iosPlatformVersion);
79
- const forceVersion = preCapVersion ? iosPlatformVersion : `${majorCapVersion}.0.0`;
80
- content = (0, migrate_1.setAllStringIn)(content, `url: "https://github.com/ionic-team/capacitor-swift-pm.git",`, `)`, ` from: "${forceVersion}"`);
81
- await (0, fs_extra_1.writeFile)(packageSwiftPath, content);
66
+ // The plugin has to resolve to the same package identity as the app, or SPM ends up with
67
+ // two packages that both vend a Capacitor product.
68
+ let rewritten = (0, capacitor_package_1.rewriteCapacitorDependency)(content, capacitorPackagePath, plugin.rootPath);
69
+ rewritten = (0, capacitor_package_1.patchPackageSwiftDeploymentTarget)(rewritten, iosVersion).content;
70
+ if (rewritten !== content) {
71
+ await (0, fs_extra_1.writeFile)(packageSwiftPath, rewritten);
82
72
  }
83
73
  }));
84
74
  await (0, spm_1.generatePackageFile)(config, validSPMPackages.concat(cordovaPlugins));
package/dist/ipc.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.receive = exports.send = void 0;
3
+ exports.deliver = exports.receive = exports.send = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const utils_subprocess_1 = require("@ionic/utils-subprocess");
6
6
  const debug_1 = tslib_1.__importDefault(require("debug"));
@@ -9,6 +9,11 @@ const https_1 = require("https");
9
9
  const path_1 = require("path");
10
10
  const cli_1 = require("./util/cli");
11
11
  const debug = (0, debug_1.default)('capacitor:ipc');
12
+ /**
13
+ * How long an awaited delivery may take. Short enough that a developer never
14
+ * notices it, long enough for a request from a cold sandbox.
15
+ */
16
+ const DELIVERY_TIMEOUT = 3000;
12
17
  /**
13
18
  * Send an IPC message to a forked process.
14
19
  */
@@ -32,8 +37,39 @@ exports.send = send;
32
37
  async function receive(msg) {
33
38
  debug('Received %O IPC message', msg.type);
34
39
  if (msg.type === 'telemetry') {
35
- const now = new Date().toISOString();
36
- const { data } = msg;
40
+ await deliver(msg.data);
41
+ }
42
+ }
43
+ exports.receive = receive;
44
+ /**
45
+ * POST a metric to the events service.
46
+ *
47
+ * Never rejects and never writes to stderr. Telemetry failing is not something
48
+ * the caller can act on, and it must not change the command's outcome or its
49
+ * exit code — so every ending, including the timeout, resolves.
50
+ */
51
+ function deliver(data, timeout = DELIVERY_TIMEOUT) {
52
+ return new Promise((resolve) => {
53
+ const body = JSON.stringify({ metrics: [data], sent_at: new Date().toISOString() });
54
+ let settled = false;
55
+ const finish = (reason) => {
56
+ if (settled) {
57
+ return;
58
+ }
59
+ settled = true;
60
+ clearTimeout(deadline);
61
+ debug('Delivery of %O metric %s', data.name, reason);
62
+ resolve();
63
+ };
64
+ // An overall deadline rather than `req.setTimeout`, which is a socket
65
+ // inactivity timer and only arms once the socket connects — so it never
66
+ // fires against an unroutable host, which is exactly the sandbox this
67
+ // bound exists for. Unref'd so a pending request can't hold the CLI open.
68
+ const deadline = setTimeout(() => {
69
+ req.destroy();
70
+ finish('timed out');
71
+ }, timeout);
72
+ deadline.unref();
37
73
  // This request is only made if telemetry is on.
38
74
  const req = (0, https_1.request)({
39
75
  hostname: 'metrics-capacitor.outsystems.com',
@@ -45,17 +81,21 @@ async function receive(msg) {
45
81
  },
46
82
  }, (response) => {
47
83
  debug('Sent %O metric to events service (status: %O)', data.name, response.statusCode);
48
- if (response.statusCode !== 202) {
84
+ if (response.statusCode === 202) {
85
+ // Draining is what lets the socket close; without it an awaited
86
+ // delivery sits until the timeout fires.
87
+ response.resume();
88
+ }
89
+ else {
49
90
  response.on('data', (chunk) => {
50
91
  debug('Bad response from events service. Request body: %O', chunk.toString());
51
92
  });
52
93
  }
94
+ response.on('end', () => finish('completed'));
95
+ response.on('error', () => finish('failed reading the response'));
53
96
  });
54
- const body = {
55
- metrics: [data],
56
- sent_at: now,
57
- };
58
- req.end(JSON.stringify(body));
59
- }
97
+ req.on('error', (e) => finish(`failed: ${e.message}`));
98
+ req.end(body);
99
+ });
60
100
  }
61
- exports.receive = receive;
101
+ exports.deliver = deliver;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectDeclared = void 0;
4
+ const agent_cli_detector_1 = require("agent-cli-detector");
5
+ const registry_1 = require("./registry");
6
+ const strategy = new agent_cli_detector_1.EnvironmentDetectionStrategy();
7
+ /**
8
+ * Identify a registered harness from the variables it sets to announce itself.
9
+ *
10
+ * Driven through the strategy rather than the package's `detectAgent()` because
11
+ * that reduces to a bare `{ detected, agent }` and discards which variable
12
+ * matched — which callers need in order to report an unrecognised harness.
13
+ *
14
+ * Only the agent id and the session variable's *name* leave this function. The
15
+ * evidence also carries each matched variable's live value, and some of those
16
+ * hold access tokens, so nothing else from it is propagated.
17
+ */
18
+ function detectDeclared(env) {
19
+ var _a, _b;
20
+ // `pid` and `maxProcessDepth` are required by the context type but unread by
21
+ // the environment strategy. Walking the process tree needs a subprocess call
22
+ // with no portable Windows equivalent, so this path stays environment-only.
23
+ const evidence = strategy.detect({
24
+ env,
25
+ agents: registry_1.capacitorAgents,
26
+ pid: 0,
27
+ maxProcessDepth: 0,
28
+ });
29
+ if (evidence.length === 0) {
30
+ return undefined;
31
+ }
32
+ const matchCounts = new Map();
33
+ for (const { agent } of evidence) {
34
+ matchCounts.set(agent.id, ((_a = matchCounts.get(agent.id)) !== null && _a !== void 0 ? _a : 0) + 1);
35
+ }
36
+ // One environment can satisfy several harnesses at once — an agent CLI run
37
+ // from inside another vendor's terminal declares both. Most matched signals
38
+ // wins; Map iteration preserves registry order, so ties resolve the same way
39
+ // on every platform instead of depending on `Object.keys(env)`.
40
+ let winner = '';
41
+ let best = 0;
42
+ for (const [id, count] of matchCounts) {
43
+ if (count > best) {
44
+ winner = id;
45
+ best = count;
46
+ }
47
+ }
48
+ const definition = registry_1.capacitorAgents.find((agent) => agent.id === winner);
49
+ return {
50
+ id: winner,
51
+ sessionVar: (_b = definition === null || definition === void 0 ? void 0 : definition.sessionEnv) === null || _b === void 0 ? void 0 : _b.find((name) => env[name]),
52
+ };
53
+ }
54
+ exports.detectDeclared = detectDeclared;