@produtype/core 0.74.0 → 0.75.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.
|
@@ -651,6 +651,25 @@ async function analyzeProject(projectPath) {
|
|
|
651
651
|
swiftDeps.push(match[1].toLowerCase());
|
|
652
652
|
}
|
|
653
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Package.resolved, which is where a Swift project's dependencies are actually
|
|
656
|
+
* legible.
|
|
657
|
+
*
|
|
658
|
+
* `Package.swift` is read above, and on a real application it usually declares the
|
|
659
|
+
* modules of that package rather than the whole tree: WordPress-iOS resolves 40-odd
|
|
660
|
+
* packages and `sentry-cocoa` — its crash reporter — appears only here. The lockfile
|
|
661
|
+
* is data rather than source, it names every transitive dependency, and it is
|
|
662
|
+
* committed by convention, which makes it the better of the two to read.
|
|
663
|
+
*
|
|
664
|
+
* `identity` rather than `location`, because that is the name SPM itself uses and
|
|
665
|
+
* the one that survives a repository moving host.
|
|
666
|
+
*/
|
|
667
|
+
for (const file of ownManifests.filter((f) => /(^|\/)Package\.resolved$/.test(f))) {
|
|
668
|
+
const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(root, file)) ?? '';
|
|
669
|
+
for (const match of raw.matchAll(/"identity"\s*:\s*"([^"]+)"/g)) {
|
|
670
|
+
swiftDeps.push(match[1].toLowerCase());
|
|
671
|
+
}
|
|
672
|
+
}
|
|
654
673
|
/**
|
|
655
674
|
* .csproj, which is XML rather than JSON or one-entry-per-line.
|
|
656
675
|
*
|
|
@@ -29,9 +29,74 @@ const REPORTER_DEPS = [
|
|
|
29
29
|
'trackjs',
|
|
30
30
|
];
|
|
31
31
|
const REPORTER_PY_DEPS = ['sentry-sdk', 'rollbar', 'bugsnag'];
|
|
32
|
+
/**
|
|
33
|
+
* The same capability, in the ecosystems where the browser does not exist.
|
|
34
|
+
*
|
|
35
|
+
* This detector was written for code running in a tab and then made `required` for the
|
|
36
|
+
* mobile-app profile, where none of what it reads can occur: an iOS application has no
|
|
37
|
+
* `window.onerror` and no npm dependency. Three real repositories measured — DuckDuckGo
|
|
38
|
+
* iOS, thunderbird-android, WordPress-iOS — and all three were told at `high` that a
|
|
39
|
+
* crash on someone's phone reaches nobody. A check whose answer cannot vary is not a
|
|
40
|
+
* measure.
|
|
41
|
+
*
|
|
42
|
+
* Coordinates rather than words, matched as substrings by the helpers: `io.sentry` also
|
|
43
|
+
* covers `io.sentry:sentry-android`, and `sentry-cocoa` is how SPM names the package
|
|
44
|
+
* whatever the product is called.
|
|
45
|
+
*/
|
|
46
|
+
const REPORTER_GRADLE_DEPS = [
|
|
47
|
+
'io.sentry',
|
|
48
|
+
'com.google.firebase:firebase-crashlytics',
|
|
49
|
+
'com.bugsnag',
|
|
50
|
+
'com.microsoft.appcenter:appcenter-crashes',
|
|
51
|
+
'ch.acra:acra',
|
|
52
|
+
'io.embrace',
|
|
53
|
+
'com.datadoghq:dd-sdk-android',
|
|
54
|
+
'com.instabug',
|
|
55
|
+
];
|
|
56
|
+
const REPORTER_SWIFT_DEPS = [
|
|
57
|
+
'sentry-cocoa',
|
|
58
|
+
'sentry-cocoa-spm',
|
|
59
|
+
'firebase-ios-sdk',
|
|
60
|
+
'firebasecrashlytics',
|
|
61
|
+
'bugsnag-cocoa',
|
|
62
|
+
'appcenter-sdk-apple',
|
|
63
|
+
'instabug',
|
|
64
|
+
'embrace-apple-sdk',
|
|
65
|
+
];
|
|
66
|
+
const REPORTER_DART_DEPS = ['sentry_flutter', 'firebase_crashlytics', 'bugsnag_flutter'];
|
|
67
|
+
/**
|
|
68
|
+
* Handlers the platform defines, which no author names.
|
|
69
|
+
*
|
|
70
|
+
* `NSSetUncaughtExceptionHandler` is Foundation, `Thread.setDefaultUncaughtExceptionHandler`
|
|
71
|
+
* is the JVM, `FlutterError.onError` is Flutter. Each is the one place its platform
|
|
72
|
+
* lets a program learn that it is about to die, so a call to it is the capability
|
|
73
|
+
* itself rather than a word that tends to accompany it — thunderbird-android installs
|
|
74
|
+
* one and was reported as having nothing.
|
|
75
|
+
*
|
|
76
|
+
* `SentrySDK.start` and `FirebaseCrashlytics` are here as well as in the dependency
|
|
77
|
+
* lists: a repository can use a reporter whose manifest is in another repository, and
|
|
78
|
+
* the call is still a call.
|
|
79
|
+
*/
|
|
80
|
+
const PLATFORM_CRASH_HANDLERS = [
|
|
81
|
+
/NSSetUncaughtExceptionHandler\s*\(/,
|
|
82
|
+
// `(` or `{`: Kotlin writes the handler as a trailing lambda, which is how both real
|
|
83
|
+
// Android applications measured install theirs.
|
|
84
|
+
/setDefaultUncaughtExceptionHandler\s*[({]/,
|
|
85
|
+
/FlutterError\s*\.\s*onError\s*=/,
|
|
86
|
+
/PlatformDispatcher\s*\.\s*instance\s*\.\s*onError\s*=/,
|
|
87
|
+
/SentrySDK\s*\.\s*start\s*\(/,
|
|
88
|
+
/Sentry\s*\.\s*init\s*\(/,
|
|
89
|
+
/FirebaseCrashlytics|Crashlytics\s*\.\s*crashlytics\s*\(/,
|
|
90
|
+
];
|
|
32
91
|
async function detectErrorReporting(ctx) {
|
|
33
92
|
const evidence = [];
|
|
34
|
-
const deps = [
|
|
93
|
+
const deps = [
|
|
94
|
+
...(0, detectContext_1.hasAnyDep)(ctx, REPORTER_DEPS),
|
|
95
|
+
...(0, detectContext_1.hasAnyPyDep)(ctx, REPORTER_PY_DEPS),
|
|
96
|
+
...(0, detectContext_1.hasAnyGradleDep)(ctx, REPORTER_GRADLE_DEPS),
|
|
97
|
+
...(0, detectContext_1.hasAnySwiftDep)(ctx, REPORTER_SWIFT_DEPS),
|
|
98
|
+
...(0, detectContext_1.hasAnyDartDep)(ctx, REPORTER_DART_DEPS),
|
|
99
|
+
];
|
|
35
100
|
for (const dep of deps)
|
|
36
101
|
evidence.push({ type: 'dependency', value: dep });
|
|
37
102
|
/**
|
|
@@ -44,6 +109,7 @@ async function detectErrorReporting(ctx) {
|
|
|
44
109
|
/window\.addEventListener\s*\(\s*['"]unhandledrejection['"]/,
|
|
45
110
|
/window\.onerror\s*=/,
|
|
46
111
|
/componentDidCatch\s*\([^)]*\)\s*\{[^}]*(fetch|report|log)/s,
|
|
112
|
+
...PLATFORM_CRASH_HANDLERS,
|
|
47
113
|
], 10);
|
|
48
114
|
for (const hit of handlers) {
|
|
49
115
|
evidence.push({ type: 'snippet', value: hit.snippet, file: hit.file, line: hit.line });
|
|
@@ -54,7 +120,7 @@ async function detectErrorReporting(ctx) {
|
|
|
54
120
|
// A reporting service is wired up once and covers everything; a hand-rolled handler
|
|
55
121
|
// usually covers what its author remembered.
|
|
56
122
|
complete: deps.length > 0,
|
|
57
|
-
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'somewhere a crash in the browser is sent', ['@sentry/browser', '@sentry/react', '@bugsnag/js', 'rollbar', 'logrocket', '@datadog/browser-rum', 'sentry-sdk', 'window.onerror', 'addEventListener("error")', 'addEventListener("unhandledrejection")']),
|
|
123
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'somewhere a crash in the browser is sent', ['@sentry/browser', '@sentry/react', '@bugsnag/js', 'rollbar', 'logrocket', '@datadog/browser-rum', 'sentry-sdk', 'window.onerror', 'addEventListener("error")', 'addEventListener("unhandledrejection")', 'NSSetUncaughtExceptionHandler', 'Thread.setDefaultUncaughtExceptionHandler', 'FlutterError.onError', 'sentry-cocoa', 'io.sentry', 'firebase-crashlytics']),
|
|
58
124
|
details: { services: deps, handlers: handlers.length },
|
|
59
125
|
};
|
|
60
126
|
}
|
|
@@ -82,6 +82,26 @@ const OFFLINE_SWIFT = [
|
|
|
82
82
|
'realm/realm-cocoa',
|
|
83
83
|
'realmswift',
|
|
84
84
|
];
|
|
85
|
+
/**
|
|
86
|
+
* Local storage the platform itself provides, which no dependency list will hold.
|
|
87
|
+
*
|
|
88
|
+
* Every entry in the lists above is a third-party database, and the two most widely
|
|
89
|
+
* used stores on these platforms ship with the operating system: Core Data on Apple's,
|
|
90
|
+
* `SQLiteDatabase` on Android's. Measured on two real applications whose whole purpose
|
|
91
|
+
* is working without a network — WordPress-iOS keeps its posts in Core Data,
|
|
92
|
+
* thunderbird-android keeps its mail in SQLite — and both were told at `high` that they
|
|
93
|
+
* store nothing locally.
|
|
94
|
+
*
|
|
95
|
+
* These are types the platform defines, not names an author picked: `NSManagedObjectContext`
|
|
96
|
+
* belongs to Core Data and appears nowhere else, and `getWritableDatabase()` is the one
|
|
97
|
+
* way an Android application opens its own database.
|
|
98
|
+
*/
|
|
99
|
+
const PLATFORM_LOCAL_STORES = [
|
|
100
|
+
/NSPersistentContainer|NSManagedObjectContext|NSPersistentStoreCoordinator/,
|
|
101
|
+
/ModelContainer\s*\(|@Model\b/,
|
|
102
|
+
/SQLiteOpenHelper|getWritableDatabase\s*\(|getReadableDatabase\s*\(/,
|
|
103
|
+
/android\.database\.sqlite\.SQLiteDatabase/,
|
|
104
|
+
];
|
|
85
105
|
/** Dependencies that put a secret somewhere the operating system protects. */
|
|
86
106
|
const SECURE_STORAGE_DEPS = [
|
|
87
107
|
'flutter_secure_storage',
|
|
@@ -315,8 +335,15 @@ async function detectMobile(ctx) {
|
|
|
315
335
|
* connection is gone is a different thing from one that shows a spinner forever.
|
|
316
336
|
*/
|
|
317
337
|
const connectivityChecks = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [/Connectivity\(\)/, /connectivity_plus/, /NetInfo\./, /navigator\.onLine/, /NWPathMonitor/, /isReachable/], 3);
|
|
338
|
+
const platformStores = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, PLATFORM_LOCAL_STORES, 3);
|
|
318
339
|
const offlineEvidence = [
|
|
319
340
|
...offlineDeps.map((dep) => ({ type: 'dependency', value: dep })),
|
|
341
|
+
...platformStores.map((match) => ({
|
|
342
|
+
type: 'snippet',
|
|
343
|
+
value: match.snippet,
|
|
344
|
+
file: match.file,
|
|
345
|
+
line: match.line,
|
|
346
|
+
})),
|
|
320
347
|
...connectivityChecks.map((match) => ({
|
|
321
348
|
type: 'snippet',
|
|
322
349
|
value: match.snippet,
|
|
@@ -361,8 +388,8 @@ async function detectMobile(ctx) {
|
|
|
361
388
|
},
|
|
362
389
|
{
|
|
363
390
|
key: 'mobile.offline',
|
|
364
|
-
present: offlineDeps.length > 0,
|
|
365
|
-
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']),
|
|
391
|
+
present: offlineDeps.length > 0 || platformStores.length > 0,
|
|
392
|
+
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(']),
|
|
366
393
|
},
|
|
367
394
|
{
|
|
368
395
|
key: 'mobile.forcedUpdate',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.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": {
|