@callstack/brownfield-cli 4.1.0 → 4.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"packageAndroid.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageAndroid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,EACL,YAAY,EAGb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,qBAAqB,SAyBjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,cAGjC,CAAC"}
1
+ {"version":3,"file":"packageAndroid.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageAndroid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,EACL,YAAY,EAGb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,qBAAqB,SAsCjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,cAGjC,CAAC"}
@@ -8,8 +8,14 @@ import { getProjectInfo } from '../utils/project.js';
8
8
  import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
9
9
  import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
10
10
  import { mergeBrownfieldConfigWithOptions } from '../../config.js';
11
- export const packageAndroidCommand = curryOptions(new Command('package:android').description('Build Android AAR'), packageAarOptions).action(actionRunner(async (cliOptions) => {
12
- const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
11
+ export const packageAndroidCommand = curryOptions(new Command('package:android')
12
+ .description('Build Android AAR')
13
+ .option('--use-local-maven', 'Use local Maven for Brownfield plugin resolution'), packageAarOptions).action(actionRunner(async (cliOptions) => {
14
+ const { useLocalMaven, ...restOptions } = cliOptions;
15
+ const options = mergeBrownfieldConfigWithOptions({
16
+ ...restOptions,
17
+ ...(useLocalMaven ? { useLocalMaven: true } : {}),
18
+ }, 'android');
13
19
  const projectRoot = findProjectRoot();
14
20
  await runExpoPrebuildIfNeeded({
15
21
  projectRoot,
@@ -1 +1 @@
1
- {"version":3,"file":"publishAndroid.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/publishAndroid.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,qBAAqB,SA0BjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,cAGjC,CAAC"}
1
+ {"version":3,"file":"publishAndroid.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/publishAndroid.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,qBAAqB,SAsCjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,cAGjC,CAAC"}
@@ -8,8 +8,14 @@ import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
8
8
  import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
9
9
  import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
10
10
  import { mergeBrownfieldConfigWithOptions } from '../../config.js';
11
- export const publishAndroidCommand = curryOptions(new Command('publish:android').description('Publish Android package to Maven local'), publishLocalAarOptions).action(actionRunner(async (cliOptions) => {
12
- const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
11
+ export const publishAndroidCommand = curryOptions(new Command('publish:android')
12
+ .description('Publish Android package to Maven local')
13
+ .option('--use-local-maven', 'Use local Maven for Brownfield plugin resolution'), publishLocalAarOptions).action(actionRunner(async (cliOptions) => {
14
+ const { useLocalMaven, ...restOptions } = cliOptions;
15
+ const options = mergeBrownfieldConfigWithOptions({
16
+ ...restOptions,
17
+ ...(useLocalMaven ? { useLocalMaven: true } : {}),
18
+ }, 'android');
13
19
  const projectRoot = findProjectRoot();
14
20
  await runExpoPrebuildIfNeeded({
15
21
  projectRoot,
@@ -22,6 +22,9 @@ export type BrownfieldPluginProps = {
22
22
  artifactId?: string;
23
23
  version?: string;
24
24
  useLocalGradlePlugin?: boolean;
25
+ minifyEnabled?: boolean;
26
+ extraProguardRules?: string[];
27
+ useLocalMaven?: boolean;
25
28
  missingDimensionStrategies?: string[];
26
29
  };
27
30
  };
@@ -35,6 +38,9 @@ export type ResolvedBrownfieldPluginAndroidConfig = {
35
38
  artifactId: string;
36
39
  version: string;
37
40
  useLocalGradlePlugin: boolean;
41
+ minifyEnabled: boolean;
42
+ extraProguardRules: string[];
43
+ useLocalMaven: boolean;
38
44
  missingDimensionStrategies: string[];
39
45
  };
40
46
  export type ResolvedBrownfieldPluginIosConfig = {
@@ -1 +1 @@
1
- {"version":3,"file":"expoPluginConfig.d.ts","sourceRoot":"","sources":["../src/expoPluginConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;QAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,0BAA0B,EAAE,MAAM,EAAE,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;IACzD,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,iCAAiC,GAAG,IAAI,CAAC;IAC9C,OAAO,EAAE,qCAAqC,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,GAAG,CAAC,EAAE;QACJ,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,CAAC;AA0DF;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,gBAAgB,GAAG,IAAI,EACnC,WAAW,EAAE,qBAAqB,GACjC,IAAI,CAQN;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,qBAAqB,EAClC,UAAU,EAAE,gBAAgB,GAAG,IAAI,EACnC,UAAU,EAAE,oBAAoB,GAC/B,8BAA8B,CA6ChC"}
1
+ {"version":3,"file":"expoPluginConfig.d.ts","sourceRoot":"","sources":["../src/expoPluginConfig.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,7 @@ export function assertNoConfigFilePluginOverlap(fileConfig, pluginProps) {
66
66
  */
67
67
  export function resolveBrownfieldPluginConfig(pluginProps, fileConfig, expoConfig) {
68
68
  const effectiveProps = fileConfig !== null ? fileConfigToPluginProps(fileConfig) : pluginProps;
69
+ const extraProguardRules = (effectiveProps.android?.extraProguardRules ?? []).filter((rule) => rule.trim().length > 0);
69
70
  const androidPackage = expoConfig.android?.package;
70
71
  /**
71
72
  * Below: android.moduleName may be provided in the fully-qualified Gradle format
@@ -97,6 +98,9 @@ export function resolveBrownfieldPluginConfig(pluginProps, fileConfig, expoConfi
97
98
  artifactId: effectiveProps.android?.artifactId ?? androidModuleName,
98
99
  version: effectiveProps.android?.version ?? '0.0.1-SNAPSHOT',
99
100
  useLocalGradlePlugin: effectiveProps.android?.useLocalGradlePlugin ?? false,
101
+ minifyEnabled: effectiveProps.android?.minifyEnabled ?? false,
102
+ extraProguardRules,
103
+ useLocalMaven: effectiveProps.android?.useLocalMaven ?? false,
100
104
  missingDimensionStrategies: effectiveProps.android?.missingDimensionStrategies ?? [],
101
105
  }
102
106
  : null,
@@ -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;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"}
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;AAUhE,UAAU,wBAAwB;IAChC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAuED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAgCR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EAAE,EAC1B,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,wBAA6B,GACrC,MAAM,CAuCR"}
@@ -5,6 +5,12 @@ const TS_TO_KOTLIN_TYPE = {
5
5
  void: 'Unit',
6
6
  Object: 'ReadableMap',
7
7
  };
8
+ function buildKotlinImports(imports) {
9
+ if (imports.length === 0) {
10
+ return '\n';
11
+ }
12
+ return `\n${[...new Set(imports)].sort().join('\n')}\n`;
13
+ }
8
14
  function mapTsTypeToKotlin(tsType, optional = false, options = {}, layer = 'delegate') {
9
15
  if (tsType.startsWith('Promise<')) {
10
16
  const inner = tsType.slice(8, -1);
@@ -22,27 +28,57 @@ function mapTsTypeToKotlin(tsType, optional = false, options = {}, layer = 'dele
22
28
  }
23
29
  return optional ? 'Any?' : 'Any';
24
30
  }
31
+ function mapParamToKotlin(param, options = {}, layer = 'delegate') {
32
+ if (param.callback) {
33
+ return param.optional ? 'Callback?' : 'Callback';
34
+ }
35
+ return mapTsTypeToKotlin(param.type, param.optional, options, layer);
36
+ }
37
+ function prepareKotlinArgs(method, options = {}) {
38
+ const preparedParams = [];
39
+ const args = method.params
40
+ .map((param) => {
41
+ if (options.modelTypeNames?.includes(param.type)) {
42
+ const convertedName = `${param.name}Model`;
43
+ preparedParams.push(` val ${convertedName} = ${param.name}${param.optional
44
+ ? `?.let(::to${param.type})`
45
+ : `.let(::to${param.type})`}`);
46
+ return convertedName;
47
+ }
48
+ return param.name;
49
+ })
50
+ .join(', ');
51
+ return { preparedParams, args };
52
+ }
25
53
  export function generateKotlinDelegate(methods, kotlinPackageName, options = {}) {
54
+ const hasAsyncMethod = methods.some((method) => method.isAsync);
55
+ const hasCallbackParam = methods.some((method) => method.params.some((param) => param.callback));
56
+ const imports = buildKotlinImports([
57
+ ...(hasCallbackParam ? ['import com.facebook.react.bridge.Callback'] : []),
58
+ ...(hasAsyncMethod ? ['import com.facebook.react.bridge.Promise'] : []),
59
+ ]);
26
60
  const methodSignatures = methods
27
61
  .map((method) => {
28
- const params = method.params
29
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options)}`)
30
- .join(', ');
31
- const returnType = method.returnType === 'void'
62
+ const methodParams = method.params.map((param) => `${param.name}: ${mapParamToKotlin(param, options)}`);
63
+ const params = [
64
+ ...methodParams,
65
+ ...(method.isAsync ? ['promise: Promise'] : []),
66
+ ].join(', ');
67
+ const returnType = method.returnType === 'void' || method.isAsync
32
68
  ? ''
33
69
  : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'delegate')}`;
34
70
  return ` fun ${method.name}(${params})${returnType}`;
35
71
  })
36
72
  .join('\n');
37
73
  return `package ${kotlinPackageName}
38
-
39
- interface BrownfieldNavigationDelegate {
74
+ ${imports}interface BrownfieldNavigationDelegate {
40
75
  ${methodSignatures}
41
76
  }
42
77
  `;
43
78
  }
44
79
  export function generateKotlinModule(methods, kotlinPackageName, options = {}) {
45
80
  const hasAsyncMethod = methods.some((method) => method.isAsync);
81
+ const hasCallbackParam = methods.some((method) => method.params.some((param) => param.callback));
46
82
  const hasObjectType = methods.some((method) => method.returnType.includes('Object') ||
47
83
  method.params.some((param) => param.type === 'Object' || options.modelTypeNames?.includes(param.type)));
48
84
  const methodImplementations = methods
@@ -53,7 +89,7 @@ export function generateKotlinModule(methods, kotlinPackageName, options = {}) {
53
89
  return `package ${kotlinPackageName}
54
90
 
55
91
  import com.facebook.react.bridge.ReactApplicationContext
56
- import com.facebook.react.bridge.ReactMethod${hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''}${hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : ''}
92
+ import com.facebook.react.bridge.ReactMethod${hasCallbackParam ? '\nimport com.facebook.react.bridge.Callback' : ''}${hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''}${hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : ''}
57
93
 
58
94
  class NativeBrownfieldNavigationModule(
59
95
  reactContext: ReactApplicationContext
@@ -68,21 +104,9 @@ ${methodImplementations}
68
104
  }
69
105
  function generateSyncKotlinMethod(method, options = {}) {
70
106
  const params = method.params
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
- })
107
+ .map((param) => `${param.name}: ${mapParamToKotlin(param, options, 'module')}`)
85
108
  .join(', ');
109
+ const { preparedParams, args } = prepareKotlinArgs(method, options);
86
110
  const signature = ` @ReactMethod\n override fun ${method.name}(${params})${method.returnType === 'void'
87
111
  ? ''
88
112
  : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'module')}`}`;
@@ -98,13 +122,16 @@ ${preparedPrefix} return BrownfieldNavigationManager.getDelegate().${method.n
98
122
  }
99
123
  function generateAsyncKotlinMethod(method, options = {}) {
100
124
  const paramsWithTypes = method.params
101
- .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`)
125
+ .map((param) => `${param.name}: ${mapParamToKotlin(param, options, 'module')}`)
102
126
  .join(', ');
103
127
  const params = paramsWithTypes.length > 0
104
128
  ? `${paramsWithTypes}, promise: Promise`
105
129
  : 'promise: Promise';
130
+ const { preparedParams, args } = prepareKotlinArgs(method, options);
131
+ const delegateArgs = args.length > 0 ? `${args}, promise` : 'promise';
132
+ const preparedPrefix = preparedParams.length > 0 ? `${preparedParams.join('\n')}\n` : '';
106
133
  return ` @ReactMethod
107
134
  override fun ${method.name}(${params}) {
108
- promise.reject("not_implemented", "${method.name} is not implemented")
135
+ ${preparedPrefix} BrownfieldNavigationManager.getDelegate().${method.name}(${delegateArgs})
109
136
  }`;
110
137
  }
@@ -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;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"}
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;AAkBhE,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAuCD,UAAU,uBAAuB;IAC/B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,qBAAsB,SAAQ,sBAAsB;CAAG;AA2CjE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CA+BR;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkCR"}
@@ -28,6 +28,14 @@ function mapTsTypeToObjC(tsType, nullable = false, options = {}) {
28
28
  }
29
29
  return nullable ? 'id _Nullable' : 'id';
30
30
  }
31
+ function mapParamToObjC(param, nullable = false, options = {}) {
32
+ if (param.callback) {
33
+ return param.optional
34
+ ? 'RCTResponseSenderBlock _Nullable'
35
+ : 'RCTResponseSenderBlock';
36
+ }
37
+ return mapTsTypeToObjC(param.type, nullable, options);
38
+ }
31
39
  function mapTsTypeToSwift(tsType, optional = false, options = {}) {
32
40
  if (tsType.startsWith('Promise<')) {
33
41
  const inner = tsType.slice(8, -1);
@@ -42,17 +50,35 @@ function mapTsTypeToSwift(tsType, optional = false, options = {}) {
42
50
  }
43
51
  return optional ? 'Any?' : 'Any';
44
52
  }
53
+ /** ObjC-compatible closures that match RCT typedefs without importing React. */
54
+ const SWIFT_RESPONSE_SENDER_BLOCK = '@escaping ([Any]?) -> Void';
55
+ const SWIFT_OPTIONAL_RESPONSE_SENDER_BLOCK = '(([Any]?) -> Void)?';
56
+ const SWIFT_PROMISE_RESOLVE_BLOCK = '@escaping (Any?) -> Void';
57
+ const SWIFT_PROMISE_REJECT_BLOCK = '@escaping (String?, String?, (any Error)?) -> Void';
58
+ function mapParamToSwift(param, options = {}) {
59
+ if (param.callback) {
60
+ return param.optional
61
+ ? SWIFT_OPTIONAL_RESPONSE_SENDER_BLOCK
62
+ : SWIFT_RESPONSE_SENDER_BLOCK;
63
+ }
64
+ return mapTsTypeToSwift(param.type, param.optional, options);
65
+ }
45
66
  export function generateSwiftDelegate(methods, options = {}) {
46
67
  const protocolMethods = methods
47
68
  .map((method) => {
48
- const params = method.params
49
- .map((param, index) => {
50
- const swiftType = mapTsTypeToSwift(param.type, param.optional, options);
69
+ const methodParams = method.params.map((param, index) => {
70
+ const swiftType = mapParamToSwift(param, options);
51
71
  const label = index === 0 ? '_' : param.name;
52
72
  return `${label} ${param.name}: ${swiftType}`;
53
- })
54
- .join(', ');
55
- const returnType = method.returnType === 'void'
73
+ });
74
+ const promiseParams = method.isAsync
75
+ ? [
76
+ `${methodParams.length === 0 ? '_ resolve' : 'resolve'}: ${SWIFT_PROMISE_RESOLVE_BLOCK}`,
77
+ `reject: ${SWIFT_PROMISE_REJECT_BLOCK}`,
78
+ ]
79
+ : [];
80
+ const params = [...methodParams, ...promiseParams].join(', ');
81
+ const returnType = method.returnType === 'void' || method.isAsync
56
82
  ? ''
57
83
  : ` -> ${mapTsTypeToSwift(method.returnType, false, options)}`;
58
84
  return ` @objc func ${method.name}(${params})${returnType}`;
@@ -97,20 +123,10 @@ ${methodImplementations}
97
123
  @end
98
124
  `;
99
125
  }
100
- function generateSyncObjCMethod(method, options) {
101
- const { name, params, returnType } = method;
102
- let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
103
- if (params.length > 0) {
104
- signature += params
105
- .map((param, index) => {
106
- const prefix = index === 0 ? ':' : ` ${param.name}:`;
107
- return `${prefix}(${mapTsTypeToObjC(param.type, param.optional, options)})${param.name}`;
108
- })
109
- .join('');
110
- }
126
+ function prepareObjCArgs(method, options) {
111
127
  const preparedParams = [];
112
128
  const delegateArgs = [];
113
- for (const param of params) {
129
+ for (const param of method.params) {
114
130
  if (options.modelTypeNames?.includes(param.type)) {
115
131
  const convertedParamName = `${param.name}Model`;
116
132
  preparedParams.push(`${param.type} *${convertedParamName} = ${param.name} == nil ? nil : [${param.type} fromDictionary:${param.name}];`);
@@ -120,6 +136,9 @@ function generateSyncObjCMethod(method, options) {
120
136
  delegateArgs.push(param.name);
121
137
  }
122
138
  }
139
+ return { preparedParams, delegateArgs };
140
+ }
141
+ function buildObjCDelegateCall(name, params, delegateArgs) {
123
142
  let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`;
124
143
  if (delegateArgs.length > 0) {
125
144
  delegateCall += delegateArgs
@@ -131,6 +150,21 @@ function generateSyncObjCMethod(method, options) {
131
150
  .join(' ');
132
151
  }
133
152
  delegateCall += ']';
153
+ return delegateCall;
154
+ }
155
+ function generateSyncObjCMethod(method, options) {
156
+ const { name, params, returnType } = method;
157
+ let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
158
+ if (params.length > 0) {
159
+ signature += params
160
+ .map((param, index) => {
161
+ const prefix = index === 0 ? ':' : ` ${param.name}:`;
162
+ return `${prefix}(${mapParamToObjC(param, param.optional, options)})${param.name}`;
163
+ })
164
+ .join('');
165
+ }
166
+ const { preparedParams, delegateArgs } = prepareObjCArgs(method, options);
167
+ const delegateCall = buildObjCDelegateCall(name, params, delegateArgs);
134
168
  const returnPrefix = returnType === 'void' ? '' : 'return ';
135
169
  const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
136
170
  const bodyPrefix = preparedLines.length > 0 ? `${preparedLines}\n` : '';
@@ -141,24 +175,29 @@ ${bodyPrefix} ${returnPrefix}${delegateCall};
141
175
  function generateAsyncObjCMethod(method, options) {
142
176
  const { name, params } = method;
143
177
  let signature = `- (void)${name}`;
144
- const allParams = [
145
- ...params,
178
+ const promiseParams = [
146
179
  { name: 'resolve', type: 'RCTPromiseResolveBlock', optional: false },
147
180
  { name: 'reject', type: 'RCTPromiseRejectBlock', optional: false },
148
181
  ];
182
+ const allParams = [...params, ...promiseParams];
149
183
  signature += ':';
150
184
  signature += allParams
151
185
  .map((param, index) => {
152
- const prefix = index === 0 ? '' : param.name;
186
+ const prefix = index === 0 ? '' : `${param.name}:`;
153
187
  const type = param.type === 'RCTPromiseResolveBlock'
154
188
  ? 'RCTPromiseResolveBlock'
155
189
  : param.type === 'RCTPromiseRejectBlock'
156
190
  ? 'RCTPromiseRejectBlock'
157
- : mapTsTypeToObjC(param.type, param.optional, options);
191
+ : mapParamToObjC(param, param.optional, options);
158
192
  return `${prefix}(${type})${param.name}`;
159
193
  })
160
194
  .join(' ');
195
+ const { preparedParams, delegateArgs } = prepareObjCArgs(method, options);
196
+ const asyncDelegateArgs = [...delegateArgs, 'resolve', 'reject'];
197
+ const delegateCall = buildObjCDelegateCall(name, allParams, asyncDelegateArgs);
198
+ const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
199
+ const bodyPrefix = preparedLines.length > 0 ? `${preparedLines}\n` : '';
161
200
  return `${signature} {
162
- reject(@"not_implemented", @"${name} is not implemented", nil);
201
+ ${bodyPrefix} ${delegateCall};
163
202
  }`;
164
203
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/navigation/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkCpB,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CA4G1E"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/navigation/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAMV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAyFpB,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAuH1E"}
@@ -29,6 +29,42 @@ function collectReferencedTypesFromText(typeText) {
29
29
  }
30
30
  return matches.filter((match) => !SKIP_TYPE_TOKENS.has(match));
31
31
  }
32
+ function getPromiseInnerType(typeText) {
33
+ return typeText.startsWith('Promise<') && typeText.endsWith('>')
34
+ ? typeText.slice(8, -1)
35
+ : undefined;
36
+ }
37
+ function parseCallbackSignature(typeNode) {
38
+ if (!typeNode || !Node.isFunctionTypeNode(typeNode)) {
39
+ return undefined;
40
+ }
41
+ return {
42
+ params: typeNode.getParameters().map((param) => ({
43
+ name: param.getName(),
44
+ type: param.getTypeNode()?.getText() ?? 'unknown',
45
+ optional: param.isOptional(),
46
+ })),
47
+ returnType: typeNode.getReturnTypeNode()?.getText() ?? 'void',
48
+ };
49
+ }
50
+ const RESERVED_ASYNC_PARAM_NAMES = new Set(['promise', 'resolve', 'reject']);
51
+ function validateMethodSignature(method) {
52
+ for (const param of method.params) {
53
+ if (getPromiseInnerType(param.type)) {
54
+ throw new Error(`Unsupported Promise parameter "${param.name}" in method "${method.name}": Promise<T> is only supported as a method return type.`);
55
+ }
56
+ if (method.isAsync &&
57
+ RESERVED_ASYNC_PARAM_NAMES.has(param.name)) {
58
+ throw new Error(`Reserved parameter name "${param.name}" in async method "${method.name}": this name is used by the generated bridging code. Rename the parameter.`);
59
+ }
60
+ if (!param.callback) {
61
+ continue;
62
+ }
63
+ if (param.callback.returnType !== 'void') {
64
+ throw new Error(`Unsupported callback parameter "${param.name}" in method "${method.name}": callback return type "${param.callback.returnType}" is not supported. Use a void callback or model the result as a Promise-returning navigation method.`);
65
+ }
66
+ }
67
+ }
32
68
  export function parseNavigationSpec(specPath) {
33
69
  if (!fs.existsSync(specPath)) {
34
70
  throw new Error(`Spec file not found: ${specPath}`);
@@ -44,20 +80,31 @@ export function parseNavigationSpec(specPath) {
44
80
  const name = method.getName();
45
81
  const params = method.getParameters().map((param) => {
46
82
  const typeNode = param.getTypeNode();
47
- return {
83
+ const callback = parseCallbackSignature(typeNode);
84
+ const methodParam = {
48
85
  name: param.getName(),
49
86
  type: typeNode?.getText() ?? 'unknown',
50
87
  optional: param.isOptional(),
51
88
  };
89
+ if (callback) {
90
+ methodParam.callback = callback;
91
+ }
92
+ return methodParam;
52
93
  });
53
94
  const returnTypeNode = method.getReturnTypeNode();
54
95
  const returnType = returnTypeNode?.getText() ?? 'void';
55
- return {
96
+ const promiseReturnType = getPromiseInnerType(returnType);
97
+ const methodSignature = {
56
98
  name,
57
99
  params,
58
100
  returnType,
59
- isAsync: returnType.startsWith('Promise<'),
101
+ isAsync: Boolean(promiseReturnType),
60
102
  };
103
+ if (promiseReturnType) {
104
+ methodSignature.promiseReturnType = promiseReturnType;
105
+ }
106
+ validateMethodSignature(methodSignature);
107
+ return methodSignature;
61
108
  });
62
109
  const declarationNodes = [
63
110
  ...sourceFile.getTypeAliases(),
@@ -1,13 +1,24 @@
1
+ export interface CallbackParam {
2
+ name: string;
3
+ type: string;
4
+ optional: boolean;
5
+ }
6
+ export interface CallbackSignature {
7
+ params: CallbackParam[];
8
+ returnType: string;
9
+ }
1
10
  export interface MethodParam {
2
11
  name: string;
3
12
  type: string;
4
13
  optional: boolean;
14
+ callback?: CallbackSignature;
5
15
  }
6
16
  export interface MethodSignature {
7
17
  name: string;
8
18
  params: MethodParam[];
9
19
  returnType: string;
10
20
  isAsync: boolean;
21
+ promiseReturnType?: string;
11
22
  }
12
23
  export interface TypeDeclaration {
13
24
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,0BAA0B,EAAE,eAAe,EAAE,CAAC;IAC9C,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,0BAA0B,EAAE,eAAe,EAAE,CAAC;IAC9C,gBAAgB,EAAE,eAAe,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
package/dist/types.d.ts CHANGED
@@ -39,6 +39,14 @@ export type BrownfieldExpoAndroidConfig = {
39
39
  * The package name for the Android library module.
40
40
  */
41
41
  packageName?: string;
42
+ /**
43
+ * Toggle minification for the generated Android library.
44
+ */
45
+ minifyEnabled?: boolean;
46
+ /**
47
+ * Extra Proguard rules appended to the default rules.
48
+ */
49
+ extraProguardRules?: string[];
42
50
  /**
43
51
  * Minimum SDK version for the Android library.
44
52
  */
@@ -70,6 +78,12 @@ export type BrownfieldExpoAndroidConfig = {
70
78
  * Disabled by default.
71
79
  */
72
80
  useLocalGradlePlugin?: boolean;
81
+ /**
82
+ * When true, prefer artifacts from the local Maven repository
83
+ * when resolving the Brownfield plugin dependencies.
84
+ * Disabled by default.
85
+ */
86
+ useLocalMaven?: boolean;
73
87
  /**
74
88
  * Missing dimension strategies as dimension + flavor(s), e.g. "type, alpha".
75
89
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callstack/brownfield-cli",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Artur Morys-Magiera <artus9033@gmail.com>",
6
6
  "bin": {
package/schema.json CHANGED
@@ -57,6 +57,13 @@
57
57
  "description": "Compile SDK version for the Android library.",
58
58
  "type": "number"
59
59
  },
60
+ "extraProguardRules": {
61
+ "description": "Extra Proguard rules appended to the default rules. Each entry must be a non-empty string.",
62
+ "items": {
63
+ "type": "string"
64
+ },
65
+ "type": "array"
66
+ },
60
67
  "groupId": {
61
68
  "description": "Maven group ID used when publishing the AAR.",
62
69
  "type": "string"
@@ -65,6 +72,10 @@
65
72
  "description": "Minimum SDK version for the Android library.",
66
73
  "type": "number"
67
74
  },
75
+ "minifyEnabled": {
76
+ "description": "Toggle minification for the generated Android library.",
77
+ "type": "boolean"
78
+ },
68
79
  "missingDimensionStrategies": {
69
80
  "description": "Missing dimension strategies as dimension + flavor(s), e.g. \"type, alpha\".",
70
81
  "items": {
@@ -84,6 +95,10 @@
84
95
  "description": "When true, load the Brownfield Gradle plugin from `@callstack/react-native-brownfield/gradle-plugin/brownfield` via `includeBuild` instead of adding the Maven classpath dependency. Disabled by default.",
85
96
  "type": "boolean"
86
97
  },
98
+ "useLocalMaven": {
99
+ "description": "When true, prefer artifacts from the local Maven repository when resolving the Brownfield plugin dependencies. Disabled by default.",
100
+ "type": "boolean"
101
+ },
87
102
  "version": {
88
103
  "description": "Maven version used when publishing the AAR.",
89
104
  "type": "string"
@@ -19,12 +19,25 @@ import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNa
19
19
  import { mergeBrownfieldConfigWithOptions } from '../../config.js';
20
20
 
21
21
  export const packageAndroidCommand = curryOptions(
22
- new Command('package:android').description('Build Android AAR'),
22
+ new Command('package:android')
23
+ .description('Build Android AAR')
24
+ .option(
25
+ '--use-local-maven',
26
+ 'Use local Maven for Brownfield plugin resolution'
27
+ ),
23
28
  packageAarOptions
24
29
  ).action(
25
- actionRunner(async (cliOptions: PackageAarFlags) => {
26
- const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
27
- const projectRoot = findProjectRoot();
30
+ actionRunner(
31
+ async (cliOptions: PackageAarFlags & { useLocalMaven?: boolean }) => {
32
+ const { useLocalMaven, ...restOptions } = cliOptions;
33
+ const options = mergeBrownfieldConfigWithOptions(
34
+ {
35
+ ...restOptions,
36
+ ...(useLocalMaven ? { useLocalMaven: true } : {}),
37
+ },
38
+ 'android'
39
+ );
40
+ const projectRoot = findProjectRoot();
28
41
 
29
42
  await runExpoPrebuildIfNeeded({
30
43
  projectRoot,
@@ -19,13 +19,24 @@ import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNa
19
19
  import { mergeBrownfieldConfigWithOptions } from '../../config.js';
20
20
 
21
21
  export const publishAndroidCommand = curryOptions(
22
- new Command('publish:android').description(
23
- 'Publish Android package to Maven local'
24
- ),
22
+ new Command('publish:android')
23
+ .description('Publish Android package to Maven local')
24
+ .option(
25
+ '--use-local-maven',
26
+ 'Use local Maven for Brownfield plugin resolution'
27
+ ),
25
28
  publishLocalAarOptions
26
29
  ).action(
27
- actionRunner(async (cliOptions: PublishLocalAarFlags) => {
28
- const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
30
+ actionRunner(
31
+ async (cliOptions: PublishLocalAarFlags & { useLocalMaven?: boolean }) => {
32
+ const { useLocalMaven, ...restOptions } = cliOptions;
33
+ const options = mergeBrownfieldConfigWithOptions(
34
+ {
35
+ ...restOptions,
36
+ ...(useLocalMaven ? { useLocalMaven: true } : {}),
37
+ },
38
+ 'android'
39
+ );
29
40
  const projectRoot = findProjectRoot();
30
41
 
31
42
  await runExpoPrebuildIfNeeded({
@@ -38,12 +49,13 @@ export const publishAndroidCommand = curryOptions(
38
49
  await runBrownieCodegenIfApplicable(projectRoot, 'kotlin');
39
50
  await runNavigationCodegenIfApplicable(projectRoot);
40
51
 
41
- await publishLocalAarAction({
42
- projectRoot,
43
- pluginConfig: platformConfig,
44
- moduleName: options.moduleName,
45
- });
46
- })
52
+ await publishLocalAarAction({
53
+ projectRoot,
54
+ pluginConfig: platformConfig,
55
+ moduleName: options.moduleName,
56
+ });
57
+ }
58
+ )
47
59
  );
48
60
 
49
61
  export const publishAndroidExample = new ExampleUsage(
@@ -24,6 +24,9 @@ export type BrownfieldPluginProps = {
24
24
  artifactId?: string;
25
25
  version?: string;
26
26
  useLocalGradlePlugin?: boolean;
27
+ minifyEnabled?: boolean;
28
+ extraProguardRules?: string[];
29
+ useLocalMaven?: boolean;
27
30
  missingDimensionStrategies?: string[];
28
31
  };
29
32
  };
@@ -38,6 +41,9 @@ export type ResolvedBrownfieldPluginAndroidConfig = {
38
41
  artifactId: string;
39
42
  version: string;
40
43
  useLocalGradlePlugin: boolean;
44
+ minifyEnabled: boolean;
45
+ extraProguardRules: string[];
46
+ useLocalMaven: boolean;
41
47
  missingDimensionStrategies: string[];
42
48
  };
43
49
 
@@ -154,6 +160,9 @@ export function resolveBrownfieldPluginConfig(
154
160
  ): ResolvedBrownfieldPluginConfig {
155
161
  const effectiveProps =
156
162
  fileConfig !== null ? fileConfigToPluginProps(fileConfig) : pluginProps;
163
+ const extraProguardRules = (
164
+ effectiveProps.android?.extraProguardRules ?? []
165
+ ).filter((rule) => rule.trim().length > 0);
157
166
 
158
167
  const androidPackage = expoConfig.android?.package;
159
168
  /**
@@ -191,6 +200,9 @@ export function resolveBrownfieldPluginConfig(
191
200
  version: effectiveProps.android?.version ?? '0.0.1-SNAPSHOT',
192
201
  useLocalGradlePlugin:
193
202
  effectiveProps.android?.useLocalGradlePlugin ?? false,
203
+ minifyEnabled: effectiveProps.android?.minifyEnabled ?? false,
204
+ extraProguardRules,
205
+ useLocalMaven: effectiveProps.android?.useLocalMaven ?? false,
194
206
  missingDimensionStrategies:
195
207
  effectiveProps.android?.missingDimensionStrategies ?? [],
196
208
  }
@@ -1,4 +1,4 @@
1
- import type { MethodSignature } from '../types.js';
1
+ import type { MethodParam, MethodSignature } from '../types.js';
2
2
 
3
3
  const TS_TO_KOTLIN_TYPE: Record<string, string> = {
4
4
  string: 'String',
@@ -12,6 +12,13 @@ interface KotlinTypeMappingOptions {
12
12
  modelTypeNames?: string[];
13
13
  }
14
14
 
15
+ function buildKotlinImports(imports: string[]): string {
16
+ if (imports.length === 0) {
17
+ return '\n';
18
+ }
19
+ return `\n${[...new Set(imports)].sort().join('\n')}\n`;
20
+ }
21
+
15
22
  function mapTsTypeToKotlin(
16
23
  tsType: string,
17
24
  optional: boolean = false,
@@ -38,21 +45,67 @@ function mapTsTypeToKotlin(
38
45
  return optional ? 'Any?' : 'Any';
39
46
  }
40
47
 
48
+ function mapParamToKotlin(
49
+ param: MethodParam,
50
+ options: KotlinTypeMappingOptions = {},
51
+ layer: 'delegate' | 'module' = 'delegate'
52
+ ): string {
53
+ if (param.callback) {
54
+ return param.optional ? 'Callback?' : 'Callback';
55
+ }
56
+ return mapTsTypeToKotlin(param.type, param.optional, options, layer);
57
+ }
58
+
59
+ function prepareKotlinArgs(
60
+ method: MethodSignature,
61
+ options: KotlinTypeMappingOptions = {}
62
+ ): { preparedParams: string[]; args: string } {
63
+ const preparedParams: string[] = [];
64
+ const args = method.params
65
+ .map((param) => {
66
+ if (options.modelTypeNames?.includes(param.type)) {
67
+ const convertedName = `${param.name}Model`;
68
+ preparedParams.push(
69
+ ` val ${convertedName} = ${param.name}${
70
+ param.optional
71
+ ? `?.let(::to${param.type})`
72
+ : `.let(::to${param.type})`
73
+ }`
74
+ );
75
+ return convertedName;
76
+ }
77
+ return param.name;
78
+ })
79
+ .join(', ');
80
+
81
+ return { preparedParams, args };
82
+ }
83
+
41
84
  export function generateKotlinDelegate(
42
85
  methods: MethodSignature[],
43
86
  kotlinPackageName: string,
44
87
  options: KotlinTypeMappingOptions = {}
45
88
  ): string {
89
+ const hasAsyncMethod = methods.some((method) => method.isAsync);
90
+ const hasCallbackParam = methods.some((method) =>
91
+ method.params.some((param) => param.callback)
92
+ );
93
+ const imports = buildKotlinImports([
94
+ ...(hasCallbackParam ? ['import com.facebook.react.bridge.Callback'] : []),
95
+ ...(hasAsyncMethod ? ['import com.facebook.react.bridge.Promise'] : []),
96
+ ]);
97
+
46
98
  const methodSignatures = methods
47
99
  .map((method) => {
48
- const params = method.params
49
- .map(
50
- (param) =>
51
- `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options)}`
52
- )
53
- .join(', ');
100
+ const methodParams = method.params.map(
101
+ (param) => `${param.name}: ${mapParamToKotlin(param, options)}`
102
+ );
103
+ const params = [
104
+ ...methodParams,
105
+ ...(method.isAsync ? ['promise: Promise'] : []),
106
+ ].join(', ');
54
107
  const returnType =
55
- method.returnType === 'void'
108
+ method.returnType === 'void' || method.isAsync
56
109
  ? ''
57
110
  : `: ${mapTsTypeToKotlin(method.returnType, false, options, 'delegate')}`;
58
111
  return ` fun ${method.name}(${params})${returnType}`;
@@ -60,8 +113,7 @@ export function generateKotlinDelegate(
60
113
  .join('\n');
61
114
 
62
115
  return `package ${kotlinPackageName}
63
-
64
- interface BrownfieldNavigationDelegate {
116
+ ${imports}interface BrownfieldNavigationDelegate {
65
117
  ${methodSignatures}
66
118
  }
67
119
  `;
@@ -73,6 +125,9 @@ export function generateKotlinModule(
73
125
  options: KotlinTypeMappingOptions = {}
74
126
  ): string {
75
127
  const hasAsyncMethod = methods.some((method) => method.isAsync);
128
+ const hasCallbackParam = methods.some((method) =>
129
+ method.params.some((param) => param.callback)
130
+ );
76
131
  const hasObjectType = methods.some(
77
132
  (method) =>
78
133
  method.returnType.includes('Object') ||
@@ -94,8 +149,8 @@ export function generateKotlinModule(
94
149
 
95
150
  import com.facebook.react.bridge.ReactApplicationContext
96
151
  import com.facebook.react.bridge.ReactMethod${
97
- hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''
98
- }${hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : ''}
152
+ hasCallbackParam ? '\nimport com.facebook.react.bridge.Callback' : ''
153
+ }${hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''}${hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : ''}
99
154
 
100
155
  class NativeBrownfieldNavigationModule(
101
156
  reactContext: ReactApplicationContext
@@ -115,27 +170,10 @@ function generateSyncKotlinMethod(
115
170
  ): string {
116
171
  const params = method.params
117
172
  .map(
118
- (param) =>
119
- `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`
173
+ (param) => `${param.name}: ${mapParamToKotlin(param, options, 'module')}`
120
174
  )
121
175
  .join(', ');
122
- const preparedParams: string[] = [];
123
- const args = method.params
124
- .map((param) => {
125
- if (options.modelTypeNames?.includes(param.type)) {
126
- const convertedName = `${param.name}Model`;
127
- preparedParams.push(
128
- ` val ${convertedName} = ${param.name}${
129
- param.optional
130
- ? `?.let(::to${param.type})`
131
- : `.let(::to${param.type})`
132
- }`
133
- );
134
- return convertedName;
135
- }
136
- return param.name;
137
- })
138
- .join(', ');
176
+ const { preparedParams, args } = prepareKotlinArgs(method, options);
139
177
 
140
178
  const signature = ` @ReactMethod\n override fun ${method.name}(${params})${
141
179
  method.returnType === 'void'
@@ -161,17 +199,19 @@ function generateAsyncKotlinMethod(
161
199
  ): string {
162
200
  const paramsWithTypes = method.params
163
201
  .map(
164
- (param) =>
165
- `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional, options, 'module')}`
202
+ (param) => `${param.name}: ${mapParamToKotlin(param, options, 'module')}`
166
203
  )
167
204
  .join(', ');
168
205
  const params =
169
206
  paramsWithTypes.length > 0
170
207
  ? `${paramsWithTypes}, promise: Promise`
171
208
  : 'promise: Promise';
209
+ const { preparedParams, args } = prepareKotlinArgs(method, options);
210
+ const delegateArgs = args.length > 0 ? `${args}, promise` : 'promise';
211
+ const preparedPrefix = preparedParams.length > 0 ? `${preparedParams.join('\n')}\n` : '';
172
212
 
173
213
  return ` @ReactMethod
174
214
  override fun ${method.name}(${params}) {
175
- promise.reject("not_implemented", "${method.name} is not implemented")
215
+ ${preparedPrefix} BrownfieldNavigationManager.getDelegate().${method.name}(${delegateArgs})
176
216
  }`;
177
217
  }
@@ -1,4 +1,4 @@
1
- import type { MethodSignature } from '../types.js';
1
+ import type { MethodParam, MethodSignature } from '../types.js';
2
2
 
3
3
  const TS_TO_OBJC_TYPE: Record<string, string> = {
4
4
  string: 'NSString *',
@@ -44,6 +44,19 @@ function mapTsTypeToObjC(
44
44
  return nullable ? 'id _Nullable' : 'id';
45
45
  }
46
46
 
47
+ function mapParamToObjC(
48
+ param: MethodParam,
49
+ nullable: boolean = false,
50
+ options: ObjCTypeMappingOptions = {}
51
+ ): string {
52
+ if (param.callback) {
53
+ return param.optional
54
+ ? 'RCTResponseSenderBlock _Nullable'
55
+ : 'RCTResponseSenderBlock';
56
+ }
57
+ return mapTsTypeToObjC(param.type, nullable, options);
58
+ }
59
+
47
60
  interface SwiftTypeMappingOptions {
48
61
  modelTypeNames?: string[];
49
62
  }
@@ -72,22 +85,46 @@ function mapTsTypeToSwift(
72
85
  return optional ? 'Any?' : 'Any';
73
86
  }
74
87
 
88
+ /** ObjC-compatible closures that match RCT typedefs without importing React. */
89
+ const SWIFT_RESPONSE_SENDER_BLOCK = '@escaping ([Any]?) -> Void';
90
+ const SWIFT_OPTIONAL_RESPONSE_SENDER_BLOCK = '(([Any]?) -> Void)?';
91
+ const SWIFT_PROMISE_RESOLVE_BLOCK = '@escaping (Any?) -> Void';
92
+ const SWIFT_PROMISE_REJECT_BLOCK =
93
+ '@escaping (String?, String?, (any Error)?) -> Void';
94
+
95
+ function mapParamToSwift(
96
+ param: MethodParam,
97
+ options: SwiftTypeMappingOptions = {}
98
+ ): string {
99
+ if (param.callback) {
100
+ return param.optional
101
+ ? SWIFT_OPTIONAL_RESPONSE_SENDER_BLOCK
102
+ : SWIFT_RESPONSE_SENDER_BLOCK;
103
+ }
104
+ return mapTsTypeToSwift(param.type, param.optional, options);
105
+ }
106
+
75
107
  export function generateSwiftDelegate(
76
108
  methods: MethodSignature[],
77
109
  options: SwiftTypeMappingOptions = {}
78
110
  ): string {
79
111
  const protocolMethods = methods
80
112
  .map((method) => {
81
- const params = method.params
82
- .map((param, index) => {
83
- const swiftType = mapTsTypeToSwift(param.type, param.optional, options);
84
- const label = index === 0 ? '_' : param.name;
85
- return `${label} ${param.name}: ${swiftType}`;
86
- })
87
- .join(', ');
113
+ const methodParams = method.params.map((param, index) => {
114
+ const swiftType = mapParamToSwift(param, options);
115
+ const label = index === 0 ? '_' : param.name;
116
+ return `${label} ${param.name}: ${swiftType}`;
117
+ });
118
+ const promiseParams = method.isAsync
119
+ ? [
120
+ `${methodParams.length === 0 ? '_ resolve' : 'resolve'}: ${SWIFT_PROMISE_RESOLVE_BLOCK}`,
121
+ `reject: ${SWIFT_PROMISE_REJECT_BLOCK}`,
122
+ ]
123
+ : [];
124
+ const params = [...methodParams, ...promiseParams].join(', ');
88
125
 
89
126
  const returnType =
90
- method.returnType === 'void'
127
+ method.returnType === 'void' || method.isAsync
91
128
  ? ''
92
129
  : ` -> ${mapTsTypeToSwift(method.returnType, false, options)}`;
93
130
 
@@ -142,25 +179,13 @@ ${methodImplementations}
142
179
  `;
143
180
  }
144
181
 
145
- function generateSyncObjCMethod(
182
+ function prepareObjCArgs(
146
183
  method: MethodSignature,
147
184
  options: ObjCGenerationOptions
148
- ): string {
149
- const { name, params, returnType } = method;
150
-
151
- let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
152
- if (params.length > 0) {
153
- signature += params
154
- .map((param, index) => {
155
- const prefix = index === 0 ? ':' : ` ${param.name}:`;
156
- return `${prefix}(${mapTsTypeToObjC(param.type, param.optional, options)})${param.name}`;
157
- })
158
- .join('');
159
- }
160
-
185
+ ): { preparedParams: string[]; delegateArgs: string[] } {
161
186
  const preparedParams: string[] = [];
162
187
  const delegateArgs: string[] = [];
163
- for (const param of params) {
188
+ for (const param of method.params) {
164
189
  if (options.modelTypeNames?.includes(param.type)) {
165
190
  const convertedParamName = `${param.name}Model`;
166
191
  preparedParams.push(
@@ -171,7 +196,14 @@ function generateSyncObjCMethod(
171
196
  delegateArgs.push(param.name);
172
197
  }
173
198
  }
199
+ return { preparedParams, delegateArgs };
200
+ }
174
201
 
202
+ function buildObjCDelegateCall(
203
+ name: string,
204
+ params: Array<{ name: string; type: string; optional: boolean }>,
205
+ delegateArgs: string[]
206
+ ): string {
175
207
  let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`;
176
208
  if (delegateArgs.length > 0) {
177
209
  delegateCall += delegateArgs
@@ -183,6 +215,27 @@ function generateSyncObjCMethod(
183
215
  .join(' ');
184
216
  }
185
217
  delegateCall += ']';
218
+ return delegateCall;
219
+ }
220
+
221
+ function generateSyncObjCMethod(
222
+ method: MethodSignature,
223
+ options: ObjCGenerationOptions
224
+ ): string {
225
+ const { name, params, returnType } = method;
226
+
227
+ let signature = `- (${mapTsTypeToObjC(returnType, false, options)})${name}`;
228
+ if (params.length > 0) {
229
+ signature += params
230
+ .map((param, index) => {
231
+ const prefix = index === 0 ? ':' : ` ${param.name}:`;
232
+ return `${prefix}(${mapParamToObjC(param, param.optional, options)})${param.name}`;
233
+ })
234
+ .join('');
235
+ }
236
+
237
+ const { preparedParams, delegateArgs } = prepareObjCArgs(method, options);
238
+ const delegateCall = buildObjCDelegateCall(name, params, delegateArgs);
186
239
 
187
240
  const returnPrefix = returnType === 'void' ? '' : 'return ';
188
241
  const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
@@ -200,27 +253,33 @@ function generateAsyncObjCMethod(
200
253
  const { name, params } = method;
201
254
 
202
255
  let signature = `- (void)${name}`;
203
- const allParams: Array<{ name: string; type: string; optional: boolean }> = [
204
- ...params,
256
+ const promiseParams: Array<{ name: string; type: string; optional: boolean }> = [
205
257
  { name: 'resolve', type: 'RCTPromiseResolveBlock', optional: false },
206
258
  { name: 'reject', type: 'RCTPromiseRejectBlock', optional: false },
207
259
  ];
260
+ const allParams = [...params, ...promiseParams];
208
261
 
209
262
  signature += ':';
210
263
  signature += allParams
211
264
  .map((param, index) => {
212
- const prefix = index === 0 ? '' : param.name;
265
+ const prefix = index === 0 ? '' : `${param.name}:`;
213
266
  const type =
214
267
  param.type === 'RCTPromiseResolveBlock'
215
268
  ? 'RCTPromiseResolveBlock'
216
269
  : param.type === 'RCTPromiseRejectBlock'
217
270
  ? 'RCTPromiseRejectBlock'
218
- : mapTsTypeToObjC(param.type, param.optional, options);
271
+ : mapParamToObjC(param, param.optional, options);
219
272
  return `${prefix}(${type})${param.name}`;
220
273
  })
221
274
  .join(' ');
222
275
 
276
+ const { preparedParams, delegateArgs } = prepareObjCArgs(method, options);
277
+ const asyncDelegateArgs = [...delegateArgs, 'resolve', 'reject'];
278
+ const delegateCall = buildObjCDelegateCall(name, allParams, asyncDelegateArgs);
279
+ const preparedLines = preparedParams.map((line) => ` ${line}`).join('\n');
280
+ const bodyPrefix = preparedLines.length > 0 ? `${preparedLines}\n` : '';
281
+
223
282
  return `${signature} {
224
- reject(@"not_implemented", @"${name} is not implemented", nil);
283
+ ${bodyPrefix} ${delegateCall};
225
284
  }`;
226
285
  }
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import { Project } from 'ts-morph';
3
3
 
4
4
  import type {
5
+ CallbackSignature,
5
6
  ModelDefinition,
6
7
  ModelFieldDefinition,
7
8
  MethodParam,
@@ -10,6 +11,7 @@ import type {
10
11
  TypeDeclaration,
11
12
  } from './types.js';
12
13
  import { Node } from 'ts-morph';
14
+ import type { TypeNode } from 'ts-morph';
13
15
 
14
16
  const SKIP_TYPE_TOKENS = new Set([
15
17
  'Array',
@@ -42,6 +44,60 @@ function collectReferencedTypesFromText(typeText: string): string[] {
42
44
  return matches.filter((match) => !SKIP_TYPE_TOKENS.has(match));
43
45
  }
44
46
 
47
+ function getPromiseInnerType(typeText: string): string | undefined {
48
+ return typeText.startsWith('Promise<') && typeText.endsWith('>')
49
+ ? typeText.slice(8, -1)
50
+ : undefined;
51
+ }
52
+
53
+ function parseCallbackSignature(
54
+ typeNode: TypeNode | undefined
55
+ ): CallbackSignature | undefined {
56
+ if (!typeNode || !Node.isFunctionTypeNode(typeNode)) {
57
+ return undefined;
58
+ }
59
+
60
+ return {
61
+ params: typeNode.getParameters().map((param) => ({
62
+ name: param.getName(),
63
+ type: param.getTypeNode()?.getText() ?? 'unknown',
64
+ optional: param.isOptional(),
65
+ })),
66
+ returnType: typeNode.getReturnTypeNode()?.getText() ?? 'void',
67
+ };
68
+ }
69
+
70
+ const RESERVED_ASYNC_PARAM_NAMES = new Set(['promise', 'resolve', 'reject']);
71
+
72
+ function validateMethodSignature(method: MethodSignature): void {
73
+ for (const param of method.params) {
74
+ if (getPromiseInnerType(param.type)) {
75
+ throw new Error(
76
+ `Unsupported Promise parameter "${param.name}" in method "${method.name}": Promise<T> is only supported as a method return type.`
77
+ );
78
+ }
79
+
80
+ if (
81
+ method.isAsync &&
82
+ RESERVED_ASYNC_PARAM_NAMES.has(param.name)
83
+ ) {
84
+ throw new Error(
85
+ `Reserved parameter name "${param.name}" in async method "${method.name}": this name is used by the generated bridging code. Rename the parameter.`
86
+ );
87
+ }
88
+
89
+ if (!param.callback) {
90
+ continue;
91
+ }
92
+
93
+ if (param.callback.returnType !== 'void') {
94
+ throw new Error(
95
+ `Unsupported callback parameter "${param.name}" in method "${method.name}": callback return type "${param.callback.returnType}" is not supported. Use a void callback or model the result as a Promise-returning navigation method.`
96
+ );
97
+ }
98
+ }
99
+ }
100
+
45
101
  export function parseNavigationSpec(specPath: string): ParsedNavigationSpec {
46
102
  if (!fs.existsSync(specPath)) {
47
103
  throw new Error(`Spec file not found: ${specPath}`);
@@ -63,21 +119,32 @@ export function parseNavigationSpec(specPath: string): ParsedNavigationSpec {
63
119
  const name = method.getName();
64
120
  const params: MethodParam[] = method.getParameters().map((param) => {
65
121
  const typeNode = param.getTypeNode();
66
- return {
122
+ const callback = parseCallbackSignature(typeNode);
123
+ const methodParam: MethodParam = {
67
124
  name: param.getName(),
68
125
  type: typeNode?.getText() ?? 'unknown',
69
126
  optional: param.isOptional(),
70
127
  };
128
+ if (callback) {
129
+ methodParam.callback = callback;
130
+ }
131
+ return methodParam;
71
132
  });
72
133
  const returnTypeNode = method.getReturnTypeNode();
73
134
  const returnType = returnTypeNode?.getText() ?? 'void';
135
+ const promiseReturnType = getPromiseInnerType(returnType);
74
136
 
75
- return {
137
+ const methodSignature: MethodSignature = {
76
138
  name,
77
139
  params,
78
140
  returnType,
79
- isAsync: returnType.startsWith('Promise<'),
141
+ isAsync: Boolean(promiseReturnType),
80
142
  };
143
+ if (promiseReturnType) {
144
+ methodSignature.promiseReturnType = promiseReturnType;
145
+ }
146
+ validateMethodSignature(methodSignature);
147
+ return methodSignature;
81
148
  });
82
149
 
83
150
  const declarationNodes = [
@@ -1,7 +1,19 @@
1
+ export interface CallbackParam {
2
+ name: string;
3
+ type: string;
4
+ optional: boolean;
5
+ }
6
+
7
+ export interface CallbackSignature {
8
+ params: CallbackParam[];
9
+ returnType: string;
10
+ }
11
+
1
12
  export interface MethodParam {
2
13
  name: string;
3
14
  type: string;
4
15
  optional: boolean;
16
+ callback?: CallbackSignature;
5
17
  }
6
18
 
7
19
  export interface MethodSignature {
@@ -9,6 +21,7 @@ export interface MethodSignature {
9
21
  params: MethodParam[];
10
22
  returnType: string;
11
23
  isAsync: boolean;
24
+ promiseReturnType?: string;
12
25
  }
13
26
 
14
27
  export interface TypeDeclaration {
package/src/types.ts CHANGED
@@ -56,6 +56,16 @@ export type BrownfieldExpoAndroidConfig = {
56
56
  */
57
57
  packageName?: string;
58
58
 
59
+ /**
60
+ * Toggle minification for the generated Android library.
61
+ */
62
+ minifyEnabled?: boolean;
63
+
64
+ /**
65
+ * Extra Proguard rules appended to the default rules.
66
+ */
67
+ extraProguardRules?: string[];
68
+
59
69
  /**
60
70
  * Minimum SDK version for the Android library.
61
71
  */
@@ -95,6 +105,13 @@ export type BrownfieldExpoAndroidConfig = {
95
105
  useLocalGradlePlugin?: boolean;
96
106
 
97
107
  /**
108
+ * When true, prefer artifacts from the local Maven repository
109
+ * when resolving the Brownfield plugin dependencies.
110
+ * Disabled by default.
111
+ */
112
+ useLocalMaven?: boolean;
113
+
114
+ /**
98
115
  * Missing dimension strategies as dimension + flavor(s), e.g. "type, alpha".
99
116
  */
100
117
  missingDimensionStrategies?: string[];