nativekeel 0.1.19 → 0.1.20

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/README.md CHANGED
@@ -6,7 +6,7 @@ Health check for React Native and Expo apps. One command, no install, no account
6
6
  npx nativekeel
7
7
  ```
8
8
 
9
- **What's new in 0.1.19:** better plans for apps that are far behind: library targets that fit the React Native you have, not the latest. **0.1.18:** the upgrade plan only suggests library versions your current React Native supports, moves React Navigation and RN Firebase packages together, and says plainly when a vulnerable package has no fixed release. Recent releases added a *Start here* list in every report, hacking risks mapped to OWASP MASVS, crash and performance checks. Every check is tuned on 450+ open-source React Native and Expo apps to keep false alarms out. [All releases →](https://nativekeel.com/changelog)
9
+ **What's new in 0.1.20:** warns about `ImageBackground`, `SafeAreaView`, `DrawerLayoutAndroid` and `UTFSequence`, which React Native has deprecated and will remove (143 of 470 apps we test import one). **0.1.19:** better plans for apps that are far behind: library targets that fit the React Native you have, not the latest. Recent releases added a *Start here* list in every report, hacking risks mapped to OWASP MASVS, crash and performance checks. Every check is tuned on 450+ open-source React Native and Expo apps to keep false alarms out. [All releases →](https://nativekeel.com/changelog)
10
10
 
11
11
  ```
12
12
  Summary 4 critical · 5 high · 4 medium · 6 low
@@ -30,7 +30,7 @@ Start here
30
30
  - **Will Google Play and the App Store accept your next build?** `targetSdk`, 16 KB memory pages (it also opens your built APK/AAB and checks every native library), the iOS privacy manifest, and missing Info.plist usage descriptions.
31
31
 
32
32
  **Crashes**
33
- - **Will it crash at runtime?** Reanimated without its Babel plugin or `react-native-worklets`, mixed `@react-native-firebase/*` or `@react-navigation/*` majors, a second copy of React or React Native inside a dependency, Kotlin modules that cannot load through the New Architecture interop layer, AppDelegate setups that crash in Release, camera/photos/location access without the Info.plist text iOS requires, imports of APIs removed from React Native core (`AsyncStorage`, `Picker`, `ViewPropTypes`, …), and traps learned from real upgrades.
33
+ - **Will it crash at runtime?** Reanimated without its Babel plugin or `react-native-worklets`, mixed `@react-native-firebase/*` or `@react-navigation/*` majors, a second copy of React or React Native inside a dependency, Kotlin modules that cannot load through the New Architecture interop layer, AppDelegate setups that crash in Release, camera/photos/location access without the Info.plist text iOS requires, imports of APIs removed from React Native core (`AsyncStorage`, `Picker`, `ViewPropTypes`, …) or deprecated for removal (`ImageBackground`, `SafeAreaView`), and traps learned from real upgrades.
34
34
 
35
35
  **Security, grouped by OWASP MASVS**
36
36
  - **Secrets in the app or the repo:** AWS, Stripe, OpenAI, Anthropic, GitHub, Slack, SendGrid, Twilio, Shopify, Google OAuth and Supabase `service_role` keys, private keys, `.env` values compiled into the bundle, Expo `extra`/`EXPO_PUBLIC_`/`eas.json` secrets, signing keys and passwords.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nativekeel",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Health check for React Native and Expo apps: upgrade blockers, store deadlines, crash risks, vulnerable dependencies and leaked secrets, with a step-by-step upgrade plan.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/analyze.js CHANGED
@@ -218,7 +218,7 @@ export async function analyze(project, { get, now = new Date(), offline: forcedO
218
218
  severity: 'critical',
219
219
  area: 'store',
220
220
  title: `targetSdk ${target}: Google Play will reject your next update`,
221
- detail: `Since ${updates.since} new apps and updates must target API ${updates.minimum}+ (extension possible until ${updates.extensionUntil}).`,
221
+ detail: `Since ${updates.since} new apps and updates must target API ${updates.minimum}+${today <= updates.extensionUntil ? ` (an extension you request in Play Console runs until ${updates.extensionUntil} at the latest)` : '; the extension period has ended'}.`,
222
222
  fix: { kind: 'target-sdk', from: target, to: updates.minimum },
223
223
  });
224
224
  }
package/src/expo-pins.js CHANGED
@@ -11,6 +11,7 @@ const EXPO_PINNED = new Set([
11
11
  '@react-native-async-storage/async-storage', '@react-native-community/datetimepicker', '@react-native-community/slider',
12
12
  '@react-native-community/netinfo', '@react-native-picker/picker', '@react-native-masked-view/masked-view',
13
13
  'lottie-react-native', '@shopify/flash-list', '@shopify/react-native-skia', '@stripe/stripe-react-native',
14
+ '@react-native-segmented-control/segmented-control', '@sentry/react-native', 'react-native-bootsplash',
14
15
  ]);
