@callstack/brownfield-cli 3.7.0 → 3.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @callstack/brownfield-cli
2
2
 
3
+ ## 3.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#335](https://github.com/callstack/react-native-brownfield/pull/335) [`e78084d`](https://github.com/callstack/react-native-brownfield/commit/e78084d227a49d39d726b8c778bd56045634678d) Thanks [@hurali97](https://github.com/hurali97)! - fix: Object params correctly reflect the generated native code instead of Any
8
+
9
+ ## 3.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#295](https://github.com/callstack/react-native-brownfield/pull/295) [`d93dcd9`](https://github.com/callstack/react-native-brownfield/commit/d93dcd96e3b90517d718cd42b2cc773fb9795eac) Thanks [@hurali97](https://github.com/hurali97)! - add expo updates support
14
+
3
15
  ## 3.7.0
4
16
 
5
17
  ### Minor Changes
@@ -1,4 +1,8 @@
1
1
  import type { MethodSignature } from '../types.js';
2
- export declare function generateKotlinDelegate(methods: MethodSignature[], kotlinPackageName: string): string;
3
- export declare function generateKotlinModule(methods: MethodSignature[], kotlinPackageName: string): string;
2
+ interface KotlinTypeMappingOptions {
3
+ modelTypeNames?: string[];
4
+ }
5
+ export declare function generateKotlinDelegate(methods: MethodSignature[], kotlinPackageName: string, options?: KotlinTypeMappingOptions): string;
6
+ export declare function generateKotlinModule(methods: MethodSignature[], kotlinPackageName: string, options?: KotlinTypeMappingOptions): string;
7
+ export {};
4
8
  //# sourceMappingURL=android.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/android.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAwBnD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,GACxB,MAAM,CAuBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,GACxB,MAAM,CAiCR"}
1
+ {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/android.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAUnD,UAAU,wBAAwB;IAChC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA4BD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAuBR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAoCR"}
@@ -5,26 +5,32 @@ const TS_TO_KOTLIN_TYPE = {
5
5
  void: 'Unit',
6
6
  Object: 'ReadableMap',
7
7
  };
8
- function mapTsTypeToKotlin(tsType, optional = false) {
8
+ function mapTsTypeToKotlin(tsType, optional = false, options = {}, layer = 'delegate') {
9
9
  if (tsType.startsWith('Promise<')) {
10
10
  const inner = tsType.slice(8, -1);
11
- return mapTsTypeToKotlin(inner, optional);
11
+ return mapTsTypeToKotlin(inner, optional, options, layer);
12
12
  }
13
13
  const mapped = TS_TO_KOTLIN_TYPE[tsType];
14
14
  if (mapped) {
15
15
  return optional ? `${mapped}?` : mapped;
16
16
  }
17
+ if (options.modelTypeNames?.includes(tsType)) {
18
+ if (layer === 'module') {
19
+ return optional ? 'ReadableMap?' : 'ReadableMap';
20
+ }
21
+ return optional ? `${tsType}?` : tsType;
22
+ }
17
23
  return optional ? 'Any?' : 'Any';
18
24
  }
19
- export function generateKotlinDelegate(methods, kotlinPackageName) {
25
+ export function generateKotlinDelegate(methods, kotlinPackageName, options = {}) {
20
26
  const methodSignatures = methods
21
27
  .map((method) => {
22
28
  const params = method.params
23
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
29
+ .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options)}`)
24
30
  .join(', ');
25
31
  const returnType = method.returnType === 'void'
26
32
  ? ''
27
- : `: ${mapTsTypeToKotlin(method.returnType, false)}`;
33
+ : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'delegate')}`;
28
34
  return ` fun ${method.name}(${params})${returnType}`;
29
35
  })
30
36
  .join('\n');
@@ -35,14 +41,14 @@ ${methodSignatures}
35
41
  }
36
42
  `;
37
43
  }
38
- export function generateKotlinModule(methods, kotlinPackageName) {
44
+ export function generateKotlinModule(methods, kotlinPackageName, options = {}) {
39
45
  const hasAsyncMethod = methods.some((method) => method.isAsync);
40
46
  const hasObjectType = methods.some((method) => method.returnType.includes('Object') ||
41
- method.params.some((param) => param.type === 'Object'));
47
+ method.params.some((param) => param.type === 'Object' || options.modelTypeNames?.includes(param.type)));
42
48
  const methodImplementations = methods
43
49
  .map((method) => method.isAsync
44
- ? generateAsyncKotlinMethod(method)
45
- : generateSyncKotlinMethod(method))
50
+ ? generateAsyncKotlinMethod(method, options)
51
+ : generateSyncKotlinMethod(method, options))
46
52
  .join('\n\n');
47
53
  return `package ${kotlinPackageName}
48
54
 
@@ -60,26 +66,39 @@ ${methodImplementations}
60
66
  }
