@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.
Files changed (50) hide show
  1. package/README.md +112 -7
  2. package/SynchroReactNative.podspec +18 -1
  3. package/android/build.gradle +5 -1
  4. package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/SynchroModuleTransactionTest.kt +593 -0
  5. package/android/src/androidTest/kotlin/com/trainstar/synchro/rn/TransactionSessionTest.kt +314 -0
  6. package/android/src/main/kotlin/com/trainstar/synchro/rn/SynchroModule.kt +328 -112
  7. package/ios/SynchroModule.mm +27 -4
  8. package/ios/SynchroModule.swift +251 -89
  9. package/ios-test-host/main.m +48 -0
  10. package/ios-tests/SynchroModuleTransactionTests.swift +709 -0
  11. package/ios-tests/TransactionSessionTests.swift +66 -0
  12. package/lib/module/NativeSynchro.js.map +1 -1
  13. package/lib/module/SynchroClient.js +141 -2
  14. package/lib/module/SynchroClient.js.map +1 -1
  15. package/lib/module/errors.js +15 -1
  16. package/lib/module/errors.js.map +1 -1
  17. package/lib/module/index.js +1 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/inspection.js +99 -8
  20. package/lib/module/inspection.js.map +1 -1
  21. package/lib/module/types.js +18 -0
  22. package/lib/module/types.js.map +1 -1
  23. package/lib/typescript/example/e2e/schema-check.test.d.ts +2 -0
  24. package/lib/typescript/example/e2e/schema-check.test.d.ts.map +1 -0
  25. package/lib/typescript/example/src/conformance/runner.d.ts +17 -2
  26. package/lib/typescript/example/src/conformance/runner.d.ts.map +1 -1
  27. package/lib/typescript/src/NativeSynchro.d.ts +7 -1
  28. package/lib/typescript/src/NativeSynchro.d.ts.map +1 -1
  29. package/lib/typescript/src/SynchroClient.d.ts +29 -1
  30. package/lib/typescript/src/SynchroClient.d.ts.map +1 -1
  31. package/lib/typescript/src/errors.d.ts +7 -1
  32. package/lib/typescript/src/errors.d.ts.map +1 -1
  33. package/lib/typescript/src/index.d.ts +3 -3
  34. package/lib/typescript/src/index.d.ts.map +1 -1
  35. package/lib/typescript/src/inspection.d.ts +23 -5
  36. package/lib/typescript/src/inspection.d.ts.map +1 -1
  37. package/lib/typescript/src/types.d.ts +62 -1
  38. package/lib/typescript/src/types.d.ts.map +1 -1
  39. package/package.json +21 -6
  40. package/src/NativeSynchro.ts +6 -1
  41. package/src/SynchroClient.ts +160 -1
  42. package/src/errors.ts +30 -0
  43. package/src/index.ts +7 -0
  44. package/src/inspection.ts +138 -9
  45. package/src/types.ts +70 -1
  46. package/tooling-patches/apply.cjs +163 -0
  47. package/tooling-patches/cli-clean-20.2.0.patch +21 -0
  48. package/tooling-patches/cli-config-android-20.2.0.patch +13 -0
  49. package/tooling-patches/react-native-builder-bob-0.40.18.patch +74 -0
  50. 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 clientState(): Promise<ClientStateInspection> {
94
+ async captureSnapshot(rowStatements: SQLStatement[] = []): Promise<ClientStateSnapshotInspection> {
66
95
  try {
67
- return parseClientStateInspection(parseJSON(await nativeForInspection(this.client).inspectClientState()));
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
- requireTransportOperationClass(operationClass);
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
- requireTransportOperationClass(operationClass);
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 requireTransportOperationClass(value: TransportOperationClass): void {
210
- if (!TRANSPORT_OPERATION_CLASSES.includes(value)) {
211
- throw new InvalidResponseError('Transport operation class is invalid');
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
+ }