@callstack/brownfield-cli 3.8.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/navigation/generators/android.d.ts +6 -2
- package/dist/navigation/generators/android.d.ts.map +1 -1
- package/dist/navigation/generators/android.js +36 -17
- package/dist/navigation/generators/ios.d.ts +11 -2
- package/dist/navigation/generators/ios.d.ts.map +1 -1
- package/dist/navigation/generators/ios.js +41 -18
- package/dist/navigation/generators/models.d.ts +3 -2
- package/dist/navigation/generators/models.d.ts.map +1 -1
- package/dist/navigation/generators/models.js +223 -3
- package/dist/navigation/generators/ts.d.ts +8 -4
- package/dist/navigation/generators/ts.d.ts.map +1 -1
- package/dist/navigation/generators/ts.js +30 -7
- package/dist/navigation/parser.d.ts +2 -2
- package/dist/navigation/parser.d.ts.map +1 -1
- package/dist/navigation/parser.js +110 -1
- package/dist/navigation/runner.d.ts.map +1 -1
- package/dist/navigation/runner.js +19 -8
- package/dist/navigation/types.d.ts +18 -0
- package/dist/navigation/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/navigation/generators/android.ts +67 -17
- package/src/navigation/generators/ios.ts +75 -18
- package/src/navigation/generators/models.ts +319 -3
- package/src/navigation/generators/ts.ts +59 -8
- package/src/navigation/parser.ts +137 -3
- package/src/navigation/runner.ts +20 -8
- package/src/navigation/types.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @callstack/brownfield-cli
|
|
2
2
|
|
|
3
|
+
## 3.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#326](https://github.com/callstack/react-native-brownfield/pull/326) [`80e6364`](https://github.com/callstack/react-native-brownfield/commit/80e6364d405d216b3e84a6297dfe346d2f01444b) Thanks [@artus9033](https://github.com/artus9033)! - feat: strip SO files by default, deprecate experimental option in favor of useStrippedSoFiles
|
|
8
|
+
|
|
9
|
+
## 3.8.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#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
|
|
14
|
+
|
|
3
15
|
## 3.8.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { MethodSignature } from '../types.js';
|
|
2
|
-
|
|
3
|
-
|
|
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;
|
|
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
|
-
|
|
3
|
-
|
|
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;
|
|
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
|
|
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 (
|
|
105
|
-
delegateCall +=
|
|
106
|
-
.map((
|
|
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}:${
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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;
|
|
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"}
|