react-native-notify-kit 10.4.0 → 10.4.2

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.
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "10.4.0";
1
+ export declare const version = "10.4.2";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by genversion.
2
- export const version = '10.4.0';
2
+ export const version = '10.4.2';
3
3
  //# sourceMappingURL=version.js.map
@@ -18,8 +18,7 @@
18
18
  #import <Foundation/Foundation.h>
19
19
  #import <NotifeeSpec/NotifeeSpec.h>
20
20
  #import <React/RCTEventEmitter.h>
21
- #import "NotifeeCore.h"
22
21
 
23
- @interface NotifeeApiModule : RCTEventEmitter <NativeNotifeeModuleSpec, NotifeeCoreDelegate>
22
+ @interface NotifeeApiModule : RCTEventEmitter <NativeNotifeeModuleSpec>
24
23
 
25
24
  @end
@@ -18,7 +18,11 @@
18
18
  #import "NotifeeApiModule.h"
19
19
  #import <React/RCTUtils.h>
20
20
  #import <UIKit/UIKit.h>
21
- #import "NotifeeCore+UNUserNotificationCenter.h"
21
+ #import "../NotifeeCore/NotifeeCore+UNUserNotificationCenter.h"
22
+ #import "../NotifeeCore/NotifeeCore.h"
23
+
24
+ @interface NotifeeApiModule () <NotifeeCoreDelegate>
25
+ @end
22
26
 
23
27
  @interface NotifeeCoreUNUserNotificationCenter (Rechain)
24
28
  - (void)rechainUserNotificationCenterDelegate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-notify-kit",
3
- "version": "10.4.0",
3
+ "version": "10.4.2",
4
4
  "author": "Marco Crupi",
5
5
  "description": "Maintained Notifee-compatible React Native notifications library for Android, iOS, FCM Mode, rich notifications, foreground services, and Expo CNG development builds.",
6
6
  "main": "dist/index.js",
@@ -4,11 +4,16 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const {
7
+ DEFAULT_NSE_CURRENT_PROJECT_VERSION,
8
+ DEFAULT_NSE_MARKETING_VERSION,
7
9
  renderNotificationServiceSwift,
8
10
  renderNseEntitlementsPlist,
9
11
  renderNseInfoPlist,
10
12
  } = require('../shared/nse/initNseCore');
11
13
 
14
+ const MARKETING_VERSION_MARKER = '__NOTIFYKIT_NSE_MARKETING_VERSION__';
15
+ const CURRENT_PROJECT_VERSION_MARKER = '__NOTIFYKIT_NSE_CURRENT_PROJECT_VERSION__';
16
+
12
17
  function withNotifyKitIosNseFiles(config, nseOptions) {
13
18
  if (!nseOptions.enabled) {
14
19
  return config;
@@ -22,13 +27,21 @@ function withNotifyKitIosNseFiles(config, nseOptions) {
22
27
  writeNotifyKitIosNseFiles(
23
28
  modConfig.modRequest.platformProjectRoot,
24
29
  nseOptions.targetName,
30
+ resolveNseVersionOptions(modConfig),
25
31
  );
26
32
  return modConfig;
27
33
  },
28
34
  ]);
29
35
  }
30
36
 
