@produtype/core 0.75.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.
|
@@ -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,37 +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
|
-
/**
|
|
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
|
-
];
|
|
105
75
|
/** Dependencies that put a secret somewhere the operating system protects. */
|
|
106
76
|
const SECURE_STORAGE_DEPS = [
|
|
107
77
|
'flutter_secure_storage',
|
|
@@ -111,22 +81,6 @@ const SECURE_STORAGE_DEPS = [
|
|
|
111
81
|
'@capacitor/preferences',
|
|
112
82
|
'capacitor-secure-storage-plugin',
|
|
113
83
|
];
|
|
114
|
-
/** Dependencies whose whole purpose is that the app works with no network. */
|
|
115
|
-
const OFFLINE_DEPS = [
|
|
116
|
-
'sqflite',
|
|
117
|
-
'drift',
|
|
118
|
-
'hive',
|
|
119
|
-
'isar',
|
|
120
|
-
'objectbox',
|
|
121
|
-
'realm',
|
|
122
|
-
'watermelondb',
|
|
123
|
-
'@nozbe/watermelondb',
|
|
124
|
-
'react-native-mmkv',
|
|
125
|
-
'@react-native-async-storage/async-storage',
|
|
126
|
-
'redux-persist',
|
|
127
|
-
'@tanstack/query-persist-client-core',
|
|
128
|
-
'powersync',
|
|
129
|
-
];
|
|
130
84
|
/**
|
|
131
85
|
* Paths that contain a platform marker without being a project.
|
|
132
86
|
*
|
|
@@ -323,19 +277,15 @@ async function detectMobile(ctx) {
|
|
|
323
277
|
line: match.line,
|
|
324
278
|
})),
|
|
325
279
|
];
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
...(0, detectContext_1.hasAnyDartDep)(ctx, OFFLINE_DEPS),
|
|
329
|
-
...(0, detectContext_1.hasAnyGradleDep)(ctx, OFFLINE_GRADLE),
|
|
330
|
-
...(0, detectContext_1.hasAnySwiftDep)(ctx, OFFLINE_SWIFT),
|
|
331
|
-
];
|
|
280
|
+
const localStores = await (0, localStores_1.readLocalStores)(ctx);
|
|
281
|
+
const offlineDeps = localStores.dependencies;
|
|
332
282
|
/**
|
|
333
283
|
* A local database is the strong signal; knowing the network dropped is the weak
|
|
334
284
|
* one. Both are recorded, because an app that stores nothing but tells the user the
|
|
335
285
|
* connection is gone is a different thing from one that shows a spinner forever.
|
|
336
286
|
*/
|
|
337
287
|
const connectivityChecks = await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source, [/Connectivity\(\)/, /connectivity_plus/, /NetInfo\./, /navigator\.onLine/, /NWPathMonitor/, /isReachable/], 3);
|
|
338
|
-
const platformStores =
|
|
288
|
+
const platformStores = localStores.uses;
|
|
339
289
|
const offlineEvidence = [
|
|
340
290
|
...offlineDeps.map((dep) => ({ type: 'dependency', value: dep })),
|
|
341
291
|
...platformStores.map((match) => ({
|
|
@@ -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": {
|