@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
- for (const file of androidFiles.filter((f) => /AndroidManifest\.xml$/i.test(f))) {
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: offlineDeps.length > 0 || platformStores.length > 0,
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.22.0",
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": {