31
- function writeNotifyKitIosNseFiles(platformProjectRoot, targetName) {
37
+ function writeNotifyKitIosNseFiles(
38
+ platformProjectRoot,
39
+ targetName,
40
+ versionOptions = {
41
+ marketingVersion: DEFAULT_NSE_MARKETING_VERSION,
42
+ currentProjectVersion: DEFAULT_NSE_CURRENT_PROJECT_VERSION,
43
+ },
44
+ ) {
32
45
  const targetDir = path.join(platformProjectRoot, targetName);
33
46
  fs.mkdirSync(targetDir, { recursive: true });
34
47
 
@@ -38,7 +51,8 @@ function writeNotifyKitIosNseFiles(platformProjectRoot, targetName) {
38
51
  );
39
52
  writeFileIfMissingOrIdentical(
40
53
  path.join(targetDir, 'Info.plist'),
41
- renderNseInfoPlist({ targetName }),
54
+ renderNseInfoPlist({ targetName, ...versionOptions }),
55
+ contents => isGeneratedNseInfoPlist(contents, targetName),
42
56
  );
43
57
  writeFileIfMissingOrIdentical(
44
58
  path.join(targetDir, `${targetName}.entitlements`),
@@ -46,10 +60,44 @@ function writeNotifyKitIosNseFiles(platformProjectRoot, targetName) {
46
60
  );
47
61
  }
48
62
 
49
- function writeFileIfMissingOrIdentical(filePath, contents) {
63
+ function resolveNseVersionOptions(config) {
64
+ return {
65
+ marketingVersion: optionalString(config.version) ?? DEFAULT_NSE_MARKETING_VERSION,
66
+ currentProjectVersion:
67
+ optionalString(config.ios?.buildNumber) ?? DEFAULT_NSE_CURRENT_PROJECT_VERSION,
68
+ };
69
+ }
70
+
71
+ function optionalString(value) {
72
+ return typeof value === 'string' ? value : undefined;
73
+ }
74
+
75
+ function isGeneratedNseInfoPlist(contents, targetName) {
76
+ const templateWithMarkers = renderNseInfoPlist({
77
+ targetName,
78
+ marketingVersion: MARKETING_VERSION_MARKER,
79
+ currentProjectVersion: CURRENT_PROJECT_VERSION_MARKER,
80
+ });
81
+ const escapedTemplate = escapeRegExp(templateWithMarkers)
82
+ .replace(MARKETING_VERSION_MARKER, '[^<]*')
83
+ .replace(CURRENT_PROJECT_VERSION_MARKER, '[^<]*');
84
+
85
+ return new RegExp(`^${escapedTemplate}$`).test(contents);
86
+ }
87
+
88
+ function escapeRegExp(value) {
89
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
90
+ }
91
+
92
+ function writeFileIfMissingOrIdentical(filePath, contents, canOverwrite) {
50
93
  if (fs.existsSync(filePath)) {
51
94
  const currentContents = fs.readFileSync(filePath, 'utf8');
52
95
  if (currentContents !== contents) {
96
+ if (canOverwrite?.(currentContents)) {
97
+ fs.writeFileSync(filePath, contents, 'utf8');
98
+ return;
99
+ }
100
+
53
101
  throw new Error(
54
102
  `[react-native-notify-kit] Refusing to overwrite existing ${filePath}. ` +
55
103
  'Delete it or make it match the generated NotifyKit NSE template.',
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('fs');
3
4
  const path = require('path');
4
5
 
5
6
  const { patchPodfileForNotifyKitNse } = require('../shared/nse/patchPodfile');
@@ -14,11 +15,12 @@ function withNotifyKitIosNsePodfile(config, nseOptions) {
14
15
 
15
16
  return withPodfile(config, modConfig => {
16
17
  const { projectRoot, platformProjectRoot } = modConfig.modRequest;
17
- const packagePathFromIos = resolveNotifyKitPackagePathFromIos(
18
- projectRoot,
19
- platformProjectRoot,
20
- );
21
- const hostUseFrameworks = detectPodfileUseFrameworks(modConfig.modResults.contents);
18
+ const packagePathFromIos = resolveNotifyKitPackagePathFromIos(projectRoot, platformProjectRoot);
19
+ const hostUseFrameworks = detectPodfileUseFrameworks(modConfig.modResults.contents, {
20
+ ignoredTargetName: nseOptions.targetName,
21
+ podfileProperties: readExpoPodfileProperties(platformProjectRoot),
22
+ env: process.env,
23
+ });
22
24
  const useFrameworks = resolveNseUseFrameworks(hostUseFrameworks, configuredUseFrameworks);
23
25
  const result = patchPodfileForNotifyKitNse(modConfig.modResults.contents, {
24
26
  targetName: nseOptions.targetName,
@@ -45,30 +47,56 @@ function normalizePodfilePath(filePath) {
45
47
  return filePath.replace(/\\/g, '/');
46
48
  }
47
49
 
48
- function detectPodfileUseFrameworks(contents) {
49
- let detected = false;
50
+ function detectPodfileUseFrameworks(contents, optionsOrIgnoredTargetName) {
51
+ const options =
52
+ typeof optionsOrIgnoredTargetName === 'string'
53
+ ? { ignoredTargetName: optionsOrIgnoredTargetName }
54
+ : (optionsOrIgnoredTargetName ?? {});
55
+ const targetBlocks = findTargetBlocks(contents);
56
+ const firstHostTarget = targetBlocks.find(
57
+ block => block.targetName !== options.ignoredTargetName,
58
+ );
59
+ let globalUseFrameworks = false;
60
+ let hostUseFrameworks = false;
61
+ let charIndex = 0;
50
62
 
51
63
  for (const line of contents.split('\n')) {
52
- const stripped = line.replace(/#.*$/, '').trim();
53
- if (!/^use_frameworks!(?:\s|$)/.test(stripped)) {
64
+ const detectedUseFrameworks = parseUseFrameworksLine(line, options);
65
+ if (detectedUseFrameworks === null) {
66
+ charIndex += line.length + 1;
54
67
  continue;
55
68
  }
56
69
 
57
- if (/:linkage\s*=>\s*:static\b/.test(stripped) || /\blinkage:\s*:static\b/.test(stripped)) {
58
- return 'static';
70
+ const targetBlock = findMostSpecificTargetBlock(targetBlocks, charIndex);
71
+ if (!targetBlock) {
72
+ globalUseFrameworks = mergeUseFrameworksDetection(globalUseFrameworks, detectedUseFrameworks);
73
+ } else if (targetBlock === firstHostTarget) {
74
+ hostUseFrameworks = mergeUseFrameworksDetection(hostUseFrameworks, detectedUseFrameworks);
59
75
  }
60
76
 
61
- if (
62
- /:linkage\s*=>\s*:dynamic\b/.test(stripped) ||
63
- /\blinkage:\s*:dynamic\b/.test(stripped)
64
- ) {
65
- return 'dynamic';
77
+ charIndex += line.length + 1;
78
+ }
79
+
80
+ return hostUseFrameworks !== false ? hostUseFrameworks : globalUseFrameworks;
81
+ }
82
+
83
+ function readExpoPodfileProperties(platformProjectRoot) {
84
+ const propertiesPath = path.join(platformProjectRoot, 'Podfile.properties.json');
85
+
86
+ try {
87
+ const parsed = JSON.parse(fs.readFileSync(propertiesPath, 'utf8'));
88
+ return isPlainObject(parsed) ? parsed : {};
89
+ } catch (error) {
90
+ if (isFileNotFoundError(error)) {
91
+ return {};
66
92
  }
67
93
 
68
- detected = true;
94
+ throw error;
69
95
  }
96
+ }
70
97
 
71
- return detected;
98
+ function isFileNotFoundError(error) {
99
+ return isPlainObject(error) && error.code === 'ENOENT';
72
100
  }
73
101
 
74
102
  function resolveNseUseFrameworks(hostUseFrameworks, configuredUseFrameworks) {
@@ -110,6 +138,177 @@ function isPlainObject(value) {
110
138
  return typeof value === 'object' && value !== null && !Array.isArray(value);
111
139
  }
112
140
 
141
+ function findTargetBlocks(contents) {
142
+ const targetPattern = /^[ \t]*target\s+['"]([^'"]+)['"]\s+do\b.*$/gm;
143
+ const blocks = [];
144
+ let match;
145
+
146
+ while ((match = targetPattern.exec(contents)) !== null) {
147
+ const endLineStart = findMatchingRubyBlockEnd(contents, match.index);
148
+ if (endLineStart === -1) {
149
+ continue;
150
+ }
151
+
152
+ blocks.push({
153
+ targetName: match[1],
154
+ startIndex: match.index,
155
+ endIndex: findLineEnd(contents, endLineStart),
156
+ });
157
+ }
158
+
159
+ return blocks;
160
+ }
161
+
162
+ function findMostSpecificTargetBlock(targetBlocks, charIndex) {
163
+ return targetBlocks
164
+ .filter(block => charIndex >= block.startIndex && charIndex < block.endIndex)
165
+ .sort((a, b) => b.startIndex - a.startIndex)[0];
166
+ }
167
+
168
+ function parseUseFrameworksLine(line, options) {
169
+ const stripped = stripRubyLineComment(line).trim();
170
+ if (!/^use_frameworks!(?:\s|$)/.test(stripped)) {
171
+ return null;
172
+ }
173
+
174
+ if (!isUseFrameworksConditionActive(stripped, options)) {
175
+ return null;
176
+ }
177
+
178
+ const podfilePropertyLinkage = getPodfilePropertyUseFrameworksLinkage(stripped, options);
179
+ if (podfilePropertyLinkage !== null) {
180
+ return podfilePropertyLinkage;
181
+ }
182
+
183
+ const envLinkage = getEnvUseFrameworksLinkage(stripped, options);
184
+ if (envLinkage !== null) {
185
+ return envLinkage;
186
+ }
187
+
188
+ if (/:linkage\s*=>\s*:static\b/.test(stripped) || /\blinkage:\s*:static\b/.test(stripped)) {
189
+ return 'static';
190
+ }
191
+
192
+ if (/:linkage\s*=>\s*:dynamic\b/.test(stripped) || /\blinkage:\s*:dynamic\b/.test(stripped)) {
193
+ return 'dynamic';
194
+ }
195
+
196
+ return true;
197
+ }
198
+
199
+ function mergeUseFrameworksDetection(current, detected) {
200
+ return detected;
201
+ }
202
+
203
+ function isUseFrameworksConditionActive(strippedLine, options) {
204
+ const condition = strippedLine.match(/\s+if\s+(.+)$/)?.[1]?.trim();
205
+ if (!condition) {
206
+ return true;
207
+ }
208
+
209
+ const podfilePropertyName = getPodfilePropertyName(condition);
210
+ if (podfilePropertyName !== null) {
211
+ return isRubyTruthy(options.podfileProperties?.[podfilePropertyName]);
212
+ }
213
+
214
+ const envName = getEnvName(condition);
215
+ if (envName !== null) {
216
+ return isRubyTruthy(options.env?.[envName]);
217
+ }
218
+
219
+ return true;
220
+ }
221
+
222
+ function getPodfilePropertyUseFrameworksLinkage(strippedLine, options) {
223
+ const propertyName = strippedLine.match(
224
+ /(?:\:linkage\s*=>|linkage:)\s*podfile_properties\[['"]([^'"]+)['"]\](?:\.to_sym)?/,
225
+ )?.[1];
226
+
227
+ return propertyName
228
+ ? normalizeUseFrameworksValue(options.podfileProperties?.[propertyName])
229
+ : null;
230
+ }
231
+
232
+ function getEnvUseFrameworksLinkage(strippedLine, options) {
233
+ const envName = strippedLine.match(
234
+ /(?:\:linkage\s*=>|linkage:)\s*ENV\[['"]([^'"]+)['"]\](?:\.to_sym)?/,
235
+ )?.[1];
236
+
237
+ return envName ? normalizeUseFrameworksValue(options.env?.[envName]) : null;
238
+ }
239
+
240
+ function normalizeUseFrameworksValue(value) {
241
+ if (value === 'static' || value === 'dynamic' || value === true) {
242
+ return value;
243
+ }
244
+
245
+ return null;
246
+ }
247
+
248
+ function getPodfilePropertyName(expression) {
249
+ return expression.match(/^podfile_properties\[['"]([^'"]+)['"]\]$/)?.[1] ?? null;
250
+ }
251
+
252
+ function getEnvName(expression) {
253
+ return expression.match(/^ENV\[['"]([^'"]+)['"]\]$/)?.[1] ?? null;
254
+ }
255
+
256
+ function isRubyTruthy(value) {
257
+ return value !== undefined && value !== null && value !== false;
258
+ }
259
+
260
+ function findLineEnd(content, lineStartIndex) {
261
+ const newlineIndex = content.indexOf('\n', lineStartIndex);
262
+ return newlineIndex === -1 ? content.length : newlineIndex + 1;
263
+ }
264
+
265
+ function findMatchingRubyBlockEnd(content, startIndex) {
266
+ const afterStart = content.slice(startIndex);
267
+ const lines = afterStart.split('\n');
268
+ let depth = 0;
269
+ let charIndex = startIndex;
270
+
271
+ for (const line of lines) {
272
+ const trimmed = stripRubyLineComment(line).trim();
273
+
274
+ depth += countRubyBlockOpeners(trimmed);
275
+
276
+ if (trimmed === 'end') {
277
+ depth--;
278
+ if (depth === 0) {
279
+ return charIndex;
280
+ }
281
+ }
282
+
283
+ charIndex += line.length + 1;
284
+ }
285
+
286
+ return -1;
287
+ }
288
+
289
+ function countRubyBlockOpeners(line) {
290
+ if (line.length === 0) {
291
+ return 0;
292
+ }
293
+
294
+ const startsWithKeywordBlock = /^(if|unless|case|begin|while|until|for|def|class|module)\b/.test(
295
+ line,
296
+ );
297
+ if (startsWithKeywordBlock) {
298
+ return 1;
299
+ }
300
+
301
+ if (/\bdo\b(\s*\|[^|]*\|)?\s*$/.test(line)) {
302
+ return 1;
303
+ }
304
+
305
+ return 0;
306
+ }
307
+
308
+ function stripRubyLineComment(line) {
309
+ return line.replace(/#.*$/, '');
310
+ }
311
+
113
312
  function requireExpoConfigPlugins() {
114
313
  try {
115
314
  return require('expo/config-plugins');
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ const {
4
+ DEFAULT_NSE_CURRENT_PROJECT_VERSION,
5
+ DEFAULT_NSE_MARKETING_VERSION,
6
+ } = require('../shared/nse/initNseCore');
3
7
  const {
4
8
  patchXcodeProjectForNotifyKitNse,
5
9
  } = require('../shared/nse/patchXcodeProject');
@@ -20,6 +24,7 @@ function withNotifyKitIosNseXcodeProject(config, nseOptions) {
20
24
  targetName: nseOptions.targetName,
21
25
  bundleIdentifier,
22
26
  parentTargetName: modConfig.modRequest.projectName,
27
+ ...resolveNseVersionOptions(modConfig),
23
28
  });
24
29
 
25
30
  if (result.didChange && !result.hostTargetUuid) {
@@ -32,6 +37,18 @@ function withNotifyKitIosNseXcodeProject(config, nseOptions) {
32
37
  });
33
38
  }
34
39
 
40
+ function resolveNseVersionOptions(config) {
41
+ return {
42
+ marketingVersion: optionalString(config.version) ?? DEFAULT_NSE_MARKETING_VERSION,
43
+ currentProjectVersion:
44
+ optionalString(config.ios?.buildNumber) ?? DEFAULT_NSE_CURRENT_PROJECT_VERSION,
45
+ };
46
+ }
47
+
48
+ function optionalString(value) {
49
+ return typeof value === 'string' ? value : undefined;
50
+ }
51
+
35
52
  function requireExpoConfigPlugins() {
36
53
  try {
37
54
  return require('expo/config-plugins');
@@ -4,6 +4,8 @@ const NSE_TARGET_NAME_PATTERN = /^[A-Za-z0-9_\-.]+$/;
4
4
  const NSE_BUNDLE_SUFFIX_PATTERN = /^\.[A-Za-z0-9\-.]+$/;
5
5
 
6
6
  const PRODUCT_BUNDLE_IDENTIFIER_PLACEHOLDER = '$(PRODUCT_BUNDLE_IDENTIFIER:default)';
7
+ const DEFAULT_NSE_MARKETING_VERSION = '1.0';
8
+ const DEFAULT_NSE_CURRENT_PROJECT_VERSION = '1';
7
9
 
8
10
  const NOTIFICATION_SERVICE_SWIFT = String.raw`import Foundation
9
11
  import UserNotifications
@@ -124,9 +126,9 @@ const NSE_INFO_PLIST_TEMPLATE = `<?xml version="1.0" encoding="UTF-8"?>
124
126
  \t<key>CFBundlePackageType</key>
125
127
  \t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
126
128
  \t<key>CFBundleShortVersionString</key>
127
- \t<string>1.0</string>
129
+ \t<string>{{MARKETING_VERSION}}</string>
128
130
  \t<key>CFBundleVersion</key>
129
- \t<string>1</string>
131
+ \t<string>{{CURRENT_PROJECT_VERSION}}</string>
130
132
  \t<key>NSExtension</key>
131
133
  \t<dict>
132
134
  \t\t<key>NSExtensionPointIdentifier</key>
@@ -185,7 +187,13 @@ function renderNotificationServiceSwift() {
185
187
  }
186
188
 
187
189
  function renderNseInfoPlist(options) {
188
- return NSE_INFO_PLIST_TEMPLATE.replace(/\{\{TARGET_NAME\}\}/g, options.targetName);
190
+ const marketingVersion = options.marketingVersion ?? DEFAULT_NSE_MARKETING_VERSION;
191
+ const currentProjectVersion =
192
+ options.currentProjectVersion ?? DEFAULT_NSE_CURRENT_PROJECT_VERSION;
193
+
194
+ return NSE_INFO_PLIST_TEMPLATE.replace(/\{\{TARGET_NAME\}\}/g, options.targetName)
195
+ .replace(/\{\{MARKETING_VERSION\}\}/g, marketingVersion)
196
+ .replace(/\{\{CURRENT_PROJECT_VERSION\}\}/g, currentProjectVersion);
189
197
  }
190
198
 
191
199
  function renderNseEntitlementsPlist() {
@@ -211,6 +219,8 @@ function toRfc1034Identifier(value) {
211
219
  }
212
220
 
213
221
  module.exports = {
222
+ DEFAULT_NSE_CURRENT_PROJECT_VERSION,
223
+ DEFAULT_NSE_MARKETING_VERSION,
214
224
  deriveNseBundleIdentifier,
215
225
  renderNotificationServiceSwift,
216
226
  renderNseEntitlementsPlist,
@@ -12,19 +12,34 @@ function patchPodfileForNotifyKitNse(podfileText, options) {
12
12
  const placement = options.placement ?? 'nested';
13
13
  const useFrameworks = options.useFrameworks ?? false;
14
14
 
15
- if (!hasUncommentedTarget(patched, options.targetName)) {
16
- const withNseTarget = insertNseTarget(
15
+ const existingNseTarget = findUncommentedTargetBlock(patched, options.targetName);
16
+
17
+ if (!existingNseTarget) {
18
+ if (!hasUncommentedTarget(patched, options.targetName)) {
19
+ const withNseTarget = insertNseTarget(
20
+ patched,
21
+ options.targetName,
22
+ packagePathFromIos,
23
+ placement,
24
+ useFrameworks,
25
+ );
26
+ if (withNseTarget === null) {
27
+ return { contents: patched, didChange: changed };
28
+ }
29
+ patched = withNseTarget;
30
+ changed = true;
31
+ }
32
+ } else if (placement === 'topLevel') {
33
+ const withUpdatedNseTarget = updateTopLevelNseTargetBlock(
17
34
  patched,
18
- options.targetName,
35
+ existingNseTarget,
19
36
  packagePathFromIos,
20
- placement,
21
37
  useFrameworks,
22
38
  );
23
- if (withNseTarget === null) {
24
- return { contents: patched, didChange: changed };
39
+ if (withUpdatedNseTarget !== patched) {
40
+ patched = withUpdatedNseTarget;
41
+ changed = true;
25
42
  }
26
- patched = withNseTarget;
27
- changed = true;
28
43
  }
29
44
 
30
45
  const withRnfbPatch = ensureRnfbPostInstallPatch(patched);
@@ -66,7 +81,7 @@ function insertNseTarget(content, targetName, packagePathFromIos, placement, use
66
81
 
67
82
  function buildNseTargetBlock(targetName, packagePathFromIos, placement, useFrameworks) {
68
83
  if (placement === 'topLevel') {
69
- const useFrameworksLine = buildUseFrameworksLine(useFrameworks);
84
+ const useFrameworksLine = buildUseFrameworksLine(useFrameworks, ' ');
70
85
 
71
86
  return (
72
87
  `target '${targetName}' do\n` +
@@ -84,17 +99,57 @@ function buildNseTargetBlock(targetName, packagePathFromIos, placement, useFrame
84
99
  );
85
100
  }
86
101
 
87
- function buildUseFrameworksLine(useFrameworks) {
102
+ function updateTopLevelNseTargetBlock(content, targetBlock, packagePathFromIos, useFrameworks) {
103
+ const originalBlock = content.slice(targetBlock.startIndex, targetBlock.endIndex);
104
+ const hasTrailingNewline = originalBlock.endsWith('\n');
105
+ const blockWithoutTrailingNewline = hasTrailingNewline
106
+ ? originalBlock.slice(0, -1)
107
+ : originalBlock;
108
+ const lines = blockWithoutTrailingNewline.split('\n');
109
+
110
+ if (lines.length < 2) {
111
+ return content;
112
+ }
113
+
114
+ const targetLine = lines[0];
115
+ const endLine = lines[lines.length - 1];
116
+ const targetIndent = targetLine.match(/^[ \t]*/)?.[0] ?? '';
117
+ const bodyIndent = `${targetIndent} `;
118
+ const generatedLines = [
119
+ ...buildUseFrameworksLine(useFrameworks, bodyIndent).trimEnd().split('\n').filter(Boolean),
120
+ `${bodyIndent}pod 'RNNotifeeCore', :path => '${packagePathFromIos}'`,
121
+ ];
122
+ const preservedBodyLines = lines
123
+ .slice(1, -1)
124
+ .filter(line => !isUncommentedUseFrameworksLine(line))
125
+ .filter(line => !isUncommentedRnNotifeeCorePodLine(line))
126
+ .filter(line => !isUncommentedSearchPathInheritanceLine(line));
127
+ const updatedBlock =
128
+ [targetLine, ...generatedLines, ...preservedBodyLines, endLine].join('\n') +
129
+ (hasTrailingNewline ? '\n' : '');
130
+
131
+ if (updatedBlock === originalBlock) {
132
+ return content;
133
+ }
134
+
135
+ return (
136
+ content.slice(0, targetBlock.startIndex) +
137
+ updatedBlock +
138
+ content.slice(targetBlock.endIndex)
139
+ );
140
+ }
141
+
142
+ function buildUseFrameworksLine(useFrameworks, indent) {
88
143
  if (useFrameworks === 'static') {
89
- return ` use_frameworks! :linkage => :static\n`;
144
+ return `${indent}use_frameworks! :linkage => :static\n`;
90
145
  }
91
146
 
92
147
  if (useFrameworks === 'dynamic') {
93
- return ` use_frameworks! :linkage => :dynamic\n`;
148
+ return `${indent}use_frameworks! :linkage => :dynamic\n`;
94
149
  }
95
150
 
96
151
  if (useFrameworks === true) {
97
- return ` use_frameworks!\n`;
152
+ return `${indent}use_frameworks!\n`;
98
153
  }
99
154
 
100
155
  return '';
@@ -241,6 +296,45 @@ function stripRubyLineComment(line) {
241
296
  return line.replace(/#.*$/, '');
242
297
  }
243
298
 
299
+ function findUncommentedTargetBlock(content, targetName) {
300
+ const pattern = new RegExp(
301
+ `^[ \\t]*target\\s+['"]${escapeRegex(targetName)}['"]\\s+do\\b.*$`,
302
+ 'gm',
303
+ );
304
+ let match;
305
+
306
+ while ((match = pattern.exec(content)) !== null) {
307
+ const line = match[0];
308
+ if (stripRubyLineComment(line).trim().length === 0) {
309
+ continue;
310
+ }
311
+
312
+ const endLineStart = findMatchingRubyBlockEnd(content, match.index);
313
+ if (endLineStart === -1) {
314
+ return null;
315
+ }
316
+
317
+ return {
318
+ startIndex: match.index,
319
+ endIndex: findLineEnd(content, endLineStart),
320
+ };
321
+ }
322
+
323
+ return null;
324
+ }
325
+
326
+ function isUncommentedUseFrameworksLine(line) {
327
+ return /^use_frameworks!(?:\s|$)/.test(stripRubyLineComment(line).trim());
328
+ }
329
+
330
+ function isUncommentedRnNotifeeCorePodLine(line) {
331
+ return /^pod\s+['"]RNNotifeeCore['"](?:\s|,|$)/.test(stripRubyLineComment(line).trim());
332
+ }
333
+
334
+ function isUncommentedSearchPathInheritanceLine(line) {
335
+ return /^inherit!\s+:search_paths\b/.test(stripRubyLineComment(line).trim());
336
+ }
337
+
244
338
  function hasUncommentedTarget(content, targetName) {
245
339
  const pattern = new RegExp(`target\\s+['"]${escapeRegex(targetName)}['"]`);
246
340
  for (const line of content.split('\n')) {
@@ -1,13 +1,44 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
+ const {
5
+ DEFAULT_NSE_CURRENT_PROJECT_VERSION,
6
+ DEFAULT_NSE_MARKETING_VERSION,
7
+ } = require('./initNseCore');
4
8
 
5
9
  function patchXcodeProjectForNotifyKitNse(proj, options) {
6
- const { targetName, bundleIdentifier, parentTargetName, deploymentTarget = '15.1' } = options;
10
+ const {
11
+ targetName,
12
+ bundleIdentifier,
13
+ parentTargetName,
14
+ deploymentTarget = '15.1',
15
+ marketingVersion = DEFAULT_NSE_MARKETING_VERSION,
16
+ currentProjectVersion = DEFAULT_NSE_CURRENT_PROJECT_VERSION,
17
+ } = options;
7
18
  const warnings = [];
19
+ const existingTargetUuid = findTargetByName(proj, targetName);
20
+
21
+ if (existingTargetUuid) {
22
+ const didChange = setBuildSettings(
23
+ proj,
24
+ existingTargetUuid,
25
+ targetName,
26
+ bundleIdentifier,
27
+ deploymentTarget,
28
+ marketingVersion,
29
+ currentProjectVersion,
30
+ );
31
+
32
+ if (!didChange) {
33
+ return { didChange: false, warnings };
34
+ }
8
35
 
9
- if (targetExists(proj, targetName)) {
10
- return { didChange: false, warnings };
36
+ return {
37
+ didChange: true,
38
+ targetUuid: existingTargetUuid,
39
+ hostTargetUuid: findHostTarget(proj, parentTargetName) ?? undefined,
40
+ warnings,
41
+ };
11
42
  }
12
43
 
13
44
  const target = proj.addTarget(targetName, 'app_extension', targetName);
@@ -40,7 +71,15 @@ function patchXcodeProjectForNotifyKitNse(proj, options) {
40
71
  stripRnfbInfoPlistInputPath(proj, hostUuid);
41
72
  }
42
73
 
43
- setBuildSettings(proj, targetName, bundleIdentifier, deploymentTarget);
74
+ setBuildSettings(
75
+ proj,
76
+ targetUuid,
77
+ targetName,
78
+ bundleIdentifier,
79
+ deploymentTarget,
80
+ marketingVersion,
81
+ currentProjectVersion,
82
+ );
44
83
 
45
84
  return {
46
85
  didChange: true,
@@ -81,34 +120,101 @@ function findTargetByName(proj, name) {
81
120
  return null;
82
121
  }
83
122
 
84
- function targetExists(proj, name) {
85
- return findTargetByName(proj, name) !== null;
86
- }
87
-
88
123
  function getTargetProductUuid(target) {
89
124
  const productReference = target.pbxNativeTarget && target.pbxNativeTarget.productReference;
90
125
  return typeof productReference === 'string' ? productReference : undefined;
91
126
  }
92
127
 
93
- function setBuildSettings(proj, targetName, bundleId, deploymentTarget) {
128
+ function setBuildSettings(
129
+ proj,
130
+ targetUuid,
131
+ targetName,
132
+ bundleId,
133
+ deploymentTarget,
134
+ marketingVersion,
135
+ currentProjectVersion,
136
+ ) {
137
+ let didChange = false;
138
+
139
+ for (const settings of getTargetBuildSettings(proj, targetUuid, targetName)) {
140
+ didChange =
141
+ setBuildSetting(settings, 'INFOPLIST_FILE', `"${targetName}/Info.plist"`) || didChange;
142
+ didChange =
143
+ setBuildSetting(settings, 'PRODUCT_BUNDLE_IDENTIFIER', `"${bundleId}"`) || didChange;
144
+ didChange = setBuildSetting(settings, 'TARGETED_DEVICE_FAMILY', `"1,2"`) || didChange;
145
+ didChange =
146
+ setBuildSetting(settings, 'IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget) || didChange;
147
+ didChange = setBuildSetting(settings, 'MARKETING_VERSION', marketingVersion) || didChange;
148
+ didChange =
149
+ setBuildSetting(settings, 'CURRENT_PROJECT_VERSION', currentProjectVersion) || didChange;
150
+ didChange = setBuildSetting(settings, 'SWIFT_VERSION', '5.0') || didChange;
151
+ didChange =
152
+ setBuildSetting(
153
+ settings,
154
+ 'CODE_SIGN_ENTITLEMENTS',
155
+ `"${targetName}/${targetName}.entitlements"`,
156
+ ) || didChange;
157
+ didChange = setBuildSetting(settings, 'GENERATE_INFOPLIST_FILE', 'NO') || didChange;
158
+ }
159
+
160
+ return didChange;
161
+ }
162
+
163
+ function getTargetBuildSettings(proj, targetUuid, targetName) {
164
+ const target = proj.pbxNativeTargetSection()[targetUuid];
165
+ const buildConfigurationListUuid = target && target.buildConfigurationList;
94
166
  const configs = proj.pbxXCBuildConfigurationSection();
167
+ const settingsByConfigurationList = getBuildSettingsFromConfigurationList(
168
+ proj,
169
+ typeof buildConfigurationListUuid === 'string' ? buildConfigurationListUuid : undefined,
170
+ configs,
171
+ );
95
172
 
96
- for (const [, value] of Object.entries(configs)) {
97
- if (typeof value !== 'object') continue;
98
- const config = value;
99
- const settings = config.buildSettings;
100
-
101
- if (!settings) continue;
102
- if (settings.PRODUCT_NAME !== `"${targetName}"`) continue;
103
-
104
- settings.INFOPLIST_FILE = `"${targetName}/Info.plist"`;
105
- settings.PRODUCT_BUNDLE_IDENTIFIER = `"${bundleId}"`;
106
- settings.TARGETED_DEVICE_FAMILY = `"1,2"`;
107
- settings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
108
- settings.SWIFT_VERSION = '5.0';
109
- settings.CODE_SIGN_ENTITLEMENTS = `"${targetName}/${targetName}.entitlements"`;
110
- settings.GENERATE_INFOPLIST_FILE = 'NO';
173
+ if (settingsByConfigurationList.length > 0) {
174
+ return settingsByConfigurationList;
175
+ }
176
+
177
+ return Object.entries(configs)
178
+ .filter(([, value]) => typeof value === 'object' && value !== null)
179
+ .map(([, value]) => value)
180
+ .filter(config => {
181
+ const settings = config.buildSettings;
182
+ return settings?.PRODUCT_NAME === `"${targetName}"`;
183
+ })
184
+ .map(config => config.buildSettings);
185
+ }
186
+
187
+ function getBuildSettingsFromConfigurationList(proj, buildConfigurationListUuid, configs) {
188
+ if (!buildConfigurationListUuid) {
189
+ return [];
111
190
  }
191
+
192
+ const configurationLists = proj.hash.project.objects.XCConfigurationList;
193
+ const configurationList = configurationLists && configurationLists[buildConfigurationListUuid];
194
+ const buildConfigurations = Array.isArray(configurationList?.buildConfigurations)
195
+ ? configurationList.buildConfigurations
196
+ : [];
197
+
198
+ return buildConfigurations
199
+ .map(ref => ref?.value)
200
+ .filter(uuid => typeof uuid === 'string')
201
+ .map(uuid => configs[uuid])
202
+ .filter(config => typeof config === 'object' && config !== null)
203
+ .map(config => {
204
+ if (typeof config.buildSettings !== 'object' || config.buildSettings === null) {
205
+ config.buildSettings = {};
206
+ }
207
+ return config.buildSettings;
208
+ });
209
+ }
210
+
211
+ function setBuildSetting(settings, key, value) {
212
+ if (settings[key] === value) {
213
+ return false;
214
+ }
215
+
216
+ settings[key] = value;
217
+ return true;
112
218
  }
113
219
 
114
220
  function fixProductFileReference(proj, targetName) {
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '10.4.0';
2
+ export const version = '10.4.2';