@produtype/core 0.74.0 → 0.76.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.
- package/dist/analyzer/analyzeProject.js +19 -0
- package/dist/analyzer/detectErrorReporting.js +68 -2
- package/dist/analyzer/detectGame.js +27 -4
- package/dist/analyzer/detectMobile.js +12 -35
- package/dist/analyzer/localStores.d.ts +40 -0
- package/dist/analyzer/localStores.js +74 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.detectGame = detectGame;
|
|
4
4
|
const detectContext_1 = require("./detectContext");
|
|
5
5
|
const textSearch_1 = require("../utils/textSearch");
|
|
6
|
+
const localStores_1 = require("./localStores");
|
|
6
7
|
const absenceEvidence_1 = require("./absenceEvidence");
|
|
7
8
|
/**
|
|
8
9
|
* Whether this project is a game.
|
|
@@ -151,20 +152,42 @@ async function detectStatePersistence(ctx) {
|
|
|
151
152
|
}
|
|
152
153
|
// A database is the durable half. Read from the stack rather than re-detected, so
|
|
153
154
|
// this agrees with what the report says the data layer is.
|
|
154
|
-
const
|
|
155
|
+
const serverStore = ctx.files.all.some((file) => /(^|\/)(schema\.prisma|.*\.sql)$/i.test(file))
|
|
155
156
|
|| Boolean(ctx.packageJson?.dependencies?.pg)
|
|
156
157
|
|| Boolean(ctx.packageJson?.dependencies?.mongoose)
|
|
157
158
|
|| Boolean(ctx.packageJson?.dependencies?.['better-sqlite3']);
|
|
158
|
-
|
|
159
|
+
/**
|
|
160
|
+
* On a device, the local store is the durable one.
|
|
161
|
+
*
|
|
162
|
+
* This whole check was written for a game in a browser, where `localStorage` is the
|
|
163
|
+
* partial case because a cleared cache takes the save with it. That reasoning does
|
|
164
|
+
* not carry to a phone: Core Data and SQLite survive the application being killed,
|
|
165
|
+
* the device restarting and the person coming back a week later — they are the
|
|
166
|
+
* durable half there, not the fragile one.
|
|
167
|
+
*
|
|
168
|
+
* It is required of the mobile-app profile, and all four real applications measured
|
|
169
|
+
* came back `partial` on it while keeping everything they own on disk. The failure
|
|
170
|
+
* this should catch on a phone is different and still caught: state held only in
|
|
171
|
+
* memory, written nowhere, gone when the operating system reclaims the process.
|
|
172
|
+
*/
|
|
173
|
+
const deviceStore = await (0, localStores_1.readLocalStores)(ctx, 3);
|
|
174
|
+
const onDevice = deviceStore.dependencies.length > 0 || deviceStore.uses.length > 0;
|
|
175
|
+
const durable = serverStore || onDevice;
|
|
176
|
+
if (serverStore)
|
|
159
177
|
evidence.push({ type: 'note', value: 'a durable store is present' });
|
|
178
|
+
for (const dep of deviceStore.dependencies)
|
|
179
|
+
evidence.push({ type: 'dependency', value: dep });
|
|
180
|
+
for (const use of deviceStore.uses) {
|
|
181
|
+
evidence.push({ type: 'snippet', value: use.snippet, file: use.file, line: use.line });
|
|
182
|
+
}
|
|
160
183
|
const saves = clientOnly.length > 0 || saveRoutes.length > 0;
|
|
161
184
|
return {
|
|
162
185
|
key: 'app.stateDurability',
|
|
163
186
|
present: saves || durable,
|
|
164
187
|
// Client storage alone is the partial case: it saves, until it does not.
|
|
165
188
|
complete: durable,
|
|
166
|
-
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'anywhere progress is written down', ['localStorage', 'sessionStorage', 'IndexedDB', 'a save route', 'schema.prisma', 'a .sql file', 'pg', 'mongoose', 'better-sqlite3']),
|
|
167
|
-
details: { durable, clientStorage: clientOnly.length > 0, saveRoutines: saveRoutes.length },
|
|
189
|
+
evidence: (0, absenceEvidence_1.evidenceOrSearch)(evidence, 'anywhere progress is written down', ['localStorage', 'sessionStorage', 'IndexedDB', 'a save route', 'schema.prisma', 'a .sql file', 'pg', 'mongoose', 'better-sqlite3', 'Core Data', 'SQLiteOpenHelper', 'Room', 'sqflite']),
|
|
190
|
+
details: { durable, serverStore, onDevice, clientStorage: clientOnly.length > 0, saveRoutines: saveRoutes.length },
|
|
168
191
|
};
|
|
169
192
|
}
|
|
170
193
|
/**
|
|
@@ -4,6 +4,7 @@ exports.detectMobile = detectMobile;
|
|
|
4
4
|
const detectContext_1 = require("./detectContext");
|
|
5
5
|
const readTextFileSafe_1 = require("../utils/readTextFileSafe");
|
|
6
6
|
const textSearch_1 = require("../utils/textSearch");
|
|
7
|
+
const localStores_1 = require("./localStores");
|
|
7
8
|
const absenceEvidence_1 = require("./absenceEvidence");
|
|
8
9
|
/**
|
|
9
10
|
* Whether this is an application that ships to somebody else's phone, and what that
|
|
@@ -71,17 +72,6 @@ const SECURE_STORAGE_SWIFT = [
|
|
|
71
72
|
'square/valet',
|
|
72
73
|
'valet',
|
|
73
74
|
];
|
|
74
|
-
/** Local databases, by platform, that let an app open without a network. */
|
|
75
|
-
const OFFLINE_GRADLE = ['androidx.room', 'io.realm', 'io.objectbox', 'com.squareup.sqldelight', 'app.cash.sqldelight'];
|
|
76
|
-
const OFFLINE_SWIFT = [
|
|
77
|
-
'groue/grdb.swift',
|
|
78
|
-
'grdb.swift',
|
|
79
|
-
'stephencelis/sqlite.swift',
|
|
80
|
-
'sqlite.swift',
|
|
81
|
-
'realm/realm-swift',
|
|
82
|
-
'realm/realm-cocoa',
|
|
83
|
-
'realmswift',
|
|
84
|
-
];
|
|
85
75
|
/** Dependencies that put a secret somewhere the operating system protects. */
|
|
86
76
|
const SECURE_STORAGE_DEPS = [
|
|
87
77
|
'flutter_secure_storage',
|
|
@@ -91,22 +81,6 @@ const SECURE_STORAGE_DEPS = [
|
|
|
91
81
|
'@capacitor/preferences',
|
|
92
82
|
'capacitor-secure-storage-plugin',
|
|
93
83
|
];
|
|
94
|
-
/** Dependencies whose whole purpose is that the app works with no network. */
|
|
95
|
-
const OFFLINE_DEPS = [
|
|
96
|
-
'sqflite',
|
|
97
|
-
'drift',
|
|
98
|
-
'hive',
|
|
99
|
-
'isar',
|
|
100
|
-
'objectbox',
|
|
101
|
-
'realm',
|
|
102
|
-
'watermelondb',
|
|
103
|
-
'@nozbe/watermelondb',
|
|
104
|
-
'react-native-mmkv',
|
|
105
|
-
'@react-native-async-storage/async-storage',
|
|
106
|
-
'redux-persist',
|
|
107
|
-
'@tanstack/query-persist-client-core',
|
|
108
|
-
'powersync',
|
|
109
|
-
];
|
|
110
84
|
/**
|
|
111
85
|
* Paths that contain a platform marker without being a project.
|
|
112
86
|
*
|
|
@@ -303,20 +277,23 @@ async function detectMobile(ctx) {
|
|
|
303
277
|
line: match.line,
|
|
304
278
|
})),
|
|
305
279
|
];
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
...(0, detectContext_1.hasAnyDartDep)(ctx, OFFLINE_DEPS),
|
|
309
|
-
...(0, detectContext_1.hasAnyGradleDep)(ctx, OFFLINE_GRADLE),
|
|
310
|
-
...(0, detectContext_1.hasAnySwiftDep)(ctx, OFFLINE_SWIFT),
|
|
311
|
-
];
|
|
280
|
+
const localStores = await (0, localStores_1.readLocalStores)(ctx);
|
|
281
|
+
const offlineDeps = localStores.dependencies;
|
|
312
282
|
/**
|
|
313
283
|
* A local database is the strong signal; knowing the network dropped is the weak
|
|
314
284
|
* one. Both are recorded, because an app that stores nothing but tells the user the
|
|
315
285
|
* connection is gone is a different thing from one that shows a spinner forever.
|
|
316
286
|
*/
|
|
317
287
|
const connectivityChecks = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [/Connectivity\(\)/, /connectivity_plus/, /NetInfo\./, /navigator\.onLine/, /NWPathMonitor/, /isReachable/], 3);
|
|
288
|
+
const platformStores = localStores.uses;
|
|
318
289
|
const offlineEvidence = [
|
|
319
290
|
...offlineDeps.map((dep) => ({ type: 'dependency', value: dep })),
|
|
291
|
+
...platformStores.map((match) => ({
|
|
292
|
+
type: 'snippet',
|
|
293
|
+
value: match.snippet,
|
|
294
|
+
file: match.file,
|
|
295
|
+
line: match.line,
|
|
296
|
+
})),
|
|
320
297
|
...connectivityChecks.map((match) => ({
|
|
321
298
|
type: 'snippet',
|
|
322
299
|
value: match.snippet,
|
|
@@ -361,8 +338,8 @@ async function detectMobile(ctx) {
|
|
|
361
338
|
},
|
|
362
339
|
{
|
|
363
340
|
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']),
|
|
341
|
+
present: offlineDeps.length > 0 || platformStores.length > 0,
|
|
342
|
+
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
343
|
},
|
|
367
344
|
{
|
|
368
345
|
key: 'mobile.forcedUpdate',
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DetectContext } from './detectContext';
|
|
2
|
+
/**
|
|
3
|
+
* Where an application on somebody else's device writes things down.
|
|
4
|
+
*
|
|
5
|
+
* One list, read by the two capabilities that both ask this question and used to
|
|
6
|
+
* answer it differently: `mobile.offline` — can this open with no network — and
|
|
7
|
+
* `app.state-durability` — does the person's work survive. A second copy would drift,
|
|
8
|
+
* and it already had: durability was written for a game in a browser and knew only
|
|
9
|
+
* `localStorage` and four npm packages, so every native application measured came back
|
|
10
|
+
* `partial` while keeping its data in Core Data or SQLite.
|
|
11
|
+
*/
|
|
12
|
+
/** Local databases declared as dependencies, by ecosystem. */
|
|
13
|
+
export declare const LOCAL_STORE_GRADLE: string[];
|
|
14
|
+
export declare const LOCAL_STORE_SWIFT: string[];
|
|
15
|
+
export declare const LOCAL_STORE_DEPS: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Storage the platform itself provides, which no dependency list will hold.
|
|
18
|
+
*
|
|
19
|
+
* Every entry above is a third-party database, and the two most widely used stores on
|
|
20
|
+
* these platforms ship with the operating system: Core Data on Apple's,
|
|
21
|
+
* `SQLiteDatabase` on Android's. Measured on two applications whose whole purpose is
|
|
22
|
+
* working without a network — WordPress-iOS keeps its posts in Core Data,
|
|
23
|
+
* thunderbird-android keeps its mail in SQLite — and both were told they store nothing
|
|
24
|
+
* locally.
|
|
25
|
+
*
|
|
26
|
+
* Types the platform defines, not names an author picked: `NSManagedObjectContext`
|
|
27
|
+
* belongs to Core Data and appears nowhere else, and `getWritableDatabase()` is the one
|
|
28
|
+
* way an Android application opens its own database.
|
|
29
|
+
*/
|
|
30
|
+
export declare const PLATFORM_LOCAL_STORES: RegExp[];
|
|
31
|
+
export interface LocalStoreReading {
|
|
32
|
+
dependencies: string[];
|
|
33
|
+
uses: Array<{
|
|
34
|
+
file: string;
|
|
35
|
+
line: number;
|
|
36
|
+
snippet: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
/** Both halves of the question, so a caller can cite whichever it found. */
|
|
40
|
+
export declare function readLocalStores(ctx: DetectContext, limit?: number): Promise<LocalStoreReading>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLATFORM_LOCAL_STORES = exports.LOCAL_STORE_DEPS = exports.LOCAL_STORE_SWIFT = exports.LOCAL_STORE_GRADLE = void 0;
|
|
4
|
+
exports.readLocalStores = readLocalStores;
|
|
5
|
+
const detectContext_1 = require("./detectContext");
|
|
6
|
+
const textSearch_1 = require("../utils/textSearch");
|
|
7
|
+
/**
|
|
8
|
+
* Where an application on somebody else's device writes things down.
|
|
9
|
+
*
|
|
10
|
+
* One list, read by the two capabilities that both ask this question and used to
|
|
11
|
+
* answer it differently: `mobile.offline` — can this open with no network — and
|
|
12
|
+
* `app.state-durability` — does the person's work survive. A second copy would drift,
|
|
13
|
+
* and it already had: durability was written for a game in a browser and knew only
|
|
14
|
+
* `localStorage` and four npm packages, so every native application measured came back
|
|
15
|
+
* `partial` while keeping its data in Core Data or SQLite.
|
|
16
|
+
*/
|
|
17
|
+
/** Local databases declared as dependencies, by ecosystem. */
|
|
18
|
+
exports.LOCAL_STORE_GRADLE = ['androidx.room', 'io.realm', 'io.objectbox', 'com.squareup.sqldelight', 'app.cash.sqldelight', 'androidx.datastore'];
|
|
19
|
+
exports.LOCAL_STORE_SWIFT = [
|
|
20
|
+
'groue/grdb.swift',
|
|
21
|
+
'grdb.swift',
|
|
22
|
+
'stephencelis/sqlite.swift',
|
|
23
|
+
'sqlite.swift',
|
|
24
|
+
'realm/realm-swift',
|
|
25
|
+
'realm/realm-cocoa',
|
|
26
|
+
'realmswift',
|
|
27
|
+
];
|
|
28
|
+
exports.LOCAL_STORE_DEPS = [
|
|
29
|
+
'sqflite',
|
|
30
|
+
'drift',
|
|
31
|
+
'hive',
|
|
32
|
+
'isar',
|
|
33
|
+
'objectbox',
|
|
34
|
+
'realm',
|
|
35
|
+
'watermelondb',
|
|
36
|
+
'@nozbe/watermelondb',
|
|
37
|
+
'react-native-mmkv',
|
|
38
|
+
'@react-native-async-storage/async-storage',
|
|
39
|
+
'redux-persist',
|
|
40
|
+
'@tanstack/query-persist-client-core',
|
|
41
|
+
'powersync',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Storage the platform itself provides, which no dependency list will hold.
|
|
45
|
+
*
|
|
46
|
+
* Every entry above is a third-party database, and the two most widely used stores on
|
|
47
|
+
* these platforms ship with the operating system: Core Data on Apple's,
|
|
48
|
+
* `SQLiteDatabase` on Android's. Measured on two applications whose whole purpose is
|
|
49
|
+
* working without a network — WordPress-iOS keeps its posts in Core Data,
|
|
50
|
+
* thunderbird-android keeps its mail in SQLite — and both were told they store nothing
|
|
51
|
+
* locally.
|
|
52
|
+
*
|
|
53
|
+
* Types the platform defines, not names an author picked: `NSManagedObjectContext`
|
|
54
|
+
* belongs to Core Data and appears nowhere else, and `getWritableDatabase()` is the one
|
|
55
|
+
* way an Android application opens its own database.
|
|
56
|
+
*/
|
|
57
|
+
exports.PLATFORM_LOCAL_STORES = [
|
|
58
|
+
/NSPersistentContainer|NSManagedObjectContext|NSPersistentStoreCoordinator/,
|
|
59
|
+
/ModelContainer\s*\(|@Model\b/,
|
|
60
|
+
/SQLiteOpenHelper|getWritableDatabase\s*\(|getReadableDatabase\s*\(/,
|
|
61
|
+
/android\.database\.sqlite\.SQLiteDatabase/,
|
|
62
|
+
];
|
|
63
|
+
/** Both halves of the question, so a caller can cite whichever it found. */
|
|
64
|
+
async function readLocalStores(ctx, limit = 3) {
|
|
65
|
+
return {
|
|
66
|
+
dependencies: [
|
|
67
|
+
...(0, detectContext_1.hasAnyDep)(ctx, exports.LOCAL_STORE_DEPS),
|
|
68
|
+
...(0, detectContext_1.hasAnyDartDep)(ctx, exports.LOCAL_STORE_DEPS),
|
|
69
|
+
...(0, detectContext_1.hasAnyGradleDep)(ctx, exports.LOCAL_STORE_GRADLE),
|
|
70
|
+
...(0, detectContext_1.hasAnySwiftDep)(ctx, exports.LOCAL_STORE_SWIFT),
|
|
71
|
+
],
|
|
72
|
+
uses: await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, exports.PLATFORM_LOCAL_STORES, limit),
|
|
73
|
+
};
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.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": {
|