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 +2 -2
- package/package.json +1 -1
- package/src/analyze.js +1 -1
- package/src/expo-pins.js +1 -0
- package/src/plan.js +1 -0
- package/src/stability.js +27 -4
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.
|
|
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.
|
|
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}
|
|
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
|
-
|
|
312
|
-
|
|
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',
|