react-native-notify-kit 10.9.1 → 10.10.1

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.
@@ -1,52 +1,54 @@
1
1
  'use strict';
2
-
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.withNotifyKitIosNsePodfile = withNotifyKitIosNsePodfile;
4
+ exports.resolveNotifyKitPackagePathFromIos = resolveNotifyKitPackagePathFromIos;
5
+ exports.normalizePodfilePath = normalizePodfilePath;
6
+ exports.detectPodfileUseFrameworks = detectPodfileUseFrameworks;
3
7
  const fs = require('fs');
4
8
  const path = require('path');
5
-
6
- const { patchPodfileForNotifyKitNse } = require('../shared/nse/patchPodfile');
7
-
9
+ const patchPodfile_1 = require('../shared/nse/patchPodfile');
8
10
  function withNotifyKitIosNsePodfile(config, nseOptions) {
9
11
  if (!nseOptions.enabled) {
10
12
  return config;
11
13
  }
12
-
13
14
  const { withPodfile } = requireExpoConfigPlugins();
14
15
  const configuredUseFrameworks = detectConfiguredExpoBuildPropertiesUseFrameworks(config);
15
-
16
16
  return withPodfile(config, modConfig => {
17
17
  const { projectRoot, platformProjectRoot } = modConfig.modRequest;
18
18
  const packagePathFromIos = resolveNotifyKitPackagePathFromIos(projectRoot, platformProjectRoot);
19
+ // Podfile mods can run before expo-build-properties writes its properties
20
+ // mod. Read the configuration that the host will consume at pod install,
21
+ // rather than a missing/stale file left by a clean/incremental prebuild.
22
+ const podfileProperties = readExpoPodfileProperties(platformProjectRoot);
23
+ if (configuredUseFrameworks === 'static' || configuredUseFrameworks === 'dynamic') {
24
+ podfileProperties['ios.useFrameworks'] = configuredUseFrameworks;
25
+ }
19
26
  const hostUseFrameworks = detectPodfileUseFrameworks(modConfig.modResults.contents, {
20
27
  ignoredTargetName: nseOptions.targetName,
21
- podfileProperties: readExpoPodfileProperties(platformProjectRoot),
28
+ podfileProperties,
22
29
  env: process.env,
23
30
  });
24
31
  const useFrameworks = resolveNseUseFrameworks(hostUseFrameworks, configuredUseFrameworks);
25
- const result = patchPodfileForNotifyKitNse(modConfig.modResults.contents, {
32
+ const result = (0, patchPodfile_1.patchPodfileForNotifyKitNse)(modConfig.modResults.contents, {
26
33
  targetName: nseOptions.targetName,
27
34
  packagePathFromIos,
28
35
  placement: 'topLevel',
29
36
  useFrameworks,
30
37
  });
31
-
32
38
  modConfig.modResults.contents = result.contents;
33
39
  return modConfig;
34
40
  });
35
41
  }
36
-
37
42
  function resolveNotifyKitPackagePathFromIos(projectRoot, platformProjectRoot) {
38
43
  const packageJsonPath = require.resolve('react-native-notify-kit/package.json', {
39
44
  paths: [projectRoot],
40
45
  });
41
46
  const packageDir = path.dirname(packageJsonPath);
42
-
43
47
  return normalizePodfilePath(path.relative(platformProjectRoot, packageDir));
44
48
  }
45
-
46
49
  function normalizePodfilePath(filePath) {
47
50
  return filePath.replace(/\\/g, '/');
48
51
  }
49
-
50
52
  function detectPodfileUseFrameworks(contents, optionsOrIgnoredTargetName) {
51
53
  const options =
52
54
  typeof optionsOrIgnoredTargetName === 'string'
@@ -59,30 +61,24 @@ function detectPodfileUseFrameworks(contents, optionsOrIgnoredTargetName) {
59
61
  let globalUseFrameworks = false;
60
62
  let hostUseFrameworks = false;
61
63
  let charIndex = 0;
62
-
63
64
  for (const line of contents.split('\n')) {
64
65
  const detectedUseFrameworks = parseUseFrameworksLine(line, options);
65
66
  if (detectedUseFrameworks === null) {
66
67
  charIndex += line.length + 1;
67
68
  continue;
68
69
  }
69
-
70
70
  const targetBlock = findMostSpecificTargetBlock(targetBlocks, charIndex);
71
71
  if (!targetBlock) {
72
72
  globalUseFrameworks = mergeUseFrameworksDetection(globalUseFrameworks, detectedUseFrameworks);
73
73
  } else if (targetBlock === firstHostTarget) {
74
74
  hostUseFrameworks = mergeUseFrameworksDetection(hostUseFrameworks, detectedUseFrameworks);
75
75
  }
76
-
77
76
  charIndex += line.length + 1;
78
77
  }
79
-
80
78
  return hostUseFrameworks !== false ? hostUseFrameworks : globalUseFrameworks;
81
79
  }
82
-
83
80
  function readExpoPodfileProperties(platformProjectRoot) {
84
81
  const propertiesPath = path.join(platformProjectRoot, 'Podfile.properties.json');
85
-
86
82
  try {
87
83
  const parsed = JSON.parse(fs.readFileSync(propertiesPath, 'utf8'));
88
84
  return isPlainObject(parsed) ? parsed : {};
@@ -90,240 +86,186 @@ function readExpoPodfileProperties(platformProjectRoot) {
90
86
  if (isFileNotFoundError(error)) {
91
87
  return {};
92
88
  }
93
-
94
89
  throw error;
95
90
  }
96
91
  }
97
-
98
92
  function isFileNotFoundError(error) {
99
93
  return isPlainObject(error) && error.code === 'ENOENT';
100
94
  }
101
-
102
95
  function resolveNseUseFrameworks(hostUseFrameworks, configuredUseFrameworks) {
103
96
  if (hostUseFrameworks === false) {
104
97
  return false;
105
98
  }
106
-
107
99
  if (hostUseFrameworks === true && configuredUseFrameworks !== false) {
108
100
  return configuredUseFrameworks;
109
101
  }
110
-
111
102
  return hostUseFrameworks;
112
103
  }
113
-
114
104
  function detectConfiguredExpoBuildPropertiesUseFrameworks(config) {
115
105
  const plugins = Array.isArray(config.plugins) ? config.plugins : [];
116
106
  let detected = false;
117
-
118
107
  for (const plugin of plugins) {
119
108
  if (!Array.isArray(plugin) || plugin[0] !== 'expo-build-properties') {
120
109
  continue;
121
110
  }
122
-
123
111
  const options = isPlainObject(plugin[1]) ? plugin[1] : null;
124
112
  const ios = isPlainObject(options?.ios) ? options.ios : null;
125
113
  const useFrameworks = ios?.useFrameworks;
126
-
127
114
  if (useFrameworks === 'static' || useFrameworks === 'dynamic') {
128
115
  detected = useFrameworks;
129
116
  } else if (useFrameworks === true || useFrameworks === false) {
130
117
  detected = useFrameworks;
131
118
  }
132
119
  }
133
-
134
120
  return detected;
135
121
  }
136
-
137
122
  function isPlainObject(value) {
138
123
  return typeof value === 'object' && value !== null && !Array.isArray(value);
139
124
  }
140
-
141
125
  function findTargetBlocks(contents) {
142
126
  const targetPattern = /^[ \t]*target\s+['"]([^'"]+)['"]\s+do\b.*$/gm;
143
127
  const blocks = [];
144
128
  let match;
145
-
146
129
  while ((match = targetPattern.exec(contents)) !== null) {
147
130
  const endLineStart = findMatchingRubyBlockEnd(contents, match.index);
148
131
  if (endLineStart === -1) {
149
132
  continue;
150
133
  }
151
-
152
134
  blocks.push({
153
135
  targetName: match[1],
154
136
  startIndex: match.index,
155
137
  endIndex: findLineEnd(contents, endLineStart),
156
138
  });
157
139
  }
158
-
159
140
  return blocks;
160
141
  }
161
-
162
142
  function findMostSpecificTargetBlock(targetBlocks, charIndex) {
163
143
  return targetBlocks
164
144
  .filter(block => charIndex >= block.startIndex && charIndex < block.endIndex)
165
145
  .sort((a, b) => b.startIndex - a.startIndex)[0];
166
146
  }
167
-
168
147
  function parseUseFrameworksLine(line, options) {
169
148
  const stripped = stripRubyLineComment(line).trim();
170
149
  if (!/^use_frameworks!(?:\s|$)/.test(stripped)) {
171
150
  return null;
172
151
  }
173
-
174
152
  if (!isUseFrameworksConditionActive(stripped, options)) {
175
153
  return null;
176
154
  }
177
-
178
155
  const podfilePropertyLinkage = getPodfilePropertyUseFrameworksLinkage(stripped, options);
179
156
  if (podfilePropertyLinkage !== null) {
180
157
  return podfilePropertyLinkage;
181
158
  }
182
-
183
159
  const envLinkage = getEnvUseFrameworksLinkage(stripped, options);
184
160
  if (envLinkage !== null) {
185
161
  return envLinkage;
186
162
  }
187
-
188
163
  if (/:linkage\s*=>\s*:static\b/.test(stripped) || /\blinkage:\s*:static\b/.test(stripped)) {
189
164
  return 'static';
190
165
  }
191
-
192
166
  if (/:linkage\s*=>\s*:dynamic\b/.test(stripped) || /\blinkage:\s*:dynamic\b/.test(stripped)) {
193
167
  return 'dynamic';
194
168
  }
195
-
196
169
  return true;
197
170
  }
198
-
199
171
  function mergeUseFrameworksDetection(current, detected) {
200
172
  return detected;
201
173
  }
202
-
203
174
  function isUseFrameworksConditionActive(strippedLine, options) {
204
175
  const condition = strippedLine.match(/\s+if\s+(.+)$/)?.[1]?.trim();
205
176
  if (!condition) {
206
177
  return true;
207
178
  }
208
-
209
179
  const podfilePropertyName = getPodfilePropertyName(condition);
210
180
  if (podfilePropertyName !== null) {
211
181
  return isRubyTruthy(options.podfileProperties?.[podfilePropertyName]);
212
182
  }
213
-
214
183
  const envName = getEnvName(condition);
215
184
  if (envName !== null) {
216
185
  return isRubyTruthy(options.env?.[envName]);
217
186
  }
218
-
219
187
  return true;
220
188
  }
221
-
222
189
  function getPodfilePropertyUseFrameworksLinkage(strippedLine, options) {
223
190
  const propertyName = strippedLine.match(
224
191
  /(?:\:linkage\s*=>|linkage:)\s*podfile_properties\[['"]([^'"]+)['"]\](?:\.to_sym)?/,
225
192
  )?.[1];
226
-
227
193
  return propertyName
228
194
  ? normalizeUseFrameworksValue(options.podfileProperties?.[propertyName])
229
195
  : null;
230
196
  }
231
-
232
197
  function getEnvUseFrameworksLinkage(strippedLine, options) {
233
198
  const envName = strippedLine.match(
234
199
  /(?:\:linkage\s*=>|linkage:)\s*ENV\[['"]([^'"]+)['"]\](?:\.to_sym)?/,
235
200
  )?.[1];
236
-
237
201
  return envName ? normalizeUseFrameworksValue(options.env?.[envName]) : null;
238
202
  }
239
-
240
203
  function normalizeUseFrameworksValue(value) {
241
204
  if (value === 'static' || value === 'dynamic' || value === true) {
242
205
  return value;
243
206
  }
244
-
245
207
  return null;
246
208
  }
247
-
248
209
  function getPodfilePropertyName(expression) {
249
210
  return expression.match(/^podfile_properties\[['"]([^'"]+)['"]\]$/)?.[1] ?? null;
250
211
  }
251
-
252
212
  function getEnvName(expression) {
253
213
  return expression.match(/^ENV\[['"]([^'"]+)['"]\]$/)?.[1] ?? null;
254
214
  }
255
-
256
215
  function isRubyTruthy(value) {
257
216
  return value !== undefined && value !== null && value !== false;
258
217
  }
259
-
260
218
  function findLineEnd(content, lineStartIndex) {
261
219
  const newlineIndex = content.indexOf('\n', lineStartIndex);
262
220
  return newlineIndex === -1 ? content.length : newlineIndex + 1;
263
221
  }
264
-
265
222
  function findMatchingRubyBlockEnd(content, startIndex) {
266
223
  const afterStart = content.slice(startIndex);
267
224
  const lines = afterStart.split('\n');
268
225
  let depth = 0;
269
226
  let charIndex = startIndex;
270
-
271
227
  for (const line of lines) {
272
228
  const trimmed = stripRubyLineComment(line).trim();
273
-
274
229
  depth += countRubyBlockOpeners(trimmed);
275
-
276
230
  if (trimmed === 'end') {
277
231
  depth--;
278
232
  if (depth === 0) {
279
233
  return charIndex;
280
234
  }
281
235
  }
282
-
283
236
  charIndex += line.length + 1;
284
237
  }
285
-
286
238
  return -1;
287
239
  }
288
-
289
240
  function countRubyBlockOpeners(line) {
290
241
  if (line.length === 0) {
291
242
  return 0;
292
243
  }
293
-
294
244
  const startsWithKeywordBlock = /^(if|unless|case|begin|while|until|for|def|class|module)\b/.test(
295
245
  line,
296
246
  );
297
247
  if (startsWithKeywordBlock) {
298
248
  return 1;
299
249
  }
300
-
301
250
  if (/\bdo\b(\s*\|[^|]*\|)?\s*$/.test(line)) {
302
251
  return 1;
303
252
  }
304
-
305
253
  return 0;
306
254
  }
307
-
308
255
  function stripRubyLineComment(line) {
309
256
  return line.replace(/#.*$/, '');
310
257
  }
311
-
312
258
  function requireExpoConfigPlugins() {
313
259
  try {
314
260
  return require('expo/config-plugins');
315
261
  } catch (error) {
316
262
  try {
317
- return require(require.resolve('expo/config-plugins', { paths: [process.cwd()] }));
263
+ const expoConfigPluginsPath = require.resolve('expo/config-plugins', {
264
+ paths: [process.cwd()],
265
+ });
266
+ return require(expoConfigPluginsPath);
318
267
  } catch {
319
268
  throw error;
320
269
  }
321
270
  }
322
271
  }
323
-
324
- module.exports = {
325
- withNotifyKitIosNsePodfile,
326
- resolveNotifyKitPackagePathFromIos,
327
- normalizePodfilePath,
328
- detectPodfileUseFrameworks,
329
- };
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.ensureNotifeeExtensionFlag = ensureNotifeeExtensionFlag;
4
+ const child_process_1 = require('child_process');
5
+ // Keep the Expo/CLI copies identical: each is shipped through a separate build.
6
+ // Parse, never execute, consumer Ruby. A line scanner cannot distinguish a
7
+ // top-level assignment from continuations, interpolation, heredocs or blocks.
8
+ const FLAG_PREFLIGHT = String.raw`
9
+ require 'ripper'
10
+ require 'json'
11
+ source = STDIN.read
12
+ ast = Ripper.sexp(source)
13
+ def walk(node, &block)
14
+ return unless node.is_a?(Array)
15
+ yield node
16
+ node.each { |child| walk(child, &block) if child.is_a?(Array) }
17
+ end
18
+ def flag_tokens(node)
19
+ tokens = []
20
+ walk(node) { |part| tokens << part if part[0] == :@gvar && part[1] == '$NotifeeExtension' }
21
+ tokens
22
+ end
23
+ def evaluation?(node)
24
+ found = false
25
+ walk(node) do |part|
26
+ if [:@ident, :@const].include?(part[0]) &&
27
+ %w[target abstract_target pod podspec use_native_modules! use_expo_modules! use_react_native! Specification eval instance_eval class_eval module_eval from_file from_string].include?(part[1])
28
+ found = true
29
+ end
30
+ found = true if part[0] == :@tstring_content && part[1].include?('.podspec')
31
+ end
32
+ found
33
+ end
34
+ # Only a bounded prelude is provably safe before an existing true flag. Unknown
35
+ # helpers and dynamic dispatch can evaluate podspecs even without a named call.
36
+ def literal?(node)
37
+ return true if node.nil? || node == false
38
+ return false unless node.is_a?(Array)
39
+ return node.all? { |part| literal?(part) } unless node[0].is_a?(Symbol)
40
+ case node[0]
41
+ when :@int, :@float, :@rational, :@imaginary, :@tstring_content, :@label, :@ident
42
+ true
43
+ when :@kw
44
+ %w[true false nil].include?(node[1])
45
+ when :var_ref, :string_literal, :string_content, :symbol_literal, :symbol,
46
+ :array, :hash, :assoclist_from_args, :assoc_new, :args_add_block, :arg_paren
47
+ node.drop(1).all? { |part| literal?(part) }
48
+ else
49
+ false
50
+ end
51
+ end
52
+ def safe_prelude?(node)
53
+ return true if node[0] == :void_stmt
54
+ if node[0] == :assign
55
+ lhs = node[1]
56
+ return lhs && lhs[0] == :var_field &&
57
+ [:@ident, :@gvar, :@ivar, :@cvar, :@const].include?(lhs[1][0]) && literal?(node[2])
58
+ end
59
+ allowed = %w[require require_relative platform source project workspace install! use_frameworks!]
60
+ if node[0] == :command
61
+ return allowed.include?(node[1][1]) && literal?(node[2])
62
+ end
63
+ if node[0] == :method_add_arg && node[1][0] == :fcall
64
+ return allowed.include?(node[1][1][1]) && literal?(node[2])
65
+ end
66
+ false
67
+ end
68
+ unless ast
69
+ puts JSON.generate({ error: 'Ruby syntax could not be parsed', line: 1 })
70
+ exit
71
+ end
72
+ has_eval = false
73
+ flag_text = nil
74
+ walk(ast) do |part|
75
+ has_eval = true if part[0] == :@ident && %w[eval instance_eval class_eval module_eval].include?(part[1])
76
+ flag_text ||= part if part[0] == :@tstring_content && part[1].include?('$NotifeeExtension')
77
+ end
78
+ if has_eval && flag_text
79
+ puts JSON.generate({ error: 'dynamic Ruby evaluation mentions the flag', line: flag_text[2][0] })
80
+ exit
81
+ end
82
+ all = flag_tokens(ast)
83
+ accepted = []
84
+ evaluating = false
85
+ error = nil
86
+ (ast[1] || []).each do |statement|
87
+ tokens = flag_tokens(statement)
88
+ unless tokens.empty?
89
+ lhs = statement[1] if statement[0] == :assign
90
+ rhs = statement[2] if statement[0] == :assign
91
+ literal_true = lhs && lhs[0] == :var_field && lhs[1][0] == :@gvar &&
92
+ lhs[1][1] == '$NotifeeExtension' && rhs && rhs[0] == :var_ref && rhs[1][0] == :@kw && rhs[1][1] == 'true'
93
+ if !literal_true || tokens.length != 1
94
+ error = { error: 'conflicting or ambiguous configuration; only unconditional literal true is supported', line: tokens[0][2][0] }
95
+ break
96
+ end
97
+ if evaluating && accepted.empty?
98
+ error = { error: 'assignment follows possible target/autolinking or podspec evaluation', line: tokens[0][2][0] }
99
+ break
100
+ end
101
+ accepted.concat(tokens)
102
+ end
103
+ evaluating ||= evaluation?(statement) || !safe_prelude?(statement)
104
+ end
105
+ puts JSON.generate(error || { assigned: !accepted.empty?, references: all.length })
106
+ `;
107
+ function ensureNotifeeExtensionFlag(contents) {
108
+ if (contents.includes('$NotifeeExtension')) {
109
+ let result;
110
+ try {
111
+ result = JSON.parse(
112
+ (0, child_process_1.execFileSync)('ruby', ['-e', FLAG_PREFLIGHT], {
113
+ input: contents,
114
+ encoding: 'utf8',
115
+ timeout: 10_000,
116
+ maxBuffer: 16 * 1024 * 1024,
117
+ }),
118
+ );
119
+ } catch {
120
+ throw new Error(
121
+ 'NotifyKit NSE: $NotifeeExtension at line 1: Ruby (Ripper) preflight unavailable. ' +
122
+ 'Use the Ruby toolchain required by CocoaPods; no changes were written.',
123
+ );
124
+ }
125
+ if (result.error) {
126
+ throw new Error(
127
+ `NotifyKit NSE: $NotifeeExtension at line ${result.line}: ${result.error}. ` +
128
+ 'Set $NotifeeExtension = true in the Podfile header before autolinking; no changes were written.',
129
+ );
130
+ }
131
+ if (result.assigned) return contents;
132
+ }
133
+ // Insert after Ruby's shebang, magic comments and other leading comments.
134
+ // Keep every original byte, including a BOM and a header without final LF.
135
+ const bom = contents.startsWith('\uFEFF') ? '\uFEFF' : '';
136
+ const text = contents.slice(bom.length);
137
+ const lines = text.split('\n');
138
+ let headerEnd = 0;
139
+ let blockComment = false;
140
+ for (let index = 0; index < lines.length; index++) {
141
+ const line = lines[index];
142
+ if (/^=begin(?:\s|$)/.test(line)) blockComment = true;
143
+ if (!blockComment && !/^\s*(?:#.*)?$/.test(line)) break;
144
+ headerEnd += line.length + (index < lines.length - 1 ? 1 : 0);
145
+ if (/^=end(?:\s|$)/.test(line)) blockComment = false;
146
+ }
147
+ const separator = headerEnd > 0 && text[headerEnd - 1] !== '\n' ? '\n' : '';
148
+ return (
149
+ bom +
150
+ text.slice(0, headerEnd) +
151
+ separator +
152
+ '$NotifeeExtension = true\n' +
153
+ text.slice(headerEnd)
154
+ );
155
+ }