61
67
  `;
62
68
  }
63
- function generateSyncKotlinMethod(method) {
69
+ function generateSyncKotlinMethod(method, options = {}) {
64
70
  const params = method.params
65
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
71
+ .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`)
72
+ .join(', ');
73
+ const preparedParams = [];
74
+ const args = method.params
75
+ .map((param) => {
76
+ if (options.modelTypeNames?.includes(param.type)) {
77
+ const convertedName = `${param.name}Model`;
78
+ preparedParams.push(` val ${convertedName} = ${param.name}${param.optional
79
+ ? `?.let(::to${param.type})`
80
+ : `.let(::to${param.type})`}`);
81
+ return convertedName;
82
+ }
83
+ return param.name;
84
+ })
66
85
  .join(', ');
67
- const args = method.params.map((param) => param.name).join(', ');
68
86
  const signature = ` @ReactMethod\n override fun ${method.name}(${params})${method.returnType === 'void'
69
87
  ? ''
70
- : `: ${mapTsTypeToKotlin(method.returnType, false)}`}`;
88
+ : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'module')}`}`;
89
+ const preparedPrefix = preparedParams.length > 0 ? `${preparedParams.join('\n')}\n` : '';
71
90
  if (method.returnType === 'void') {
72
91
  return `${signature} {
73
- BrownfieldNavigationManager.getDelegate().${method.name}(${args})
92
+ ${preparedPrefix} BrownfieldNavigationManager.getDelegate().${method.name}(${args})
74
93
  }`;
75
94
  }
76
95
  return `${signature} {
77
- return BrownfieldNavigationManager.getDelegate().${method.name}(${args})
96
+ ${preparedPrefix} return BrownfieldNavigationManager.getDelegate().${method.name}(${args})
78
97
  }`;
79
98
  }