15
16
 
16
17
  export function expoPinnedNames(root) {
package/src/plan.js CHANGED
@@ -315,6 +315,7 @@ export function buildPlan(result) {
315
315
  for (const p of byKind('patch-version')) {
316
316
  cleanup.push(`\`patches/${p.file}\` targets ${p.pkg} ${p.version} but ${p.installed} is installed: check whether the fix is still needed, then recreate or delete it.`);
317
317
  }
318
+ for (const d of byKind('deprecated-core')) cleanup.push(d.step);
318
319
  if (byKind('podfile-cli-require').length) {
319
320
  cleanup.push("Delete `require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'` from `ios/Podfile`; `use_native_modules!` comes from React Native.");
320
321
  }
package/src/stability.js CHANGED
@@ -277,10 +277,19 @@ const REMOVED_FROM_CORE = {
277
277
  ART: 'react-native-svg',
278
278
  };
279
279
  const REMOVED_PROP_TYPES = ['ViewPropTypes', 'ColorPropType', 'EdgeInsetsPropType', 'PointPropType'];
280
+ // Deprecated in core and announced for removal (RN index.js warnOnce, checked 0.78-0.87).
281
+ // They still work; moving now avoids a crash when they are removed.
282
+ const DEPRECATED_IN_CORE = {
283
+ SafeAreaView: { since: 81, use: 'react-native-safe-area-context' },
284
+ ImageBackground: { since: 87, use: 'a View with an absolutely positioned Image' },
285
+ DrawerLayoutAndroid: { since: 87, use: 'react-native-drawer-layout' },
286
+ UTFSequence: { since: 87, use: "Unicode escapes written directly (e.g. '\\ufeff')" },
287
+ };
280
288
 
281
289
  export function removedCoreImports(project) {
282
290
  const root = project.root;
283
291
  const used = new Map(); // name -> first file:line
292
+ const deprecated = new Map();
284
293
  const files = [];
285
294
  const walk = (dir) => {
286
295
  let entries = [];
@@ -308,17 +317,31 @@ export function removedCoreImports(project) {
308
317
  for (const b of blocks) {
309
318
  for (const raw of b.names.split(',')) {
310
319
  const name = raw.trim().replace(/^type\s+/, '').split(/\s+as\s+/)[0].trim();
311
- if ((REMOVED_FROM_CORE[name] || REMOVED_PROP_TYPES.includes(name)) && !used.has(name)) {
312
- used.set(name, `${path.relative(root, file)}:${text.slice(0, b.index).split('\n').length}`);
313
- }
320
+ const at = () => `${path.relative(root, file)}:${text.slice(0, b.index).split('\n').length}`;
321
+ if ((REMOVED_FROM_CORE[name] || REMOVED_PROP_TYPES.includes(name)) && !used.has(name)) used.set(name, at());
322
+ if (DEPRECATED_IN_CORE[name] && !deprecated.has(name)) deprecated.set(name, at());
314
323
  }
315
324
  }
316
325
  }
317
- if (!used.size) return [];
318
326
  const minor = project.rnVersion ? Number(project.rnVersion.split('.')[1]) : null;
327
+ const findings = [];
328
+ if (deprecated.size) {
329
+ const names = [...deprecated.keys()];
330
+ const warnsNow = minor !== null && names.some((n) => minor >= DEPRECATED_IN_CORE[n].since);
331
+ findings.push({
332
+ id: 'deprecated-core-imports',
333
+ severity: 'low',
334
+ area: 'react-native',
335
+ title: `${names.join(', ')} ${names.length === 1 ? 'is' : 'are'} deprecated in React Native core and will be removed`,
336
+ detail: `${[...deprecated].map(([n, at]) => `${n} (${at}, deprecated in 0.${DEPRECATED_IN_CORE[n].since}) → ${DEPRECATED_IN_CORE[n].use}`).join('; ')}. ${warnsNow ? 'They already log a deprecation warning.' : 'They start logging a warning when you upgrade.'} Once removed, the import returns undefined and the screen crashes, so move off them while it is a small change.`,
337
+ fix: { kind: 'deprecated-core', step: `Replace ${names.map((n) => `\`${n}\` (→ ${DEPRECATED_IN_CORE[n].use})`).join(', ')} before React Native removes ${names.length === 1 ? 'it' : 'them'}.` },
338
+ });
339
+ }
340
+ if (!used.size) return findings;
319
341
  const crashesNow = [...used.keys()].some((n) => (REMOVED_PROP_TYPES.includes(n) ? minor >= 74 : minor >= 72));
320
342
  const list = [...used].map(([n, at]) => `${n} (${at}) → ${REMOVED_FROM_CORE[n] || 'deprecated-react-native-prop-types'}`);
321
343
  return [
344
+ ...findings,
322
345
  {
323
346
  id: 'crash-removed-core-imports',
324
347
  severity: crashesNow ? 'high' : 'medium',