@trainstar/synchro-react-native 0.3.2 → 0.4.0-rc.2
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/README.md +112 -7
- package/SynchroReactNative.podspec +18 -1
- package/android/build.gradle +5 -1
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +593 -0
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/TransactionSessionTest.kt +314 -0
- package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +328 -112
- package/ios/SynchroModule.mm +27 -4
- package/ios/SynchroModule.swift +251 -89
- package/ios-test-host/main.m +48 -0
- package/ios-tests/SynchroModuleTransactionTests.swift +709 -0
- package/ios-tests/TransactionSessionTests.swift +66 -0
- package/lib/module/NativeSynchro.js.map +1 -1
- package/lib/module/SynchroClient.js +141 -2
- package/lib/module/SynchroClient.js.map +1 -1
- package/lib/module/errors.js +15 -1
- package/lib/module/errors.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/inspection.js +99 -8
- package/lib/module/inspection.js.map +1 -1
- package/lib/module/types.js +18 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/example/e2e/schema-check.test.d.ts +2 -0
- package/lib/typescript/example/e2e/schema-check.test.d.ts.map +1 -0
- package/lib/typescript/example/src/conformance/runner.d.ts +17 -2
- package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
- package/lib/typescript/src/NativeSynchro.d.ts +7 -1
- package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
- package/lib/typescript/src/SynchroClient.d.ts +29 -1
- package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
- package/lib/typescript/src/errors.d.ts +7 -1
- package/lib/typescript/src/errors.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/inspection.d.ts +23 -5
- package/lib/typescript/src/inspection.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +62 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +21 -6
- package/src/NativeSynchro.ts +6 -1
- package/src/SynchroClient.ts +160 -1
- package/src/errors.ts +30 -0
- package/src/index.ts +7 -0
- package/src/inspection.ts +138 -9
- package/src/types.ts +70 -1
- package/tooling-patches/apply.cjs +163 -0
- package/tooling-patches/cli-clean-20.2.0.patch +21 -0
- package/tooling-patches/cli-config-android-20.2.0.patch +13 -0
- package/tooling-patches/react-native-builder-bob-0.40.18.patch +74 -0
- package/tooling-patches/stream-json-1.9.1.patch +40 -0
package/src/inspection.ts
CHANGED
|
@@ -2,14 +2,24 @@ import {
|
|
|
2
2
|
configureInspection,
|
|
3
3
|
nativeForInspection,
|
|
4
4
|
parseClientStateInspection,
|
|
5
|
+
parseRetainedMutationInspection,
|
|
6
|
+
parseRetainedRejectionInspection,
|
|
5
7
|
parseTransportObservationSnapshot,
|
|
6
8
|
SynchroClient,
|
|
7
9
|
} from './SynchroClient';
|
|
8
10
|
import { InvalidResponseError, mapNativeError } from './errors';
|
|
11
|
+
import { assertValidSQLiteBindParams } from './sqliteValues';
|
|
9
12
|
import type {
|
|
10
13
|
ClientStateInspection,
|
|
14
|
+
RetainedMutationInspection,
|
|
15
|
+
RetainedRejectionInspection,
|
|
16
|
+
Row,
|
|
17
|
+
SQLStatement,
|
|
11
18
|
TransportObservationSnapshot,
|
|
12
19
|
TransportOperationClass,
|
|
20
|
+
MigrationCheckpoint,
|
|
21
|
+
MigrationJournalInspection,
|
|
22
|
+
PhysicalSchemaColumnInspection,
|
|
13
23
|
} from './types';
|
|
14
24
|
|
|
15
25
|
const TRANSPORT_OPERATION_CLASSES: readonly TransportOperationClass[] = [
|
|
@@ -45,6 +55,25 @@ export interface DurableStateInspection {
|
|
|
45
55
|
}>;
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
/**
|
|
59
|
+
* One read-only snapshot of client state and retained details. A detail list is
|
|
60
|
+
* null when its record count exceeds the native capture bound. `applicationRows`
|
|
61
|
+
* holds the rows that the requested read statements returned inside the snapshot.
|
|
62
|
+
*/
|
|
63
|
+
export interface ClientStateSnapshotInspection {
|
|
64
|
+
clientState: ClientStateInspection;
|
|
65
|
+
captureOverflowed: boolean;
|
|
66
|
+
retainedMutations: RetainedMutationInspection[] | null;
|
|
67
|
+
rejectedMutations: RetainedRejectionInspection[] | null;
|
|
68
|
+
applicationRows: Row[];
|
|
69
|
+
migrationJournal: MigrationJournalInspection | null;
|
|
70
|
+
migrationJournalTruncated: boolean;
|
|
71
|
+
physicalSchema: PhysicalSchemaColumnInspection[];
|
|
72
|
+
physicalSchemaTruncated: boolean;
|
|
73
|
+
acceptedMutationOutcomes: Record<string, string>;
|
|
74
|
+
acceptedMutationOutcomesTruncated: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
48
77
|
export interface SynchroInspectionOptions {
|
|
49
78
|
transportObservationCapacity?: number;
|
|
50
79
|
requireNewDatabase?: boolean;
|
|
@@ -62,9 +91,29 @@ export class SynchroInspection {
|
|
|
62
91
|
);
|
|
63
92
|
}
|
|
64
93
|
|
|
65
|
-
async
|
|
94
|
+
async captureSnapshot(rowStatements: SQLStatement[] = []): Promise<ClientStateSnapshotInspection> {
|
|
66
95
|
try {
|
|
67
|
-
|
|
96
|
+
const statements = rowStatements.map((statement) => ({
|
|
97
|
+
sql: statement.sql,
|
|
98
|
+
params: assertValidSQLiteBindParams(statement.params ?? []),
|
|
99
|
+
}));
|
|
100
|
+
const result = await nativeForInspection(this.client).inspectClientStateSnapshot(statements);
|
|
101
|
+
const snapshot = requireRecord(parseJSON(result.inspection), 'client state snapshot');
|
|
102
|
+
const extensions = parseCaptureExtensions(snapshot);
|
|
103
|
+
const clientState = requireRecord(snapshot.client_state, 'client state');
|
|
104
|
+
if (typeof clientState.capture_overflowed !== 'boolean') {
|
|
105
|
+
throw new InvalidResponseError('Native bridge returned invalid capture bound');
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
...extensions,
|
|
109
|
+
clientState: parseClientStateInspection(clientState),
|
|
110
|
+
captureOverflowed: clientState.capture_overflowed,
|
|
111
|
+
retainedMutations:
|
|
112
|
+
nullableArray(snapshot.retained_mutations, 'retained mutations')?.map(parseRetainedMutationInspection) ?? null,
|
|
113
|
+
rejectedMutations:
|
|
114
|
+
nullableArray(snapshot.rejected_mutations, 'rejected mutations')?.map(parseRetainedRejectionInspection) ?? null,
|
|
115
|
+
applicationRows: [...result.applicationRows] as Row[],
|
|
116
|
+
};
|
|
68
117
|
} catch (error) {
|
|
69
118
|
throw mapNativeError(error);
|
|
70
119
|
}
|
|
@@ -105,8 +154,8 @@ export class SynchroInspection {
|
|
|
105
154
|
}
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
async armTransportPause(operationClass: TransportOperationClass): Promise<void> {
|
|
109
|
-
|
|
157
|
+
async armTransportPause(operationClass: TransportOperationClass | MigrationCheckpoint): Promise<void> {
|
|
158
|
+
requirePauseTarget(operationClass);
|
|
110
159
|
try {
|
|
111
160
|
await nativeForInspection(this.client).armTransportPause(operationClass);
|
|
112
161
|
} catch (error) {
|
|
@@ -114,8 +163,8 @@ export class SynchroInspection {
|
|
|
114
163
|
}
|
|
115
164
|
}
|
|
116
165
|
|
|
117
|
-
async awaitTransportPause(operationClass: TransportOperationClass, timeoutMs: number): Promise<void> {
|
|
118
|
-
|
|
166
|
+
async awaitTransportPause(operationClass: TransportOperationClass | MigrationCheckpoint, timeoutMs: number): Promise<void> {
|
|
167
|
+
requirePauseTarget(operationClass);
|
|
119
168
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000) {
|
|
120
169
|
throw new InvalidResponseError('Transport pause request is invalid');
|
|
121
170
|
}
|
|
@@ -187,6 +236,16 @@ function parseDurableState(value: unknown): DurableStateInspection {
|
|
|
187
236
|
return proof as unknown as DurableStateInspection;
|
|
188
237
|
}
|
|
189
238
|
|
|
239
|
+
function nullableArray(value: unknown, name: string): unknown[] | null {
|
|
240
|
+
if (value === null) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
if (!Array.isArray(value)) {
|
|
244
|
+
throw new InvalidResponseError(`Native bridge returned invalid ${name}`);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
190
249
|
function requireRecord(value: unknown, name: string): Record<string, unknown> {
|
|
191
250
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
192
251
|
throw new InvalidResponseError(`Native bridge returned invalid ${name}`);
|
|
@@ -206,10 +265,77 @@ function isNullableString(value: unknown): value is string | null {
|
|
|
206
265
|
return value === null || typeof value === 'string';
|
|
207
266
|
}
|
|
208
267
|
|
|
209
|
-
function
|
|
210
|
-
if (
|
|
211
|
-
|
|
268
|
+
function requirePauseTarget(value: TransportOperationClass | MigrationCheckpoint): void {
|
|
269
|
+
if (value !== 'migration_prepared' && value !== 'migration_committed' &&
|
|
270
|
+
!TRANSPORT_OPERATION_CLASSES.includes(value as TransportOperationClass)) {
|
|
271
|
+
throw new InvalidResponseError('Pause target is invalid');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseCaptureExtensions(snapshot: Record<string, unknown>): Pick<ClientStateSnapshotInspection,
|
|
276
|
+
'migrationJournal' | 'migrationJournalTruncated' | 'physicalSchema' | 'physicalSchemaTruncated' |
|
|
277
|
+
'acceptedMutationOutcomes' | 'acceptedMutationOutcomesTruncated'> {
|
|
278
|
+
if (typeof snapshot.migration_journal_truncated !== 'boolean' ||
|
|
279
|
+
typeof snapshot.physical_schema_truncated !== 'boolean' || !Array.isArray(snapshot.physical_schema) ||
|
|
280
|
+
snapshot.physical_schema.length > 512) {
|
|
281
|
+
throw new InvalidResponseError('Native bridge returned invalid migration capture');
|
|
282
|
+
}
|
|
283
|
+
const journal = snapshot.migration_journal;
|
|
284
|
+
if (journal !== null) {
|
|
285
|
+
const value = requireRecord(journal, 'migration journal');
|
|
286
|
+
for (const key of ['source', 'target']) {
|
|
287
|
+
const schema = requireRecord(value[key], 'migration schema');
|
|
288
|
+
if (!isNonnegativeSafeInteger(schema.version) || typeof schema.hash !== 'string') {
|
|
289
|
+
throw new InvalidResponseError('Native bridge returned invalid migration schema');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const stored = requireRecord(value.stored, 'stored migration bindings');
|
|
293
|
+
if (typeof value.action !== 'string' || typeof value.phase !== 'string' ||
|
|
294
|
+
Object.values(stored).some((item) => typeof item !== 'string') ||
|
|
295
|
+
['journal_version', 'target_manifest_json', 'affected_scopes_json', 'scope_cursor_updates_json',
|
|
296
|
+
'migration_plan_version', 'migration_plan_json', 'migration_plan_hash'].some((key) => typeof stored[key] !== 'string')) {
|
|
297
|
+
throw new InvalidResponseError('Native bridge returned invalid migration journal');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const physicalSchema = snapshot.physical_schema.map((item) => {
|
|
301
|
+
const column = requireRecord(item, 'physical schema column');
|
|
302
|
+
if (typeof column.table_name !== 'string' || typeof column.name !== 'string' || typeof column.type !== 'string' ||
|
|
303
|
+
typeof column.not_null !== 'boolean' || !isNonnegativeSafeInteger(column.primary_key_position)) {
|
|
304
|
+
throw new InvalidResponseError('Native bridge returned invalid physical schema column');
|
|
305
|
+
}
|
|
306
|
+
return column as unknown as PhysicalSchemaColumnInspection;
|
|
307
|
+
});
|
|
308
|
+
if (snapshot.migration_journal_truncated && journal !== null) {
|
|
309
|
+
throw new InvalidResponseError('Native bridge returned truncated migration details');
|
|
310
|
+
}
|
|
311
|
+
const accepted = requireRecord(snapshot.accepted_mutation_outcomes, 'accepted mutation outcomes');
|
|
312
|
+
const entries = Object.entries(accepted);
|
|
313
|
+
if (typeof snapshot.accepted_mutation_outcomes_truncated !== 'boolean' || entries.length > 512 ||
|
|
314
|
+
(snapshot.accepted_mutation_outcomes_truncated && entries.length !== 0)) {
|
|
315
|
+
throw new InvalidResponseError('Native bridge returned invalid accepted outcome capture');
|
|
316
|
+
}
|
|
317
|
+
let acceptedBytes = 0;
|
|
318
|
+
for (const [id, outcome] of entries) {
|
|
319
|
+
if (typeof outcome !== 'string') throw new InvalidResponseError('Native bridge returned invalid accepted outcome');
|
|
320
|
+
for (const text of [id, outcome]) {
|
|
321
|
+
for (const character of text) {
|
|
322
|
+
const first = character.charCodeAt(0);
|
|
323
|
+
if (character.length === 1 && first >= 0xd800 && first <= 0xdfff) {
|
|
324
|
+
throw new InvalidResponseError('Native bridge returned invalid accepted outcome encoding');
|
|
325
|
+
}
|
|
326
|
+
acceptedBytes += character.length === 2 ? 4 : first <= 0x7f ? 1 : first <= 0x7ff ? 2 : 3;
|
|
327
|
+
if (acceptedBytes > 65_536) throw new InvalidResponseError('Native bridge returned oversized accepted outcomes');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
212
330
|
}
|
|
331
|
+
return {
|
|
332
|
+
migrationJournal: journal as MigrationJournalInspection | null,
|
|
333
|
+
migrationJournalTruncated: snapshot.migration_journal_truncated,
|
|
334
|
+
physicalSchema,
|
|
335
|
+
physicalSchemaTruncated: snapshot.physical_schema_truncated,
|
|
336
|
+
acceptedMutationOutcomes: accepted as Record<string, string>,
|
|
337
|
+
acceptedMutationOutcomesTruncated: snapshot.accepted_mutation_outcomes_truncated,
|
|
338
|
+
};
|
|
213
339
|
}
|
|
214
340
|
|
|
215
341
|
export { sha256Hex } from './digest';
|
|
@@ -223,4 +349,7 @@ export type {
|
|
|
223
349
|
TransportObservation,
|
|
224
350
|
TransportObservationSnapshot,
|
|
225
351
|
TransportOperationClass,
|
|
352
|
+
MigrationCheckpoint,
|
|
353
|
+
MigrationJournalInspection,
|
|
354
|
+
PhysicalSchemaColumnInspection,
|
|
226
355
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -186,7 +186,8 @@ export type MutationRejectionCode =
|
|
|
186
186
|
| 'schema_incompatible'
|
|
187
187
|
| 'policy_rejected'
|
|
188
188
|
| 'validation_failed'
|
|
189
|
-
| 'table_not_synced'
|
|
189
|
+
| 'table_not_synced'
|
|
190
|
+
| 'atomic_batch_rejected';
|
|
190
191
|
|
|
191
192
|
export interface SchemaRef {
|
|
192
193
|
version: number;
|
|
@@ -199,6 +200,7 @@ export interface AuthoredMutationField {
|
|
|
199
200
|
value: JSONValue;
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
/** A retained mutation with its complete ledger binding and authored values. */
|
|
202
204
|
export interface PendingMutationInspection {
|
|
203
205
|
mutationID: string;
|
|
204
206
|
localOrder: number;
|
|
@@ -220,6 +222,29 @@ export interface PendingMutationInspection {
|
|
|
220
222
|
authoredFields: AuthoredMutationField[];
|
|
221
223
|
}
|
|
222
224
|
|
|
225
|
+
/**
|
|
226
|
+
* A retained mutation that a queue from before the mutation ledger imported.
|
|
227
|
+
* It has only the fields that the old queue stored.
|
|
228
|
+
*/
|
|
229
|
+
export interface LegacyMutationInspection {
|
|
230
|
+
representation: 'legacy';
|
|
231
|
+
mutationID: string;
|
|
232
|
+
localOrder: number;
|
|
233
|
+
tableName: string;
|
|
234
|
+
recordID: string;
|
|
235
|
+
operation: MutationOperation;
|
|
236
|
+
baseVersion: string | null;
|
|
237
|
+
clientVersion: string;
|
|
238
|
+
status: LocalMutationStatus;
|
|
239
|
+
sourceKind: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** One retained mutation in its stored representation. */
|
|
243
|
+
export type RetainedMutationInspection =
|
|
244
|
+
| (PendingMutationInspection & { representation: 'current' })
|
|
245
|
+
| LegacyMutationInspection;
|
|
246
|
+
|
|
247
|
+
/** A retained rejection with its exact mutation and rejection JSON. */
|
|
223
248
|
export interface RejectedMutationInspection {
|
|
224
249
|
mutationID: string;
|
|
225
250
|
tableName: string;
|
|
@@ -235,6 +260,29 @@ export interface RejectedMutationInspection {
|
|
|
235
260
|
updatedAt: string;
|
|
236
261
|
}
|
|
237
262
|
|
|
263
|
+
/**
|
|
264
|
+
* A retained rejection that a database from before the mutation ledger stored.
|
|
265
|
+
* It has only the fields that the old rejection table stored.
|
|
266
|
+
*/
|
|
267
|
+
export interface LegacyRejectionInspection {
|
|
268
|
+
representation: 'legacy';
|
|
269
|
+
mutationID: string;
|
|
270
|
+
tableName: string;
|
|
271
|
+
recordID: string;
|
|
272
|
+
status: MutationStatus;
|
|
273
|
+
code: MutationRejectionCode;
|
|
274
|
+
message: string | null;
|
|
275
|
+
serverRowJSON: string | null;
|
|
276
|
+
serverVersion: string | null;
|
|
277
|
+
createdAt: string;
|
|
278
|
+
updatedAt: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** One retained rejection in its stored representation. */
|
|
282
|
+
export type RetainedRejectionInspection =
|
|
283
|
+
| (RejectedMutationInspection & { representation: 'current' })
|
|
284
|
+
| LegacyRejectionInspection;
|
|
285
|
+
|
|
238
286
|
export interface ScopeStateInspection {
|
|
239
287
|
scopeID: string;
|
|
240
288
|
cursor: string | null;
|
|
@@ -281,6 +329,24 @@ export interface ClientStateInspection {
|
|
|
281
329
|
provenanceMaintenanceWorkCursor: string;
|
|
282
330
|
}
|
|
283
331
|
|
|
332
|
+
export type MigrationCheckpoint = 'migration_prepared' | 'migration_committed';
|
|
333
|
+
|
|
334
|
+
export interface MigrationJournalInspection {
|
|
335
|
+
source: SchemaRef;
|
|
336
|
+
target: SchemaRef;
|
|
337
|
+
action: string;
|
|
338
|
+
phase: string;
|
|
339
|
+
stored: Record<string, string>;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export interface PhysicalSchemaColumnInspection {
|
|
343
|
+
table_name: string;
|
|
344
|
+
name: string;
|
|
345
|
+
type: string;
|
|
346
|
+
not_null: boolean;
|
|
347
|
+
primary_key_position: number;
|
|
348
|
+
}
|
|
349
|
+
|
|
284
350
|
export type TransportOperationClass =
|
|
285
351
|
| 'connect'
|
|
286
352
|
| 'pull'
|
|
@@ -294,12 +360,15 @@ export interface TransportObservation {
|
|
|
294
360
|
sequence: number;
|
|
295
361
|
operationClass: TransportOperationClass;
|
|
296
362
|
statusCode: number;
|
|
363
|
+
errorCode?: string;
|
|
364
|
+
retryable?: boolean;
|
|
297
365
|
durationNanoseconds: number;
|
|
298
366
|
cursorFingerprints?: string[];
|
|
299
367
|
cursorFingerprintsComplete?: boolean;
|
|
300
368
|
requestFacts?: Record<string, JSONValue>;
|
|
301
369
|
rebuildResponseFacts?: Record<string, JSONValue>;
|
|
302
370
|
pullResponseFacts?: Record<string, JSONValue>;
|
|
371
|
+
connectResponseFacts?: Record<string, JSONValue>;
|
|
303
372
|
}
|
|
304
373
|
|
|
305
374
|
export interface TransportObservationSnapshot {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const {spawnSync} = require('child_process');
|
|
7
|
+
|
|
8
|
+
const appRoot = fs.realpathSync(process.cwd());
|
|
9
|
+
|
|
10
|
+
const repairs = {
|
|
11
|
+
cli: [{
|
|
12
|
+
caller: '@react-native-community/cli-platform-android',
|
|
13
|
+
callerVersion: '20.2.0',
|
|
14
|
+
packageName: '@react-native-community/cli-config-android',
|
|
15
|
+
version: '20.2.0',
|
|
16
|
+
patch: 'cli-config-android-20.2.0.patch',
|
|
17
|
+
sources: [{
|
|
18
|
+
path: 'build/config/findComponentDescriptors.js',
|
|
19
|
+
preimage: '2cfe70a25818c4cd61fc8c122602f5363b8d57ffc1cf8c153f26fe68f1b8f047',
|
|
20
|
+
postimage: '75ace921b876fb1164e2b80dda9045bfebf2972b9465da992bba5758452add94'
|
|
21
|
+
}]
|
|
22
|
+
}, {
|
|
23
|
+
caller: '@react-native-community/cli',
|
|
24
|
+
callerVersion: '20.2.0',
|
|
25
|
+
packageName: '@react-native-community/cli-clean',
|
|
26
|
+
version: '20.2.0',
|
|
27
|
+
patch: 'cli-clean-20.2.0.patch',
|
|
28
|
+
sources: [{
|
|
29
|
+
path: 'build/clean.js',
|
|
30
|
+
preimage: 'f46f188859df1d616676d5cb246ed0b96b8f5e71868c5df914d31830acdde4be',
|
|
31
|
+
postimage: '9aea3a5112e0432ec78f5716d91ceac56d35e2b24eb5c45f4f28d254b9eaf9c2'
|
|
32
|
+
}]
|
|
33
|
+
}],
|
|
34
|
+
'stream-json': [{
|
|
35
|
+
caller: 'bunyamin',
|
|
36
|
+
callerVersion: '1.6.3',
|
|
37
|
+
packageName: 'stream-json',
|
|
38
|
+
version: '1.9.1',
|
|
39
|
+
patch: 'stream-json-1.9.1.patch',
|
|
40
|
+
sources: [{
|
|
41
|
+
path: 'Assembler.js',
|
|
42
|
+
preimage: 'a341b21d86a9c005e5433eadb6c33d4708d611c8bc624ee383463c0fb6d2e32b',
|
|
43
|
+
postimage: '51bf2168f777c8c7285dd521c32f0de636f363efc9baa80dbfb140fa55372203'
|
|
44
|
+
}]
|
|
45
|
+
}],
|
|
46
|
+
bob: [{
|
|
47
|
+
caller: 'react-native-builder-bob',
|
|
48
|
+
callerVersion: '0.40.18',
|
|
49
|
+
packageName: 'react-native-builder-bob',
|
|
50
|
+
version: '0.40.18',
|
|
51
|
+
patch: 'react-native-builder-bob-0.40.18.patch',
|
|
52
|
+
sources: [{
|
|
53
|
+
path: 'lib/targets/module.js',
|
|
54
|
+
preimage: '1ef8ee20f2767347348e86de7f421358b7705623ae04a9b952fabc61f365ede6',
|
|
55
|
+
postimage: '16686f186b97a7a83fa6fc761db191a623d0f5697acb45b8df907e343c7629fa'
|
|
56
|
+
}, {
|
|
57
|
+
path: 'lib/targets/typescript.js',
|
|
58
|
+
preimage: '451fe8050e20bc1c4724564b2c33fb819f815848f5ad2eb529be5915bf62f2bb',
|
|
59
|
+
postimage: 'd22c16e9d4ebe97b15a3b734b70e1060a5526b907cad2b1ec3bb1004d4ed0675'
|
|
60
|
+
}]
|
|
61
|
+
}]
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function fail(message) {
|
|
65
|
+
throw new Error(`Synchro tooling patch: ${message}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolvedPackageRoot(request, paths) {
|
|
69
|
+
const packageJson = require.resolve(`${request}/package.json`, {paths});
|
|
70
|
+
return fs.realpathSync(path.dirname(packageJson));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function assertInsideInstall(target) {
|
|
74
|
+
const relative = path.relative(appRoot, target);
|
|
75
|
+
if (relative === '' || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
76
|
+
fail('resolved target is outside the caller installation');
|
|
77
|
+
}
|
|
78
|
+
if (!relative.split(path.sep).includes('node_modules')) {
|
|
79
|
+
fail('resolved target is not in node_modules');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function packageIdentity(packageRoot, expectedName, expectedVersion) {
|
|
84
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
85
|
+
if (manifest.name !== expectedName) {
|
|
86
|
+
fail('unexpected package identity');
|
|
87
|
+
}
|
|
88
|
+
if (manifest.version !== expectedVersion) {
|
|
89
|
+
fail('unexpected package version');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hashFile(file) {
|
|
94
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function callerRoot(repair) {
|
|
98
|
+
if (repair.caller === 'bunyamin') {
|
|
99
|
+
const entry = require.resolve('bunyamin', {paths: [appRoot]});
|
|
100
|
+
let directory = path.dirname(fs.realpathSync(entry));
|
|
101
|
+
while (directory !== path.dirname(directory)) {
|
|
102
|
+
const manifest = path.join(directory, 'package.json');
|
|
103
|
+
if (fs.existsSync(manifest) && JSON.parse(fs.readFileSync(manifest, 'utf8')).name === 'bunyamin') {
|
|
104
|
+
return directory;
|
|
105
|
+
}
|
|
106
|
+
directory = path.dirname(directory);
|
|
107
|
+
}
|
|
108
|
+
fail('could not locate the resolved bunyamin package root');
|
|
109
|
+
}
|
|
110
|
+
return resolvedPackageRoot(repair.caller, [appRoot]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function targetRoot(repair, caller) {
|
|
114
|
+
if (repair.caller === 'bunyamin') {
|
|
115
|
+
const source = require.resolve('stream-json/Assembler.js', {paths: [caller]});
|
|
116
|
+
return fs.realpathSync(path.dirname(source));
|
|
117
|
+
}
|
|
118
|
+
return resolvedPackageRoot(repair.packageName, [caller]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function applyRepair(repair) {
|
|
122
|
+
const caller = callerRoot(repair);
|
|
123
|
+
assertInsideInstall(caller);
|
|
124
|
+
packageIdentity(caller, repair.caller, repair.callerVersion);
|
|
125
|
+
const target = targetRoot(repair, caller);
|
|
126
|
+
assertInsideInstall(target);
|
|
127
|
+
packageIdentity(target, repair.packageName, repair.version);
|
|
128
|
+
const sources = repair.sources.map(source => {
|
|
129
|
+
const file = path.join(target, source.path);
|
|
130
|
+
if (!fs.existsSync(file) || fs.realpathSync(file) !== file) {
|
|
131
|
+
fail('unexpected or missing source target');
|
|
132
|
+
}
|
|
133
|
+
return {file, source};
|
|
134
|
+
});
|
|
135
|
+
if (sources.every(({file, source}) => hashFile(file) === source.postimage)) {
|
|
136
|
+
process.stdout.write(`${repair.packageName} ${repair.version} already has the verified patch\n`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!sources.every(({file, source}) => hashFile(file) === source.preimage)) {
|
|
140
|
+
fail('unexpected source hash');
|
|
141
|
+
}
|
|
142
|
+
const patchFile = path.join(__dirname, repair.patch);
|
|
143
|
+
const result = spawnSync('patch', ['--batch', '--forward', '--fuzz=0', '-p1', '-d', target], {
|
|
144
|
+
input: fs.readFileSync(patchFile),
|
|
145
|
+
encoding: 'utf8',
|
|
146
|
+
stdio: ['pipe', 'inherit', 'inherit']
|
|
147
|
+
});
|
|
148
|
+
if (result.error || result.status !== 0) {
|
|
149
|
+
fail('patch command failed');
|
|
150
|
+
}
|
|
151
|
+
if (!sources.every(({file, source}) => hashFile(file) === source.postimage)) {
|
|
152
|
+
fail('postimage verification failed');
|
|
153
|
+
}
|
|
154
|
+
process.stdout.write(`applied verified ${repair.packageName} ${repair.version} patch\n`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const name = process.argv[2] || 'cli';
|
|
158
|
+
if (!Object.prototype.hasOwnProperty.call(repairs, name)) {
|
|
159
|
+
fail('unsupported repair');
|
|
160
|
+
}
|
|
161
|
+
for (const repair of repairs[name]) {
|
|
162
|
+
applyRepair(repair);
|
|
163
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
diff --git a/build/clean.js b/build/clean.js
|
|
2
|
+
index 0cad23b..942fb42 100644
|
|
3
|
+
--- a/build/clean.js
|
|
4
|
+
+++ b/build/clean.js
|
|
5
|
+
@@ -157,13 +157,13 @@ async function clean(_argv, ctx, cleanOptions) {
|
|
6
|
+
description: 'Metro, haste-map caches',
|
|
7
|
+
tasks: [{
|
|
8
|
+
label: 'Clean Metro cache',
|
|
9
|
+
- action: () => cleanDir(`${_os().default.tmpdir()}/metro-*`)
|
|
10
|
+
+ action: () => cleanDir(`${_fastGlob().default.convertPathToPattern(_os().default.tmpdir())}/metro-*`)
|
|
11
|
+
}, {
|
|
12
|
+
label: 'Clean Haste cache',
|
|
13
|
+
- action: () => cleanDir(`${_os().default.tmpdir()}/haste-map-*`)
|
|
14
|
+
+ action: () => cleanDir(`${_fastGlob().default.convertPathToPattern(_os().default.tmpdir())}/haste-map-*`)
|
|
15
|
+
}, {
|
|
16
|
+
label: 'Clean React Native cache',
|
|
17
|
+
- action: () => cleanDir(`${_os().default.tmpdir()}/react-*`)
|
|
18
|
+
+ action: () => cleanDir(`${_fastGlob().default.convertPathToPattern(_os().default.tmpdir())}/react-*`)
|
|
19
|
+
}]
|
|
20
|
+
},
|
|
21
|
+
bun: {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
diff --git a/build/config/findComponentDescriptors.js b/build/config/findComponentDescriptors.js
|
|
2
|
+
index 36fdb7c..4e857d0 100644
|
|
3
|
+
--- a/build/config/findComponentDescriptors.js
|
|
4
|
+
+++ b/build/config/findComponentDescriptors.js
|
|
5
|
+
@@ -42,7 +42,7 @@ function findComponentDescriptors(packageRoot) {
|
|
6
|
+
} catch (error) {
|
|
7
|
+
// no jsSrcsDir, continue with default glob pattern
|
|
8
|
+
}
|
|
9
|
+
- const globPattern = jsSrcsDir ? `${jsSrcsDir}/**/*{.js,.jsx,.ts,.tsx}` : '**/*{.js,.jsx,.ts,.tsx}';
|
|
10
|
+
+ const globPattern = jsSrcsDir ? `${_fastGlob().default.convertPathToPattern(jsSrcsDir)}/**/*{.js,.jsx,.ts,.tsx}` : '**/*{.js,.jsx,.ts,.tsx}';
|
|
11
|
+
const files = _fastGlob().default.sync(globPattern, {
|
|
12
|
+
cwd: (0, _cliTools().unixifyPaths)(packageRoot),
|
|
13
|
+
onlyFiles: true,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
diff --git a/lib/targets/module.js b/lib/targets/module.js
|
|
2
|
+
--- a/lib/targets/module.js
|
|
3
|
+
+++ b/lib/targets/module.js
|
|
4
|
+
@@ -6,7 +6,7 @@
|
|
5
|
+
exports.default = build;
|
|
6
|
+
var _path = _interopRequireDefault(require("path"));
|
|
7
|
+
var _kleur = _interopRequireDefault(require("kleur"));
|
|
8
|
+
-var _del = _interopRequireDefault(require("del"));
|
|
9
|
+
+var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
10
|
+
var _compile = _interopRequireDefault(require("../utils/compile"));
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
async function build({
|
|
13
|
+
@@ -19,7 +19,11 @@
|
|
14
|
+
report
|
|
15
|
+
}) {
|
|
16
|
+
report.info(`Cleaning up previous build at ${_kleur.default.blue(_path.default.relative(root, output))}`);
|
|
17
|
+
- await (0, _del.default)([output]);
|
|
18
|
+
+ const outputRelative = _path.default.relative(process.cwd(), output);
|
|
19
|
+
+ if (outputRelative === '' || outputRelative === '..' || outputRelative.startsWith(`..${_path.default.sep}`) || _path.default.isAbsolute(outputRelative)) {
|
|
20
|
+
+ throw new Error('Refusing to remove build output outside the current working directory.');
|
|
21
|
+
+ }
|
|
22
|
+
+ await _fsExtra.default.remove(output);
|
|
23
|
+
await (0, _compile.default)({
|
|
24
|
+
...options,
|
|
25
|
+
variants,
|
|
26
|
+
diff --git a/lib/targets/typescript.js b/lib/targets/typescript.js
|
|
27
|
+
--- a/lib/targets/typescript.js
|
|
28
|
+
+++ b/lib/targets/typescript.js
|
|
29
|
+
@@ -4,7 +4,6 @@
|
|
30
|
+
value: true
|
|
31
|
+
});
|
|
32
|
+
exports.default = build;
|
|
33
|
+
-var _del = _interopRequireDefault(require("del"));
|
|
34
|
+
var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
35
|
+
var _json = _interopRequireDefault(require("json5"));
|
|
36
|
+
var _kleur = _interopRequireDefault(require("kleur"));
|
|
37
|
+
@@ -23,7 +22,11 @@
|
|
38
|
+
esm
|
|
39
|
+
}) {
|
|
40
|
+
report.info(`Cleaning up previous build at ${_kleur.default.blue(_path.default.relative(root, output))}`);
|
|
41
|
+
- await (0, _del.default)([output]);
|
|
42
|
+
+ const outputRelative = _path.default.relative(process.cwd(), output);
|
|
43
|
+
+ if (outputRelative === '' || outputRelative === '..' || outputRelative.startsWith(`..${_path.default.sep}`) || _path.default.isAbsolute(outputRelative)) {
|
|
44
|
+
+ throw new Error('Refusing to remove build output outside the current working directory.');
|
|
45
|
+
+ }
|
|
46
|
+
+ await _fsExtra.default.remove(output);
|
|
47
|
+
report.info(`Generating type definitions with ${_kleur.default.blue('tsc')}`);
|
|
48
|
+
const project = options?.project ? options.project : 'tsconfig.json';
|
|
49
|
+
const tsconfig = _path.default.join(root, project);
|
|
50
|
+
@@ -101,8 +104,12 @@
|
|
51
|
+
throw new Error('Neither commonjs nor module output is enabled.');
|
|
52
|
+
}
|
|
53
|
+
const tsbuildinfo = _path.default.join(outDir, project.replace(/\.json$/, '.tsbuildinfo'));
|
|
54
|
+
+ const buildInfoRelative = _path.default.relative(process.cwd(), tsbuildinfo);
|
|
55
|
+
+ const tsbuildinfoIsOwned = buildInfoRelative !== '' && buildInfoRelative !== '..' && !buildInfoRelative.startsWith(`..${_path.default.sep}`) && !_path.default.isAbsolute(buildInfoRelative);
|
|
56
|
+
try {
|
|
57
|
+
- await (0, _del.default)([tsbuildinfo]);
|
|
58
|
+
+ if (tsbuildinfoIsOwned) {
|
|
59
|
+
+ await _fsExtra.default.remove(tsbuildinfo);
|
|
60
|
+
+ }
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Ignore
|
|
63
|
+
}
|
|
64
|
+
@@ -110,7 +117,9 @@
|
|
65
|
+
cwd: root
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
- await (0, _del.default)([tsbuildinfo]);
|
|
69
|
+
+ if (tsbuildinfoIsOwned) {
|
|
70
|
+
+ await _fsExtra.default.remove(tsbuildinfo);
|
|
71
|
+
+ }
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// Ignore
|
|
74
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
diff --git a/Assembler.js b/Assembler.js
|
|
2
|
+
index 2a8bda9..3a0aace 100644
|
|
3
|
+
--- a/Assembler.js
|
|
4
|
+
+++ b/Assembler.js
|
|
5
|
+
@@ -120,7 +120,16 @@ class Assembler extends EventEmitter {
|
|
6
|
+
if (this.current instanceof Array) {
|
|
7
|
+
this.current.push(value);
|
|
8
|
+
} else {
|
|
9
|
+
- this.current[this.key] = value;
|
|
10
|
+
+ if (this.key === '__proto__') {
|
|
11
|
+
+ Object.defineProperty(this.current, this.key, {
|
|
12
|
+
+ value,
|
|
13
|
+
+ writable: true,
|
|
14
|
+
+ enumerable: true,
|
|
15
|
+
+ configurable: true
|
|
16
|
+
+ });
|
|
17
|
+
+ } else {
|
|
18
|
+
+ this.current[this.key] = value;
|
|
19
|
+
+ }
|
|
20
|
+
this.key = null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
@@ -138,7 +147,16 @@ class Assembler extends EventEmitter {
|
|
24
|
+
} else {
|
|
25
|
+
value = this.reviver(this.key, value);
|
|
26
|
+
if (value !== undefined) {
|
|
27
|
+
- this.current[this.key] = value;
|
|
28
|
+
+ if (this.key === '__proto__') {
|
|
29
|
+
+ Object.defineProperty(this.current, this.key, {
|
|
30
|
+
+ value,
|
|
31
|
+
+ writable: true,
|
|
32
|
+
+ enumerable: true,
|
|
33
|
+
+ configurable: true
|
|
34
|
+
+ });
|
|
35
|
+
+ } else {
|
|
36
|
+
+ this.current[this.key] = value;
|
|
37
|
+
+ }
|
|
38
|
+
}
|
|
39
|
+
this.key = null;
|
|
40
|
+
}
|