80
- function generateAsyncKotlinMethod(method) {
99
+ function generateAsyncKotlinMethod(method, options = {}) {
81
100
  const paramsWithTypes = method.params
82
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
101
+ .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`)
83
102
  .join(', ');
84
103
  const params = paramsWithTypes.length > 0
85
104
  ? `${paramsWithTypes}, promise: Promise`
@@ -1,4 +1,13 @@
1
1
  import type { MethodSignature } from '../types.js';
2
- export declare function generateSwiftDelegate(methods: MethodSignature[]): string;
3
- export declare function generateObjCImplementation(methods: MethodSignature[]): string;
2
+ interface ObjCTypeMappingOptions {
3
+ modelTypeNames?: string[];
4
+ }
5
+ interface SwiftTypeMappingOptions {
6
+ modelTypeNames?: string[];
7
+ }
8
+ interface ObjCGenerationOptions extends ObjCTypeMappingOptions {
9
+ }
10
+ export declare function generateSwiftDelegate(methods: MethodSignature[], options?: SwiftTypeMappingOptions): string;
11
+ export declare function generateObjCImplementation(methods: MethodSignature[], options?: ObjCGenerationOptions): string;
12
+ export {};
4
13
  //# sourceMappingURL=ios.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAgDnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CA0BxE;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAgC7E"}
1
+ {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAkBnD,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA0BD,UAAU,uBAAuB;IAC/B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,qBAAsB,SAAQ,sBAAsB;CAAG;AAwBjE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CA0BR;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkCR"}
@@ -12,10 +12,13 @@ const TS_TO_SWIFT_TYPE = {
12
12
  void: 'Void',
13
13
  Object: '[String: Any]',
14
14
  };
15
- function mapTsTypeToObjC(tsType, nullable = false) {
15
+ function mapTsTypeToObjC(tsType, nullable = false, options = {}) {
16
16
  if (tsType.startsWith('Promise<')) {
17
17
  return 'void';
18
18
  }
19
+ if (options.modelTypeNames?.includes(tsType)) {
20
+ return nullable ? 'NSDictionary * _Nullable' : 'NSDictionary *';
21
+ }
19
22
  const mapped = TS_TO_OBJC_TYPE[tsType];
20
23
  if (mapped) {
21
24
  if (nullable && mapped.includes('*')) {
@@ -25,30 +28,33 @@ function mapTsTypeToObjC(tsType, nullable = false) {
25
28
  }
26
29
  return nullable ? 'id _Nullable' : 'id';
27
30
  }
28
- function mapTsTypeToSwift(tsType, optional = false) {
31
+ function mapTsTypeToSwift(tsType, optional = false, options = {}) {
29
32
  if (tsType.startsWith('Promise<')) {
30
33
  const inner = tsType.slice(8, -1);
31
- return mapTsTypeToSwift(inner, optional);
34
+ return mapTsTypeToSwift(inner, optional, options);
32
35
  }
33
36
  const mapped = TS_TO_SWIFT_TYPE[tsType];
34
37
  if (mapped) {
35
38
  return optional ? `${mapped}?` : mapped;
36
39
  }
40
+ if (options.modelTypeNames?.includes(tsType)) {
41
+ return optional ? `${tsType}?` : tsType;
42
+ }
37
43
  return optional ? 'Any?' : 'Any';
38
44
  }
39
- export function generateSwiftDelegate(methods) {
45
+ export function generateSwiftDelegate(methods, options = {}) {
40
46
  const protocolMethods = methods
41
47
  .map((method) => {
42
48
  const params = method.params
43
49
  .map((param, index) => {
44
- const swiftType = mapTsTypeToSwift(param.type, param.optional);
50
+ const swiftType = mapTsTypeToSwift(param.type, param.optional, options);
45
51
  const label = index === 0 ? '_' : param.name;
46
52
  return `${label} ${param.name}: ${swiftType}`;
47
53
  })
48
54
  .join(', ');
49
55
  const returnType = method.returnType === 'void'
50
56
  ? ''
51
- : ` -> ${mapTsTypeToSwift(method.returnType, false)}`;
57
+ : ` -> ${mapTsTypeToSwift(method.returnType, false, options)}`;
52
58
  return ` @objc func ${method.name}(${params})${returnType}`;
53
59
  })
54
60
  .join('\n');
@@ -59,9 +65,11 @@ ${protocolMethods}
59
65
  }
60
66
  `;
61
67
  }
62
- export function generateObjCImplementation(methods) {
68
+ export function generateObjCImplementation(methods, options = {}) {
63
69
  const methodImplementations = methods
64
- .map((method) => method.isAsync ? generateAsyncObjCMethod(method) : generateSyncObjCMethod(method))
70
+ .map((method) => method.isAsync
71
+ ? generateAsyncObjCMethod(method, options)
72
+ : generateSyncObjCMethod(method, options))
65
73
  .join('\n\n');
66
74
  return `#import "NativeBrownfieldNavigation.h"
67
75
 
@@ -89,33 +97,48 @@ ${methodImplementations}
89
97
  @end
90
98
  `;
91
99
  }
92
- function generateSyncObjCMethod(method) {
100
+ function generateSyncObjCMethod(method, options) {
93
101
  const { name, params, returnType } = method;
94
- let signature = `- (${mapTsTypeToObjC(returnType)})${name}`;
102
+ let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
95
103
  if (params.length > 0) {
96
104
  signature += params
97
105
  .map((param, index) => {
98
106
  const prefix = index === 0 ? ':' : ` ${param.name}:`;
99
- return `${prefix}(${mapTsTypeToObjC(param.type, param.optional)})${param.name}`;
107
+ return `${prefix}(${mapTsTypeToObjC(param.type, param.optional, options)})${param.name}`;
100
108
  })
101
109
  .join('');
102
110
  }
111
+ const preparedParams = [];
112
+ const delegateArgs = [];
113
+ for (const param of params) {
114
+ if (options.modelTypeNames?.includes(param.type)) {
115
+ const convertedParamName = `${param.name}Model`;
116
+ preparedParams.push(`${param.type} *${convertedParamName} = ${param.name} == nil ? nil : [${param.type} fromDictionary:${param.name}];`);
117
+ delegateArgs.push(convertedParamName);
118
+ }
119
+ else {
120
+ delegateArgs.push(param.name);
121
+ }
122
+ }
103
123
  let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`;
104
- if (params.length > 0) {
105
- delegateCall += params
106
- .map((param, index) => {
124
+ if (delegateArgs.length > 0) {
125
+ delegateCall += delegateArgs
126
+ .map((argName, index) => {
127
+ const param = params[index];
107
128
  const label = index === 0 ? '' : param.name;
108
- return `${label}:${param.name}`;
129
+ return `${label}:${argName}`;
109
130
  })
110
131
  .join(' ');
111
132
  }
112
133
  delegateCall += ']';
113
134
  const returnPrefix = returnType === 'void' ? '' : 'return ';
135
+ const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
136
+ const bodyPrefix = preparedLines.length > 0 ? `${preparedLines}\n` : '';
114
137
  return `${signature} {
115
- ${returnPrefix}${delegateCall};
138
+ ${bodyPrefix} ${returnPrefix}${delegateCall};
116
139
  }`;
117
140
  }
118
- function generateAsyncObjCMethod(method) {
141
+ function generateAsyncObjCMethod(method, options) {
119
142
  const { name, params } = method;
120
143
  let signature = `- (void)${name}`;
121
144
  const allParams = [
@@ -131,7 +154,7 @@ function generateAsyncObjCMethod(method) {
131
154
  ? 'RCTPromiseResolveBlock'
132
155
  : param.type === 'RCTPromiseRejectBlock'
133
156
  ? 'RCTPromiseRejectBlock'
134
- : mapTsTypeToObjC(param.type, param.optional);
157
+ : mapTsTypeToObjC(param.type, param.optional, options);
135
158
  return `${prefix}(${type})${param.name}`;
136
159
  })
137
160
  .join(' ');
@@ -1,13 +1,14 @@
1
- import type { MethodSignature } from '../types.js';
1
+ import type { MethodSignature, ModelDefinition } from '../types.js';
2
2
  export interface NavigationModelsOptions {
3
3
  specPath: string;
4
4
  methods: MethodSignature[];
5
5
  kotlinPackageName: string;
6
+ modelDefinitions?: ModelDefinition[];
6
7
  }
7
8
  export interface GeneratedNavigationModels {
8
9
  swiftModels?: string;
9
10
  kotlinModels?: string;
10
11
  modelTypeNames: string[];
11
12
  }
12
- export declare function generateNavigationModels({ specPath, methods, kotlinPackageName, }: NavigationModelsOptions): Promise<GeneratedNavigationModels>;
13
+ export declare function generateNavigationModels({ specPath, methods, kotlinPackageName, modelDefinitions, }: NavigationModelsOptions): Promise<GeneratedNavigationModels>;
13
14
  //# sourceMappingURL=models.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/models.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAkGD,wBAAsB,wBAAwB,CAAC,EAC7C,QAAQ,EACR,OAAO,EACP,iBAAiB,GAClB,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CA6C9D"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/models.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAoGD,wBAAsB,wBAAwB,CAAC,EAC7C,QAAQ,EACR,OAAO,EACP,iBAAiB,EACjB,gBAAqB,GACtB,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CA0D9D"}
@@ -61,6 +61,8 @@ async function generateModelsForLanguage({ typeNames, schema, lang, kotlinPackag
61
61
  'access-level': 'public',
62
62
  'mutable-properties': 'true',
63
63
  initializers: 'false',
64
+ 'struct-or-class': 'class',
65
+ 'objective-c-support': 'true',
64
66
  'swift-5-support': 'true',
65
67
  }
66
68
  : {
@@ -74,7 +76,7 @@ async function generateModelsForLanguage({ typeNames, schema, lang, kotlinPackag
74
76
  });
75
77
  return lines.join('\n');
76
78
  }
77
- export async function generateNavigationModels({ specPath, methods, kotlinPackageName, }) {
79
+ export async function generateNavigationModels({ specPath, methods, kotlinPackageName, modelDefinitions = [], }) {
78
80
  const absoluteSpecPath = path.resolve(process.cwd(), specPath);
79
81
  if (!fs.existsSync(absoluteSpecPath)) {
80
82
  throw new Error(`Spec file not found: ${absoluteSpecPath}`);
@@ -104,9 +106,227 @@ export async function generateNavigationModels({ specPath, methods, kotlinPackag
104
106
  kotlinPackageName,
105
107
  }),
106
108
  ]);
109
+ const swiftModelConversions = generateSwiftModelConversionExtensions(modelDefinitions, modelTypeNames);
110
+ const kotlinModelConversions = generateKotlinModelConversionExtensions(modelDefinitions, modelTypeNames);
107
111
  return {
108
- swiftModels,
109
- kotlinModels,
112
+ swiftModels: swiftModelConversions
113
+ ? `${swiftModels}\n\n${swiftModelConversions}`
114
+ : swiftModels,
115
+ kotlinModels: kotlinModelConversions
116
+ ? `${ensureKotlinReadableMapImport(kotlinModels)}\n\n${kotlinModelConversions}`
117
+ : kotlinModels,
110
118
  modelTypeNames,
111
119
  };
112
120
  }
121
+ function ensureKotlinReadableMapImport(kotlinModels) {
122
+ const importLine = 'import com.facebook.react.bridge.ReadableMap';
123
+ if (kotlinModels.includes(importLine)) {
124
+ return kotlinModels;
125
+ }
126
+ const packageMatch = kotlinModels.match(/^package[^\n]*\n/);
127
+ if (!packageMatch) {
128
+ return `${importLine}\n${kotlinModels}`;
129
+ }
130
+ const packageLine = packageMatch[0];
131
+ const rest = kotlinModels.slice(packageLine.length);
132
+ return `${packageLine}\n${importLine}\n${rest}`;
133
+ }
134
+ function generateSwiftModelConversionExtensions(modelDefinitions, modelTypeNames) {
135
+ const modelDefinitionsByLookupName = buildModelDefinitionsByLookupName(modelDefinitions);
136
+ const conversionModelNames = collectConversionModelNames(modelDefinitionsByLookupName, modelTypeNames);
137
+ return conversionModelNames
138
+ .map((modelName) => {
139
+ const definition = modelDefinitionsByLookupName.get(modelName);
140
+ if (!definition) {
141
+ return '';
142
+ }
143
+ const sortedFields = [...definition.fields].sort((a, b) => a.name.localeCompare(b.name));
144
+ if (sortedFields.length === 0) {
145
+ return '';
146
+ }
147
+ const args = sortedFields
148
+ .map((field) => `${field.name}: ${mapDictionaryValueToSwiftExpression(field.name, field.type, field.optional)}`)
149
+ .join(', ');
150
+ return `@objc public extension ${modelName} {
151
+ static func fromDictionary(_ value: NSDictionary) -> ${modelName} {
152
+ return ${modelName}(${args})
153
+ }
154
+ }`;
155
+ })
156
+ .filter(Boolean)
157
+ .join('\n\n');
158
+ }
159
+ function mapDictionaryValueToSwiftExpression(fieldName, fieldType, optional) {
160
+ const parsedType = parseFieldType(fieldType);
161
+ const normalizedFieldType = parsedType.normalizedType;
162
+ const valueAccess = `value["${fieldName}"]`;
163
+ const allowsNil = optional || parsedType.nullable;
164
+ if (normalizedFieldType === 'string') {
165
+ return allowsNil ? `${valueAccess} as? String` : `${valueAccess} as! String`;
166
+ }
167
+ if (normalizedFieldType === 'number') {
168
+ return allowsNil
169
+ ? `(${valueAccess} as? NSNumber)?.doubleValue`
170
+ : `(${valueAccess} as! NSNumber).doubleValue`;
171
+ }
172
+ if (normalizedFieldType === 'boolean') {
173
+ return allowsNil
174
+ ? `(${valueAccess} as? NSNumber)?.boolValue`
175
+ : `(${valueAccess} as! NSNumber).boolValue`;
176
+ }
177
+ if (normalizedFieldType === 'string[]') {
178
+ return allowsNil ? `${valueAccess} as? [String]` : `${valueAccess} as! [String]`;
179
+ }
180
+ if (isSimpleTypeReference(normalizedFieldType)) {
181
+ const nestedModelType = resolveNestedModelTypeName(normalizedFieldType);
182
+ return allowsNil
183
+ ? `(${valueAccess} as? NSDictionary).map(${nestedModelType}.fromDictionary)`
184
+ : `${nestedModelType}.fromDictionary(${valueAccess} as! NSDictionary)`;
185
+ }
186
+ return `fatalError("Unsupported TypeScript field type '${escapeSwiftStringLiteral(normalizedFieldType)}' for field '${escapeSwiftStringLiteral(fieldName)}' in Swift conversion. Consider using 'Any' or a custom converter.")`;
187
+ }
188
+ function generateKotlinModelConversionExtensions(modelDefinitions, modelTypeNames) {
189
+ const modelDefinitionsByLookupName = buildModelDefinitionsByLookupName(modelDefinitions);
190
+ const conversionModelNames = collectConversionModelNames(modelDefinitionsByLookupName, modelTypeNames);
191
+ let usesStringArrayHelper = false;
192
+ const conversionFunctions = conversionModelNames
193
+ .map((modelName) => {
194
+ const definition = modelDefinitionsByLookupName.get(modelName);
195
+ if (!definition) {
196
+ return '';
197
+ }
198
+ const sortedFields = [...definition.fields].sort((a, b) => a.name.localeCompare(b.name));
199
+ if (sortedFields.length === 0) {
200
+ return '';
201
+ }
202
+ const args = sortedFields
203
+ .map((field) => {
204
+ const fieldExpression = mapReadableMapFieldExpression('value', field.name, field.type, field.optional);
205
+ if (fieldExpression.includes('readStringArray(')) {
206
+ usesStringArrayHelper = true;
207
+ }
208
+ return `${field.name} = ${fieldExpression}`;
209
+ })
210
+ .join(', ');
211
+ return `fun to${modelName}(value: ReadableMap): ${modelName} {
212
+ return ${modelName}(${args})
213
+ }`;
214
+ })
215
+ .filter(Boolean)
216
+ .join('\n\n');
217
+ if (!usesStringArrayHelper) {
218
+ return conversionFunctions;
219
+ }
220
+ const stringArrayHelper = `private fun readStringArray(value: ReadableMap, key: String, required: Boolean): List<String>? {
221
+ if (!value.hasKey(key) || value.isNull(key)) {
222
+ if (required) error("Missing required array field '$key'")
223
+ return null
224
+ }
225
+ val array = value.getArray(key) ?: return null
226
+ return array.toArrayList().map {
227
+ it as? String ?: error("Expected string elements for array field '$key'")
228
+ }
229
+ }`;
230
+ return conversionFunctions
231
+ ? `${conversionFunctions}\n\n${stringArrayHelper}`
232
+ : stringArrayHelper;
233
+ }
234
+ function mapReadableMapFieldExpression(mapName, fieldName, fieldType, optional) {
235
+ const parsedType = parseFieldType(fieldType);
236
+ const normalizedFieldType = parsedType.normalizedType;
237
+ const allowsNull = optional || parsedType.nullable;
238
+ if (normalizedFieldType === 'string') {
239
+ return allowsNull
240
+ ? `${mapName}.getString("${fieldName}")`
241
+ : `${mapName}.getString("${fieldName}")!!`;
242
+ }
243
+ if (normalizedFieldType === 'number') {
244
+ return allowsNull
245
+ ? `if (${mapName}.hasKey("${fieldName}") && !${mapName}.isNull("${fieldName}")) ${mapName}.getDouble("${fieldName}") else null`
246
+ : `${mapName}.getDouble("${fieldName}")`;
247
+ }
248
+ if (normalizedFieldType === 'boolean') {
249
+ return allowsNull
250
+ ? `if (${mapName}.hasKey("${fieldName}") && !${mapName}.isNull("${fieldName}")) ${mapName}.getBoolean("${fieldName}") else null`
251
+ : `${mapName}.getBoolean("${fieldName}")`;
252
+ }
253
+ if (normalizedFieldType === 'string[]') {
254
+ return allowsNull
255
+ ? `readStringArray(${mapName}, "${fieldName}", false)`
256
+ : `readStringArray(${mapName}, "${fieldName}", true)!!`;
257
+ }
258
+ if (isSimpleTypeReference(normalizedFieldType)) {
259
+ const nestedModelType = resolveNestedModelTypeName(normalizedFieldType);
260
+ return allowsNull
261
+ ? `${mapName}.getMap("${fieldName}")?.let { to${nestedModelType}(it) }`
262
+ : `to${nestedModelType}(${mapName}.getMap("${fieldName}")!!)`;
263
+ }
264
+ return `error("Unsupported TypeScript field type '${escapeKotlinStringLiteral(normalizedFieldType)}' for field '${escapeKotlinStringLiteral(fieldName)}' in Kotlin conversion. Consider using 'Any' or a custom converter.")`;
265
+ }
266
+ function isSimpleTypeReference(typeName) {
267
+ return /^[A-Za-z_]\w*$/.test(typeName);
268
+ }
269
+ function escapeSwiftStringLiteral(value) {
270
+ return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
271
+ }
272
+ function escapeKotlinStringLiteral(value) {
273
+ return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
274
+ }
275
+ function parseFieldType(fieldType) {
276
+ const unionTypes = fieldType
277
+ .split('|')
278
+ .map((part) => part.trim())
279
+ .filter(Boolean);
280
+ const nullable = unionTypes.includes('null') || unionTypes.includes('undefined');
281
+ const nonNullableTypes = unionTypes.filter((part) => part !== 'null' && part !== 'undefined');
282
+ const normalizedType = nonNullableTypes.length === 1 ? nonNullableTypes[0] : fieldType.trim();
283
+ return { normalizedType, nullable };
284
+ }
285
+ function resolveNestedModelTypeName(typeName) {
286
+ if (typeName.endsWith('Type') && typeName.length > 'Type'.length) {
287
+ return typeName.slice(0, -'Type'.length);
288
+ }
289
+ return typeName;
290
+ }
291
+ function buildModelDefinitionsByLookupName(modelDefinitions) {
292
+ const definitionsByLookupName = new Map();
293
+ for (const definition of modelDefinitions) {
294
+ if (!definitionsByLookupName.has(definition.name)) {
295
+ definitionsByLookupName.set(definition.name, definition);
296
+ }
297
+ const nestedName = resolveNestedModelTypeName(definition.name);
298
+ if (nestedName !== definition.name &&
299
+ !definitionsByLookupName.has(nestedName)) {
300
+ definitionsByLookupName.set(nestedName, definition);
301
+ }
302
+ }
303
+ return definitionsByLookupName;
304
+ }
305
+ function collectConversionModelNames(modelDefinitionsByLookupName, modelTypeNames) {
306
+ const queue = [...modelTypeNames];
307
+ const visited = new Set();
308
+ const orderedModelNames = [];
309
+ while (queue.length > 0) {
310
+ const modelName = queue.shift();
311
+ if (!modelName || visited.has(modelName)) {
312
+ continue;
313
+ }
314
+ visited.add(modelName);
315
+ const definition = modelDefinitionsByLookupName.get(modelName);
316
+ if (!definition) {
317
+ continue;
318
+ }
319
+ orderedModelNames.push(modelName);
320
+ for (const field of definition.fields) {
321
+ const parsedFieldType = parseFieldType(field.type);
322
+ if (!isSimpleTypeReference(parsedFieldType.normalizedType)) {
323
+ continue;
324
+ }
325
+ const nestedModelName = resolveNestedModelTypeName(parsedFieldType.normalizedType);
326
+ if (!visited.has(nestedModelName)) {
327
+ queue.push(nestedModelName);
328
+ }
329
+ }
330
+ }
331
+ return orderedModelNames;
332
+ }
@@ -1,6 +1,10 @@
1
- import type { MethodSignature } from '../types.js';
2
- export declare function generateTurboModuleSpec(methods: MethodSignature[]): string;
3
- export declare function generateIndexTs(methods: MethodSignature[]): string;
1
+ import type { MethodSignature, TypeDeclaration } from '../types.js';
2
+ interface TurboSpecGenerationOptions {
3
+ modelTypeNames?: string[];
4
+ }
5
+ export declare function generateTurboModuleSpec(methods: MethodSignature[], referencedTypeDeclarations?: TypeDeclaration[], options?: TurboSpecGenerationOptions): string;
6
+ export declare function generateIndexTs(methods: MethodSignature[], referencedTypeDeclarations?: TypeDeclaration[]): string;
4
7
  export declare function transpileWithConsumerBabel(tsCode: string, projectRoot: string, packageRoot: string): string;
5
- export declare function generateIndexDts(methods: MethodSignature[]): string;
8
+ export declare function generateIndexDts(methods: MethodSignature[], referencedTypeDeclarations?: TypeDeclaration[]): string;
9
+ export {};
6
10
  //# sourceMappingURL=ts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ts.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAoB1E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CA6BlE;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CA0CR;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAgBnE"}
1
+ {"version":3,"file":"ts.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpE,UAAU,0BAA0B;IAClC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAkBD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,eAAe,EAAE,EAC1B,0BAA0B,GAAE,eAAe,EAAO,EAClD,OAAO,GAAE,0BAA+B,GACvC,MAAM,CA2BR;AAaD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,EAAE,EAC1B,0BAA0B,GAAE,eAAe,EAAO,GACjD,MAAM,CA+BR;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CA0CR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EAAE,EAC1B,0BAA0B,GAAE,eAAe,EAAO,GACjD,MAAM,CAiBR"}