@trainstar/synchro-react-native 0.4.0-rc.1 → 0.4.0-rc.3
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 +62 -11
- package/SynchroReactNative.podspec +13 -1
- package/android/build.gradle +1 -1
- package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +10 -11
- package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +22 -2
- package/ios/SynchroModule.swift +292 -270
- package/ios-test-host/main.m +48 -0
- package/ios-tests/SynchroModuleTransactionTests.swift +267 -1
- package/lib/module/NativeSynchro.js.map +1 -1
- package/lib/module/SynchroClient.js +26 -0
- package/lib/module/SynchroClient.js.map +1 -1
- package/lib/module/inspection.js +69 -6
- package/lib/module/inspection.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 +16 -2
- package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
- package/lib/typescript/src/NativeSynchro.d.ts +13 -14
- package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
- package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
- package/lib/typescript/src/inspection.d.ts +11 -4
- package/lib/typescript/src/inspection.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +18 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +24 -9
- package/src/NativeSynchro.ts +13 -17
- package/src/SynchroClient.ts +32 -0
- package/src/inspection.ts +95 -8
- package/src/types.ts +21 -0
- 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/SynchroClient.ts
CHANGED
|
@@ -691,6 +691,14 @@ function parseTransportObservation(value: unknown, index: number): TransportObse
|
|
|
691
691
|
if (!isRecord(value) || !TRANSPORT_OPERATION_CLASSES.includes(value.operation_class as TransportOperationClass)) {
|
|
692
692
|
throw new InvalidResponseError(`Native bridge returned an invalid ${name}`);
|
|
693
693
|
}
|
|
694
|
+
const errorCode = value.error_code;
|
|
695
|
+
if (errorCode !== undefined && typeof errorCode !== 'string') {
|
|
696
|
+
throw new InvalidResponseError(`Native bridge returned invalid ${name} error code`);
|
|
697
|
+
}
|
|
698
|
+
const retryable = value.retryable;
|
|
699
|
+
if (retryable !== undefined && typeof retryable !== 'boolean') {
|
|
700
|
+
throw new InvalidResponseError(`Native bridge returned invalid ${name} retryability`);
|
|
701
|
+
}
|
|
694
702
|
const cursorFingerprints = value.cursor_fingerprints;
|
|
695
703
|
if (cursorFingerprints !== undefined && (!Array.isArray(cursorFingerprints) || !cursorFingerprints.every((item) => typeof item === 'string'))) {
|
|
696
704
|
throw new InvalidResponseError(`Native bridge returned invalid ${name} cursor fingerprints`);
|
|
@@ -699,16 +707,40 @@ function parseTransportObservation(value: unknown, index: number): TransportObse
|
|
|
699
707
|
if (cursorFingerprintsComplete !== undefined && typeof cursorFingerprintsComplete !== 'boolean') {
|
|
700
708
|
throw new InvalidResponseError(`Native bridge returned invalid ${name} cursor completeness`);
|
|
701
709
|
}
|
|
710
|
+
const connectFacts = optionalTransportFacts(value.connect_response_facts, `${name} connect response facts`);
|
|
711
|
+
if (connectFacts !== undefined) {
|
|
712
|
+
const affected = connectFacts.affected_scope_fingerprints;
|
|
713
|
+
const updates = connectFacts.scope_cursor_updates;
|
|
714
|
+
const hashPattern = /^[0-9a-f]{64}$/;
|
|
715
|
+
if (
|
|
716
|
+
value.operation_class !== 'connect' || value.status_code !== 200 ||
|
|
717
|
+
Object.keys(connectFacts).length !== 7 ||
|
|
718
|
+
!['none', 'replace', 'rebuild_local', 'unsupported'].includes(connectFacts.action as string) ||
|
|
719
|
+
typeof connectFacts.schema_version !== 'number' || !Number.isSafeInteger(connectFacts.schema_version) || connectFacts.schema_version <= 0 ||
|
|
720
|
+
typeof connectFacts.schema_hash !== 'string' || !hashPattern.test(connectFacts.schema_hash) ||
|
|
721
|
+
!Array.isArray(affected) || affected.length > 16 ||
|
|
722
|
+
!affected.every((item, position) => typeof item === 'string' && hashPattern.test(item) && (position === 0 || (affected[position - 1] as string) < item)) ||
|
|
723
|
+
typeof connectFacts.affected_scopes_complete !== 'boolean' ||
|
|
724
|
+
!isRecord(updates) || Object.keys(updates).length > 16 ||
|
|
725
|
+
!Object.entries(updates).every(([scope, cursor]) => hashPattern.test(scope) && (cursor === null || typeof cursor === 'string' && hashPattern.test(cursor))) ||
|
|
726
|
+
typeof connectFacts.scope_cursor_updates_complete !== 'boolean'
|
|
727
|
+
) {
|
|
728
|
+
throw new InvalidResponseError(`Native bridge returned invalid ${name} connect response facts`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
702
731
|
return {
|
|
703
732
|
sequence: requiredSafeInteger(value.sequence, `${name} sequence`),
|
|
704
733
|
operationClass: value.operation_class as TransportOperationClass,
|
|
705
734
|
statusCode: requiredSafeInteger(value.status_code, `${name} status code`),
|
|
735
|
+
...(errorCode === undefined ? {} : { errorCode }),
|
|
736
|
+
...(retryable === undefined ? {} : { retryable }),
|
|
706
737
|
durationNanoseconds: requiredSafeInteger(value.duration_nanoseconds, `${name} duration`),
|
|
707
738
|
...(cursorFingerprints === undefined ? {} : { cursorFingerprints: [...cursorFingerprints] as string[] }),
|
|
708
739
|
...(cursorFingerprintsComplete === undefined ? {} : { cursorFingerprintsComplete }),
|
|
709
740
|
...(value.request_facts == null ? {} : { requestFacts: optionalTransportRequestFacts(value.request_facts, `${name} request facts`)! }),
|
|
710
741
|
...(value.rebuild_response_facts == null ? {} : { rebuildResponseFacts: optionalTransportFacts(value.rebuild_response_facts, `${name} rebuild response facts`)! }),
|
|
711
742
|
...(value.pull_response_facts == null ? {} : { pullResponseFacts: optionalTransportFacts(value.pull_response_facts, `${name} pull response facts`)! }),
|
|
743
|
+
...(connectFacts === undefined ? {} : { connectResponseFacts: connectFacts }),
|
|
712
744
|
};
|
|
713
745
|
}
|
|
714
746
|
|
package/src/inspection.ts
CHANGED
|
@@ -17,6 +17,9 @@ import type {
|
|
|
17
17
|
SQLStatement,
|
|
18
18
|
TransportObservationSnapshot,
|
|
19
19
|
TransportOperationClass,
|
|
20
|
+
MigrationCheckpoint,
|
|
21
|
+
MigrationJournalInspection,
|
|
22
|
+
PhysicalSchemaColumnInspection,
|
|
20
23
|
} from './types';
|
|
21
24
|
|
|
22
25
|
const TRANSPORT_OPERATION_CLASSES: readonly TransportOperationClass[] = [
|
|
@@ -59,9 +62,16 @@ export interface DurableStateInspection {
|
|
|
59
62
|
*/
|
|
60
63
|
export interface ClientStateSnapshotInspection {
|
|
61
64
|
clientState: ClientStateInspection;
|
|
65
|
+
captureOverflowed: boolean;
|
|
62
66
|
retainedMutations: RetainedMutationInspection[] | null;
|
|
63
67
|
rejectedMutations: RetainedRejectionInspection[] | null;
|
|
64
68
|
applicationRows: Row[];
|
|
69
|
+
migrationJournal: MigrationJournalInspection | null;
|
|
70
|
+
migrationJournalTruncated: boolean;
|
|
71
|
+
physicalSchema: PhysicalSchemaColumnInspection[];
|
|
72
|
+
physicalSchemaTruncated: boolean;
|
|
73
|
+
acceptedMutationOutcomes: Record<string, string>;
|
|
74
|
+
acceptedMutationOutcomesTruncated: boolean;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
export interface SynchroInspectionOptions {
|
|
@@ -89,8 +99,15 @@ export class SynchroInspection {
|
|
|
89
99
|
}));
|
|
90
100
|
const result = await nativeForInspection(this.client).inspectClientStateSnapshot(statements);
|
|
91
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
|
+
}
|
|
92
107
|
return {
|
|
93
|
-
|
|
108
|
+
...extensions,
|
|
109
|
+
clientState: parseClientStateInspection(clientState),
|
|
110
|
+
captureOverflowed: clientState.capture_overflowed,
|
|
94
111
|
retainedMutations:
|
|
95
112
|
nullableArray(snapshot.retained_mutations, 'retained mutations')?.map(parseRetainedMutationInspection) ?? null,
|
|
96
113
|
rejectedMutations:
|
|
@@ -137,8 +154,8 @@ export class SynchroInspection {
|
|
|
137
154
|
}
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
async armTransportPause(operationClass: TransportOperationClass): Promise<void> {
|
|
141
|
-
|
|
157
|
+
async armTransportPause(operationClass: TransportOperationClass | MigrationCheckpoint): Promise<void> {
|
|
158
|
+
requirePauseTarget(operationClass);
|
|
142
159
|
try {
|
|
143
160
|
await nativeForInspection(this.client).armTransportPause(operationClass);
|
|
144
161
|
} catch (error) {
|
|
@@ -146,8 +163,8 @@ export class SynchroInspection {
|
|
|
146
163
|
}
|
|
147
164
|
}
|
|
148
165
|
|
|
149
|
-
async awaitTransportPause(operationClass: TransportOperationClass, timeoutMs: number): Promise<void> {
|
|
150
|
-
|
|
166
|
+
async awaitTransportPause(operationClass: TransportOperationClass | MigrationCheckpoint, timeoutMs: number): Promise<void> {
|
|
167
|
+
requirePauseTarget(operationClass);
|
|
151
168
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000) {
|
|
152
169
|
throw new InvalidResponseError('Transport pause request is invalid');
|
|
153
170
|
}
|
|
@@ -248,10 +265,77 @@ function isNullableString(value: unknown): value is string | null {
|
|
|
248
265
|
return value === null || typeof value === 'string';
|
|
249
266
|
}
|
|
250
267
|
|
|
251
|
-
function
|
|
252
|
-
if (
|
|
253
|
-
|
|
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
|
+
}
|
|
254
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
|
+
};
|
|
255
339
|
}
|
|
256
340
|
|
|
257
341
|
export { sha256Hex } from './digest';
|
|
@@ -265,4 +349,7 @@ export type {
|
|
|
265
349
|
TransportObservation,
|
|
266
350
|
TransportObservationSnapshot,
|
|
267
351
|
TransportOperationClass,
|
|
352
|
+
MigrationCheckpoint,
|
|
353
|
+
MigrationJournalInspection,
|
|
354
|
+
PhysicalSchemaColumnInspection,
|
|
268
355
|
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -329,6 +329,24 @@ export interface ClientStateInspection {
|
|
|
329
329
|
provenanceMaintenanceWorkCursor: string;
|
|
330
330
|
}
|
|
331
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
|
+
|
|
332
350
|
export type TransportOperationClass =
|
|
333
351
|
| 'connect'
|
|
334
352
|
| 'pull'
|
|
@@ -342,12 +360,15 @@ export interface TransportObservation {
|
|
|
342
360
|
sequence: number;
|
|
343
361
|
operationClass: TransportOperationClass;
|
|
344
362
|
statusCode: number;
|
|
363
|
+
errorCode?: string;
|
|
364
|
+
retryable?: boolean;
|
|
345
365
|
durationNanoseconds: number;
|
|
346
366
|
cursorFingerprints?: string[];
|
|
347
367
|
cursorFingerprintsComplete?: boolean;
|
|
348
368
|
requestFacts?: Record<string, JSONValue>;
|
|
349
369
|
rebuildResponseFacts?: Record<string, JSONValue>;
|
|
350
370
|
pullResponseFacts?: Record<string, JSONValue>;
|
|
371
|
+
connectResponseFacts?: Record<string, JSONValue>;
|
|
351
372
|
}
|
|
352
373
|
|
|
353
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
|
+
}
|