@produtype/core 1.22.0 → 1.24.0
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.
|
@@ -247,6 +247,19 @@ function isTestOrExamplePath(file) {
|
|
|
247
247
|
* and it says which build the file is part of.
|
|
248
248
|
*/
|
|
249
249
|
|| /(^|\/)config\/(test|dev)\.exs$/i.test(file)
|
|
250
|
+
/**
|
|
251
|
+
* Gradle's source sets, which name the build they belong to.
|
|
252
|
+
*
|
|
253
|
+
* `src/androidTest/` is instrumentation tests and `src/test/` is unit tests;
|
|
254
|
+
* neither is compiled into the shipped application, and `src/main/` is. davx5
|
|
255
|
+
* keeps an `AndroidManifest.xml` in `core/src/androidTest/` declaring three
|
|
256
|
+
* permissions its tests need, and the report opened its permission finding with
|
|
257
|
+
* that file — a test's manifest offered as the app's permission surface.
|
|
258
|
+
*
|
|
259
|
+
* The same argument as `_test.go` and `config/test.exs`: the directory name is
|
|
260
|
+
* the toolchain's, not the author's, and it says which build the file is part of.
|
|
261
|
+
*/
|
|
262
|
+
|| /(^|\/)src\/(androidTest|test)\//i.test(file)
|
|
250
263
|
|| /(^|\/)test[-_][^/]+\.(ts|tsx|js|jsx|mjs|cjs|py)$/i.test(file)
|
|
251
264
|
/**
|
|
252
265
|
* The other half of the convention.
|
|
@@ -232,7 +232,20 @@ async function detectMobile(ctx) {
|
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Gradle names the source set a file belongs to, and `androidTest` is not shipped.
|
|
237
|
+
*
|
|
238
|
+
* davx5 keeps an `AndroidManifest.xml` in `core/src/androidTest/` declaring the
|
|
239
|
+
* three permissions its instrumentation tests need, and the permission finding
|
|
240
|
+
* opened with that file: a test's manifest offered as the application's permission
|
|
241
|
+
* surface. `src/main/` is what goes into the APK.
|
|
242
|
+
*
|
|
243
|
+
* Filtered here rather than in the path rule that excludes `_test.go` and
|
|
244
|
+
* `config/test.exs`, because these manifests are read from the whole file list and
|
|
245
|
+
* never pass through it.
|
|
246
|
+
*/
|
|
247
|
+
const SHIPPED_SOURCE_SET = (file) => !/(^|\/)src\/(androidTest|test)\//i.test(file);
|
|
248
|
+
for (const file of androidFiles.filter((f) => /AndroidManifest\.xml$/i.test(f) && SHIPPED_SOURCE_SET(f))) {
|
|
236
249
|
const text = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
|
|
237
250
|
const declared = [...text.matchAll(/<uses-permission[^>]*android:name="([^"]+)"/g)].map(([, name]) => name);
|
|
238
251
|
// Android has no purpose strings, so the equivalent evidence is asking at the
|
|
@@ -245,6 +258,33 @@ async function detectMobile(ctx) {
|
|
|
245
258
|
});
|
|
246
259
|
}
|
|
247
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Xcode has generated the Info.plist from build settings since version 13.
|
|
263
|
+
*
|
|
264
|
+
* damus declares five purpose strings and its `Info.plist` holds none of them:
|
|
265
|
+
* they are `INFOPLIST_KEY_NSCameraUsageDescription` and its four siblings in
|
|
266
|
+
* `project.pbxproj`, and again in `de.lproj/InfoPlist.strings` for every language
|
|
267
|
+
* it ships. The report told a careful application, at `high`, that it gives no
|
|
268
|
+
* reason for any permission it asks for.
|
|
269
|
+
*
|
|
270
|
+
* `INFOPLIST_KEY_` is Xcode's prefix and `InfoPlist.strings` is Apple's
|
|
271
|
+
* localisation file; the sentence after the equals sign is the author's. An empty
|
|
272
|
+
* one is treated the same way as an empty `<string>` in the plist — it passes the
|
|
273
|
+
* compiler and fails review.
|
|
274
|
+
*/
|
|
275
|
+
for (const file of ctx.files.all.filter((f) => /project\.pbxproj$|(^|\/)InfoPlist\.strings$/i.test(f)).slice(0, 8)) {
|
|
276
|
+
const text = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
|
|
277
|
+
const declared = [...text.matchAll(/(?:INFOPLIST_KEY_)?"?(NS\w*UsageDescription)"?\s*=\s*"([^"]*)"/g)];
|
|
278
|
+
const explained = declared.filter(([, , reason]) => reason.trim().length > 0);
|
|
279
|
+
if (!explained.length)
|
|
280
|
+
continue;
|
|
281
|
+
permissionsExplained = true;
|
|
282
|
+
permissionEvidence.push({
|
|
283
|
+
type: 'file',
|
|
284
|
+
value: `${explained.length} permission${explained.length === 1 ? '' : 's'} with a reason in ${file}`,
|
|
285
|
+
file,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
248
288
|
const runtimeRequests = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [
|
|
249
289
|
/requestPermissions?\(/,
|
|
250
290
|
/ActivityCompat\.requestPermissions/,
|
|
@@ -253,6 +293,24 @@ async function detectMobile(ctx) {
|
|
|
253
293
|
/request\(\)\s*;?\s*\/\/\s*permission/,
|
|
254
294
|
/PermissionsAndroid\.request/,
|
|
255
295
|
/requestPermissionsAsync/,
|
|
296
|
+
/**
|
|
297
|
+
* How Android asks for a permission now, in the two libraries Google publishes.
|
|
298
|
+
*
|
|
299
|
+
* davx5 shows a switch per permission and calls
|
|
300
|
+
* `state::launchMultiplePermissionRequest` when somebody turns one on —
|
|
301
|
+
* Accompanist's API, which is asking at the moment of use as plainly as it can
|
|
302
|
+
* be asked. It was told at `high` that it gives no reason for any of the twelve
|
|
303
|
+
* permissions it declares.
|
|
304
|
+
*
|
|
305
|
+
* `ActivityResultContracts.RequestPermission` is the AndroidX contract that
|
|
306
|
+
* replaced `ActivityCompat.requestPermissions`, and
|
|
307
|
+
* `rememberMultiplePermissionsState` is Accompanist's. Both names belong to
|
|
308
|
+
* Google; what the launcher is called afterwards belongs to the author, which
|
|
309
|
+
* is why the pattern stops at the contract.
|
|
310
|
+
*/
|
|
311
|
+
/ActivityResultContracts\.RequestMultiplePermissions|ActivityResultContracts\.RequestPermission\b/,
|
|
312
|
+
/remember(?:Multiple)?Permissions?State\s*\(/,
|
|
313
|
+
/launch(?:Multiple)?Permissions?Request\b/,
|
|
256
314
|
], 3);
|
|
257
315
|
for (const match of runtimeRequests) {
|
|
258
316
|
permissionsExplained = true;
|
|
@@ -286,6 +344,22 @@ async function detectMobile(ctx) {
|
|
|
286
344
|
*/
|
|
287
345
|
const connectivityChecks = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [/Connectivity\(\)/, /connectivity_plus/, /NetInfo\./, /navigator\.onLine/, /NWPathMonitor/, /isReachable/], 3);
|
|
288
346
|
const platformStores = localStores.uses;
|
|
347
|
+
/**
|
|
348
|
+
* Watching the network is not storing anything, and it was the citation under
|
|
349
|
+
* "no local database".
|
|
350
|
+
*
|
|
351
|
+
* `NWPathMonitor`, `NetInfo` and `navigator.onLine` tell an application whether it
|
|
352
|
+
* is connected. They were pushed into this evidence list while counting for nothing
|
|
353
|
+
* in the verdict, so damus — which was found to have no store this can read — was
|
|
354
|
+
* shown `let network_monitor = NWPathMonitor()` as the reason. A line answering one
|
|
355
|
+
* question, offered as the evidence for another.
|
|
356
|
+
*
|
|
357
|
+
* Where a store *is* found they belong: knowing you are offline is part of working
|
|
358
|
+
* offline. Where none is, the search that came up empty is the honest evidence, and
|
|
359
|
+
* a reader who keeps their notes in something this cannot read can see what was
|
|
360
|
+
* looked for and say so.
|
|
361
|
+
*/
|
|
362
|
+
const hasALocalStore = offlineDeps.length > 0 || platformStores.length > 0;
|
|
289
363
|
const offlineEvidence = [
|
|
290
364
|
...offlineDeps.map((dep) => ({ type: 'dependency', value: dep })),
|
|
291
365
|
...platformStores.map((match) => ({
|
|
@@ -294,7 +368,7 @@ async function detectMobile(ctx) {
|
|
|
294
368
|
file: match.file,
|
|
295
369
|
line: match.line,
|
|
296
370
|
})),
|
|
297
|
-
...connectivityChecks.map((match) => ({
|
|
371
|
+
...(hasALocalStore ? connectivityChecks : []).map((match) => ({
|
|
298
372
|
type: 'snippet',
|
|
299
373
|
value: match.snippet,
|
|
300
374
|
file: match.file,
|
|
@@ -338,7 +412,7 @@ async function detectMobile(ctx) {
|
|
|
338
412
|
},
|
|
339
413
|
{
|
|
340
414
|
key: 'mobile.offline',
|
|
341
|
-
present:
|
|
415
|
+
present: hasALocalStore,
|
|
342
416
|
evidence: (0, absenceEvidence_1.evidenceOrSearch)(offlineEvidence, 'a local database the app can read with no network', ['sqflite', 'drift', 'hive', 'isar', 'objectbox', 'realm', 'androidx.room', 'sqldelight', 'grdb.swift', 'sqlite.swift', 'NSManagedObjectContext', 'SQLiteOpenHelper', 'getWritableDatabase(']),
|
|
343
417
|
},
|
|
344
418
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|