@newly/cli 0.2.203 → 0.2.204
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/commands/appStoreEvaluate.js +70 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync, } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
const PREVIEW_APP_ID = 'app.newly.preview';
|
|
4
5
|
const SCAFFOLD_SCHEME = 'expobase';
|
|
5
6
|
const INBOUND_LINK = /Linking\.createURL|makeRedirectUri|emailRedirectTo|\bredirectTo\s*:|expobase:\/\//;
|
|
7
|
+
const SCAFFOLD_APP_NAME = 'expo-base';
|
|
8
|
+
export const SCAFFOLD_ICON_SHA256 = '7a667804bb80a6a424a5daf18a2599c4f32237cf06fe78fc0de45dbb09e0eccf';
|
|
9
|
+
const SCAFFOLD_ICON_COMPOSER_MARK = path.join('Assets', 'expo-symbol 2.svg');
|
|
10
|
+
const STARTER_SCREEN = /@newly\/welcome|Welcome to Newly|Made with Newly/;
|
|
11
|
+
const PLACEHOLDER_ID = /com\.placeholder\./;
|
|
6
12
|
const PLACEHOLDER_COPY = /lorem ipsum|example\.com|\bTBD\b|\bTODO\b|\bplaceholder\b/i;
|
|
7
13
|
const INPUT_PLACEHOLDER = /\bplaceholder\??\s*[=:]\s*(\{[^}]*\}|"[^"]*"|'[^']*'|`[^`]*`|[\w.$]+)/gi;
|
|
8
14
|
const PLACEHOLDER_NAME = /\.placeholder\b|\bplaceholder\b(?=\s*[,})\]])/gi;
|
|
@@ -86,6 +92,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
86
92
|
'.cc',
|
|
87
93
|
'.cpp',
|
|
88
94
|
'.cxx',
|
|
95
|
+
'.entitlements',
|
|
89
96
|
'.h',
|
|
90
97
|
'.hpp',
|
|
91
98
|
'.java',
|
|
@@ -216,6 +223,8 @@ function evaluateLocalItem(root, appJson, item) {
|
|
|
216
223
|
return makeItem(item, 'pass', 'Expo Router adds a layout where a directory has none.', 'expo-router getRoutesCore.js');
|
|
217
224
|
case 'expo_duplicate_route':
|
|
218
225
|
return evaluateDuplicateRoutes(root, item);
|
|
226
|
+
case 'starter_leftovers':
|
|
227
|
+
return evaluateStarterLeftovers(root, appJson, item);
|
|
219
228
|
case 'ios_encryption':
|
|
220
229
|
return evaluateEncryption(appJson, item);
|
|
221
230
|
case 'plugin_permission_missing':
|
|
@@ -297,6 +306,67 @@ function evaluateExternalPayment(root, item) {
|
|
|
297
306
|
return makeItem(item, 'pass', 'No external payment SDK patterns found.', 'scan: no Stripe/PayPal/Braintree patterns found');
|
|
298
307
|
return makeItem(item, 'warning', 'External payment SDK found; verify it is not used for digital goods on iOS.', evidence(matches));
|
|
299
308
|
}
|
|
309
|
+
function isScaffoldIconPng(root, icon) {
|
|
310
|
+
try {
|
|
311
|
+
return createHash('sha256').update(readFileSync(path.join(root, icon))).digest('hex') === SCAFFOLD_ICON_SHA256;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function hasStarterIcon(root, appJson) {
|
|
318
|
+
const iosIcon = getPath(appJson, ['expo', 'ios', 'icon']);
|
|
319
|
+
if (iosIcon && typeof iosIcon === 'object') {
|
|
320
|
+
return Object.values(iosIcon).some((icon) => typeof icon === 'string' && isScaffoldIconPng(root, icon));
|
|
321
|
+
}
|
|
322
|
+
const iosPath = stringValue(iosIcon);
|
|
323
|
+
if (iosPath && !/\.png$/i.test(iosPath))
|
|
324
|
+
return existsSync(path.join(root, iosPath, SCAFFOLD_ICON_COMPOSER_MARK));
|
|
325
|
+
return isScaffoldIconPng(root, iosPath ?? stringValue(getPath(appJson, ['expo', 'icon'])) ?? './assets/images/icon.png');
|
|
326
|
+
}
|
|
327
|
+
function evaluateStarterLeftovers(root, appJson, item) {
|
|
328
|
+
const fromConfig = [];
|
|
329
|
+
const fromSource = [];
|
|
330
|
+
const proof = [];
|
|
331
|
+
if (stringValue(getPath(appJson, ['expo', 'name'])) === SCAFFOLD_APP_NAME) {
|
|
332
|
+
fromConfig.push(`the app is still named ${SCAFFOLD_APP_NAME}`);
|
|
333
|
+
proof.push(`app.json expo.name = "${SCAFFOLD_APP_NAME}"`);
|
|
334
|
+
}
|
|
335
|
+
const starterIcon = hasStarterIcon(root, appJson);
|
|
336
|
+
if (starterIcon)
|
|
337
|
+
proof.push('icon: the scaffold default');
|
|
338
|
+
const config = structuredClone(getPath(appJson, ['expo']) ?? {});
|
|
339
|
+
delete config.ios?.bundleIdentifier;
|
|
340
|
+
delete config.android?.package;
|
|
341
|
+
if (PLACEHOLDER_ID.test(JSON.stringify(config))) {
|
|
342
|
+
fromConfig.push('app.json still has a com.placeholder identifier');
|
|
343
|
+
proof.push('app.json: com.placeholder outside the store ids');
|
|
344
|
+
}
|
|
345
|
+
const screen = scan(root, STARTER_SCREEN, { dirs: ['app', 'src', 'components', 'ios'] });
|
|
346
|
+
if (screen.length) {
|
|
347
|
+
fromSource.push('the Newly welcome screen or branding is still in the app');
|
|
348
|
+
proof.push(evidence(screen));
|
|
349
|
+
}
|
|
350
|
+
const ids = scan(root, PLACEHOLDER_ID, { dirs: ['app', 'src', 'components', 'contexts', 'ios', 'plugins', 'targets'] });
|
|
351
|
+
if (ids.length) {
|
|
352
|
+
fromSource.push('a com.placeholder identifier is still in the code');
|
|
353
|
+
proof.push(evidence(ids));
|
|
354
|
+
}
|
|
355
|
+
const dynamicConfig = dynamicExpoConfig(root);
|
|
356
|
+
const blocking = dynamicConfig ? fromSource : [...fromConfig, ...fromSource];
|
|
357
|
+
const icon = starterIcon && !dynamicConfig ? ' The app icon is also still the starter icon.' : '';
|
|
358
|
+
const consequence = 'A reviewer reads the starter as an unfinished app (Guideline 2.1), and apps that look alike are rejected as spam (4.3), so replace it before you submit.';
|
|
359
|
+
if (blocking.length)
|
|
360
|
+
return makeItem(item, 'fail', `Left over from the Newly starter: ${blocking.join('; ')}.${icon} ${consequence}`, proof.join('\n'));
|
|
361
|
+
if (starterIcon && !dynamicConfig) {
|
|
362
|
+
return makeItem(item, 'warning', 'The app icon is still the starter icon, the same in every new Newly app. Give the app its own before you submit.', proof.join('\n'));
|
|
363
|
+
}
|
|
364
|
+
if (dynamicConfig && (fromConfig.length || starterIcon)) {
|
|
365
|
+
const found = [...fromConfig, ...(starterIcon ? ['the app icon is the starter icon'] : [])];
|
|
366
|
+
return makeItem(item, 'info', uncheckedUnder(dynamicConfig, `Left over from the Newly starter: ${found.join('; ')}.`), proof.join('\n'));
|
|
367
|
+
}
|
|
368
|
+
return makeItem(item, 'pass', 'Nothing left over from the Newly starter.', 'name, icon, welcome screen and placeholder ids checked');
|
|
369
|
+
}
|
|
300
370
|
function evaluateDuplicateRoutes(root, item) {
|
|
301
371
|
const appDir = routerDir(root);
|
|
302
372
|
if (!existsSync(appDir))
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.204';
|