@callstack/brownfield-cli 3.0.0-rc.2 → 3.1.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +1 -1
  3. package/dist/brownfield/commands/packageAndroid.d.ts.map +1 -1
  4. package/dist/brownfield/commands/packageAndroid.js +2 -0
  5. package/dist/brownfield/commands/packageIos.d.ts.map +1 -1
  6. package/dist/brownfield/commands/packageIos.js +17 -1
  7. package/dist/brownfield/commands/publishAndroid.d.ts.map +1 -1
  8. package/dist/brownfield/commands/publishAndroid.js +2 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -0
  11. package/dist/navigation/commands/codegen.d.ts +9 -0
  12. package/dist/navigation/commands/codegen.d.ts.map +1 -0
  13. package/dist/navigation/commands/codegen.js +24 -0
  14. package/dist/navigation/commands/index.d.ts +2 -0
  15. package/dist/navigation/commands/index.d.ts.map +1 -0
  16. package/dist/navigation/commands/index.js +1 -0
  17. package/dist/navigation/config.d.ts +6 -0
  18. package/dist/navigation/config.d.ts.map +1 -0
  19. package/dist/navigation/config.js +25 -0
  20. package/dist/navigation/generators/android.d.ts +4 -0
  21. package/dist/navigation/generators/android.d.ts.map +1 -0
  22. package/dist/navigation/generators/android.js +91 -0
  23. package/dist/navigation/generators/ios.d.ts +4 -0
  24. package/dist/navigation/generators/ios.d.ts.map +1 -0
  25. package/dist/navigation/generators/ios.js +141 -0
  26. package/dist/navigation/generators/models.d.ts +13 -0
  27. package/dist/navigation/generators/models.d.ts.map +1 -0
  28. package/dist/navigation/generators/models.js +112 -0
  29. package/dist/navigation/generators/ts.d.ts +6 -0
  30. package/dist/navigation/generators/ts.d.ts.map +1 -0
  31. package/dist/navigation/generators/ts.js +96 -0
  32. package/dist/navigation/helpers/runNavigationCodegenIfApplicable.d.ts +5 -0
  33. package/dist/navigation/helpers/runNavigationCodegenIfApplicable.d.ts.map +1 -0
  34. package/dist/navigation/helpers/runNavigationCodegenIfApplicable.js +11 -0
  35. package/dist/navigation/index.d.ts +7 -0
  36. package/dist/navigation/index.d.ts.map +1 -0
  37. package/dist/navigation/index.js +6 -0
  38. package/dist/navigation/parser.d.ts +3 -0
  39. package/dist/navigation/parser.d.ts.map +1 -0
  40. package/dist/navigation/parser.js +33 -0
  41. package/dist/navigation/runner.d.ts +8 -0
  42. package/dist/navigation/runner.d.ts.map +1 -0
  43. package/dist/navigation/runner.js +126 -0
  44. package/dist/navigation/spec-discovery.d.ts +3 -0
  45. package/dist/navigation/spec-discovery.d.ts.map +1 -0
  46. package/dist/navigation/spec-discovery.js +15 -0
  47. package/dist/navigation/types.d.ts +24 -0
  48. package/dist/navigation/types.d.ts.map +1 -0
  49. package/dist/navigation/types.js +1 -0
  50. package/package.json +7 -2
  51. package/src/brownfield/commands/packageAndroid.ts +2 -0
  52. package/src/brownfield/commands/packageIos.ts +34 -1
  53. package/src/brownfield/commands/publishAndroid.ts +2 -0
  54. package/src/index.ts +4 -0
  55. package/src/navigation/commands/codegen.ts +57 -0
  56. package/src/navigation/commands/index.ts +1 -0
  57. package/src/navigation/config.ts +35 -0
  58. package/src/navigation/generators/android.ts +127 -0
  59. package/src/navigation/generators/ios.ts +169 -0
  60. package/src/navigation/generators/models.ts +170 -0
  61. package/src/navigation/generators/ts.ts +123 -0
  62. package/src/navigation/helpers/runNavigationCodegenIfApplicable.ts +17 -0
  63. package/src/navigation/index.ts +10 -0
  64. package/src/navigation/parser.ts +43 -0
  65. package/src/navigation/runner.ts +256 -0
  66. package/src/navigation/spec-discovery.ts +25 -0
  67. package/src/navigation/types.ts +25 -0
  68. package/dist/brownfield/utils/index.d.ts +0 -4
  69. package/dist/brownfield/utils/index.d.ts.map +0 -1
  70. package/dist/brownfield/utils/index.js +0 -3
  71. package/dist/brownfield/utils/rn-cli.d.ts +0 -17
  72. package/dist/brownfield/utils/rn-cli.d.ts.map +0 -1
  73. package/dist/brownfield/utils/rn-cli.js +0 -31
@@ -0,0 +1,127 @@
1
+ import type { MethodSignature } from '../types.js';
2
+
3
+ const TS_TO_KOTLIN_TYPE: Record<string, string> = {
4
+ string: 'String',
5
+ number: 'Double',
6
+ boolean: 'Boolean',
7
+ void: 'Unit',
8
+ Object: 'ReadableMap',
9
+ };
10
+
11
+ function mapTsTypeToKotlin(tsType: string, optional: boolean = false): string {
12
+ if (tsType.startsWith('Promise<')) {
13
+ const inner = tsType.slice(8, -1);
14
+ return mapTsTypeToKotlin(inner, optional);
15
+ }
16
+
17
+ const mapped = TS_TO_KOTLIN_TYPE[tsType];
18
+ if (mapped) {
19
+ return optional ? `${mapped}?` : mapped;
20
+ }
21
+
22
+ return optional ? 'Any?' : 'Any';
23
+ }
24
+
25
+ export function generateKotlinDelegate(
26
+ methods: MethodSignature[],
27
+ kotlinPackageName: string
28
+ ): string {
29
+ const methodSignatures = methods
30
+ .map((method) => {
31
+ const params = method.params
32
+ .map(
33
+ (param) =>
34
+ `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`
35
+ )
36
+ .join(', ');
37
+ const returnType =
38
+ method.returnType === 'void'
39
+ ? ''
40
+ : `: ${mapTsTypeToKotlin(method.returnType, false)}`;
41
+ return ` fun ${method.name}(${params})${returnType}`;
42
+ })
43
+ .join('\n');
44
+
45
+ return `package ${kotlinPackageName}
46
+
47
+ interface BrownfieldNavigationDelegate {
48
+ ${methodSignatures}
49
+ }
50
+ `;
51
+ }
52
+
53
+ export function generateKotlinModule(
54
+ methods: MethodSignature[],
55
+ kotlinPackageName: string
56
+ ): string {
57
+ const hasAsyncMethod = methods.some((method) => method.isAsync);
58
+ const hasObjectType = methods.some(
59
+ (method) =>
60
+ method.returnType.includes('Object') ||
61
+ method.params.some((param) => param.type === 'Object')
62
+ );
63
+
64
+ const methodImplementations = methods
65
+ .map((method) =>
66
+ method.isAsync
67
+ ? generateAsyncKotlinMethod(method)
68
+ : generateSyncKotlinMethod(method)
69
+ )
70
+ .join('\n\n');
71
+
72
+ return `package ${kotlinPackageName}
73
+
74
+ import com.facebook.react.bridge.ReactApplicationContext
75
+ import com.facebook.react.bridge.ReactMethod${
76
+ hasAsyncMethod ? '\nimport com.facebook.react.bridge.Promise' : ''
77
+ }${hasObjectType ? '\nimport com.facebook.react.bridge.ReadableMap' : ''}
78
+
79
+ class NativeBrownfieldNavigationModule(
80
+ reactContext: ReactApplicationContext
81
+ ) : NativeBrownfieldNavigationSpec(reactContext) {
82
+ ${methodImplementations}
83
+
84
+ companion object {
85
+ const val NAME = "NativeBrownfieldNavigation"
86
+ }
87
+ }
88
+ `;
89
+ }
90
+
91
+ function generateSyncKotlinMethod(method: MethodSignature): string {
92
+ const params = method.params
93
+ .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
94
+ .join(', ');
95
+ const args = method.params.map((param) => param.name).join(', ');
96
+
97
+ const signature = ` @ReactMethod\n override fun ${method.name}(${params})${
98
+ method.returnType === 'void'
99
+ ? ''
100
+ : `: ${mapTsTypeToKotlin(method.returnType, false)}`
101
+ }`;
102
+
103
+ if (method.returnType === 'void') {
104
+ return `${signature} {
105
+ BrownfieldNavigationManager.getDelegate().${method.name}(${args})
106
+ }`;
107
+ }
108
+
109
+ return `${signature} {
110
+ return BrownfieldNavigationManager.getDelegate().${method.name}(${args})
111
+ }`;
112
+ }
113
+
114
+ function generateAsyncKotlinMethod(method: MethodSignature): string {
115
+ const paramsWithTypes = method.params
116
+ .map((param) => `${param.name}: ${mapTsTypeToKotlin(param.type, param.optional)}`)
117
+ .join(', ');
118
+ const params =
119
+ paramsWithTypes.length > 0
120
+ ? `${paramsWithTypes}, promise: Promise`
121
+ : 'promise: Promise';
122
+
123
+ return ` @ReactMethod
124
+ override fun ${method.name}(${params}) {
125
+ promise.reject("not_implemented", "${method.name} is not implemented")
126
+ }`;
127
+ }
@@ -0,0 +1,169 @@
1
+ import type { MethodSignature } from '../types.js';
2
+
3
+ const TS_TO_OBJC_TYPE: Record<string, string> = {
4
+ string: 'NSString *',
5
+ number: 'double',
6
+ boolean: 'BOOL',
7
+ void: 'void',
8
+ Object: 'NSDictionary *',
9
+ };
10
+
11
+ const TS_TO_SWIFT_TYPE: Record<string, string> = {
12
+ string: 'String',
13
+ number: 'Double',
14
+ boolean: 'Bool',
15
+ void: 'Void',
16
+ Object: '[String: Any]',
17
+ };
18
+
19
+ function mapTsTypeToObjC(tsType: string, nullable: boolean = false): string {
20
+ if (tsType.startsWith('Promise<')) {
21
+ return 'void';
22
+ }
23
+
24
+ const mapped = TS_TO_OBJC_TYPE[tsType];
25
+ if (mapped) {
26
+ if (nullable && mapped.includes('*')) {
27
+ return mapped.replace(' *', ' * _Nullable');
28
+ }
29
+ return mapped;
30
+ }
31
+
32
+ return nullable ? 'id _Nullable' : 'id';
33
+ }
34
+
35
+ function mapTsTypeToSwift(tsType: string, optional: boolean = false): string {
36
+ if (tsType.startsWith('Promise<')) {
37
+ const inner = tsType.slice(8, -1);
38
+ return mapTsTypeToSwift(inner, optional);
39
+ }
40
+
41
+ const mapped = TS_TO_SWIFT_TYPE[tsType];
42
+ if (mapped) {
43
+ return optional ? `${mapped}?` : mapped;
44
+ }
45
+
46
+ return optional ? 'Any?' : 'Any';
47
+ }
48
+
49
+ export function generateSwiftDelegate(methods: MethodSignature[]): string {
50
+ const protocolMethods = methods
51
+ .map((method) => {
52
+ const params = method.params
53
+ .map((param, index) => {
54
+ const swiftType = mapTsTypeToSwift(param.type, param.optional);
55
+ const label = index === 0 ? '_' : param.name;
56
+ return `${label} ${param.name}: ${swiftType}`;
57
+ })
58
+ .join(', ');
59
+
60
+ const returnType =
61
+ method.returnType === 'void'
62
+ ? ''
63
+ : ` -> ${mapTsTypeToSwift(method.returnType, false)}`;
64
+
65
+ return ` @objc func ${method.name}(${params})${returnType}`;
66
+ })
67
+ .join('\n');
68
+
69
+ return `import Foundation
70
+
71
+ @objc public protocol BrownfieldNavigationDelegate: AnyObject {
72
+ ${protocolMethods}
73
+ }
74
+ `;
75
+ }
76
+
77
+ export function generateObjCImplementation(methods: MethodSignature[]): string {
78
+ const methodImplementations = methods
79
+ .map((method) =>
80
+ method.isAsync ? generateAsyncObjCMethod(method) : generateSyncObjCMethod(method)
81
+ )
82
+ .join('\n\n');
83
+
84
+ return `#import "NativeBrownfieldNavigation.h"
85
+
86
+ #if __has_include("BrownfieldNavigation/BrownfieldNavigation-Swift.h")
87
+ #import "BrownfieldNavigation/BrownfieldNavigation-Swift.h"
88
+ #else
89
+ #import "BrownfieldNavigation-Swift.h"
90
+ #endif
91
+
92
+ @implementation NativeBrownfieldNavigation
93
+
94
+ ${methodImplementations}
95
+
96
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
97
+ (const facebook::react::ObjCTurboModule::InitParams &)params
98
+ {
99
+ return std::make_shared<facebook::react::NativeBrownfieldNavigationSpecJSI>(params);
100
+ }
101
+
102
+ + (NSString *)moduleName
103
+ {
104
+ return @"NativeBrownfieldNavigation";
105
+ }
106
+
107
+ @end
108
+ `;
109
+ }
110
+
111
+ function generateSyncObjCMethod(method: MethodSignature): string {
112
+ const { name, params, returnType } = method;
113
+
114
+ let signature = `- (${mapTsTypeToObjC(returnType)})${name}`;
115
+ if (params.length > 0) {
116
+ signature += params
117
+ .map((param, index) => {
118
+ const prefix = index === 0 ? ':' : ` ${param.name}:`;
119
+ return `${prefix}(${mapTsTypeToObjC(param.type, param.optional)})${param.name}`;
120
+ })
121
+ .join('');
122
+ }
123
+
124
+ let delegateCall = `[[[BrownfieldNavigationManager shared] getDelegate] ${name}`;
125
+ if (params.length > 0) {
126
+ delegateCall += params
127
+ .map((param, index) => {
128
+ const label = index === 0 ? '' : param.name;
129
+ return `${label}:${param.name}`;
130
+ })
131
+ .join(' ');
132
+ }
133
+ delegateCall += ']';
134
+
135
+ const returnPrefix = returnType === 'void' ? '' : 'return ';
136
+
137
+ return `${signature} {
138
+ ${returnPrefix}${delegateCall};
139
+ }`;
140
+ }
141
+
142
+ function generateAsyncObjCMethod(method: MethodSignature): string {
143
+ const { name, params } = method;
144
+
145
+ let signature = `- (void)${name}`;
146
+ const allParams: Array<{ name: string; type: string; optional: boolean }> = [
147
+ ...params,
148
+ { name: 'resolve', type: 'RCTPromiseResolveBlock', optional: false },
149
+ { name: 'reject', type: 'RCTPromiseRejectBlock', optional: false },
150
+ ];
151
+
152
+ signature += ':';
153
+ signature += allParams
154
+ .map((param, index) => {
155
+ const prefix = index === 0 ? '' : param.name;
156
+ const type =
157
+ param.type === 'RCTPromiseResolveBlock'
158
+ ? 'RCTPromiseResolveBlock'
159
+ : param.type === 'RCTPromiseRejectBlock'
160
+ ? 'RCTPromiseRejectBlock'
161
+ : mapTsTypeToObjC(param.type, param.optional);
162
+ return `${prefix}(${type})${param.name}`;
163
+ })
164
+ .join(' ');
165
+
166
+ return `${signature} {
167
+ reject(@"not_implemented", @"${name} is not implemented", nil);
168
+ }`;
169
+ }
@@ -0,0 +1,170 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ FetchingJSONSchemaStore,
5
+ InputData,
6
+ JSONSchemaInput,
7
+ quicktype,
8
+ } from 'quicktype-core';
9
+ import { schemaForTypeScriptSources } from 'quicktype-typescript-input';
10
+
11
+ import type { MethodSignature } from '../types.js';
12
+
13
+ export interface NavigationModelsOptions {
14
+ specPath: string;
15
+ methods: MethodSignature[];
16
+ kotlinPackageName: string;
17
+ }
18
+
19
+ export interface GeneratedNavigationModels {
20
+ swiftModels?: string;
21
+ kotlinModels?: string;
22
+ modelTypeNames: string[];
23
+ }
24
+
25
+ const SKIP_TYPE_TOKENS = new Set([
26
+ 'Array',
27
+ 'Date',
28
+ 'Map',
29
+ 'Object',
30
+ 'Promise',
31
+ 'ReadonlyArray',
32
+ 'Record',
33
+ 'Set',
34
+ 'any',
35
+ 'boolean',
36
+ 'false',
37
+ 'null',
38
+ 'number',
39
+ 'object',
40
+ 'string',
41
+ 'true',
42
+ 'undefined',
43
+ 'unknown',
44
+ 'void',
45
+ ]);
46
+
47
+ function collectReferencedTypes(methods: MethodSignature[]): Set<string> {
48
+ const referenced = new Set<string>();
49
+
50
+ for (const method of methods) {
51
+ const typeTexts = [
52
+ method.returnType,
53
+ ...method.params.map((param) => param.type),
54
+ ];
55
+ for (const typeText of typeTexts) {
56
+ const matches = typeText.match(/\b[A-Za-z_]\w*\b/g);
57
+ if (!matches) {
58
+ continue;
59
+ }
60
+ for (const match of matches) {
61
+ if (!SKIP_TYPE_TOKENS.has(match)) {
62
+ referenced.add(match);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ return referenced;
69
+ }
70
+
71
+ async function generateModelsForLanguage({
72
+ typeNames,
73
+ schema,
74
+ lang,
75
+ kotlinPackageName,
76
+ }: {
77
+ typeNames: string[];
78
+ schema: object;
79
+ lang: 'swift' | 'kotlin';
80
+ kotlinPackageName: string;
81
+ }): Promise<string> {
82
+ const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
83
+
84
+ for (const typeName of typeNames) {
85
+ const rootSchema = JSON.parse(JSON.stringify(schema)) as {
86
+ $ref?: string;
87
+ };
88
+ rootSchema.$ref = `#/definitions/${typeName}`;
89
+
90
+ await schemaInput.addSource({
91
+ name: typeName,
92
+ schema: JSON.stringify(rootSchema),
93
+ });
94
+ }
95
+
96
+ const inputData = new InputData();
97
+ inputData.addInput(schemaInput);
98
+
99
+ const rendererOptions =
100
+ lang === 'swift'
101
+ ? {
102
+ 'access-level': 'public',
103
+ 'mutable-properties': 'true',
104
+ initializers: 'false',
105
+ 'swift-5-support': 'true',
106
+ }
107
+ : {
108
+ framework: 'just-types',
109
+ package: kotlinPackageName,
110
+ };
111
+
112
+ const { lines } = await quicktype({
113
+ inputData,
114
+ lang,
115
+ rendererOptions,
116
+ });
117
+
118
+ return lines.join('\n');
119
+ }
120
+
121
+ export async function generateNavigationModels({
122
+ specPath,
123
+ methods,
124
+ kotlinPackageName,
125
+ }: NavigationModelsOptions): Promise<GeneratedNavigationModels> {
126
+ const absoluteSpecPath = path.resolve(process.cwd(), specPath);
127
+
128
+ if (!fs.existsSync(absoluteSpecPath)) {
129
+ throw new Error(`Spec file not found: ${absoluteSpecPath}`);
130
+ }
131
+
132
+ const schemaData = schemaForTypeScriptSources([absoluteSpecPath]);
133
+ if (!schemaData.schema) {
134
+ throw new Error('Failed to generate schema from TypeScript spec');
135
+ }
136
+
137
+ const parsedSchema = JSON.parse(schemaData.schema) as {
138
+ definitions?: Record<string, unknown>;
139
+ };
140
+ const referencedTypes = collectReferencedTypes(methods);
141
+ const definitions = parsedSchema.definitions ?? {};
142
+ const modelTypeNames = [...referencedTypes].filter((typeName) =>
143
+ Object.hasOwn(definitions, typeName)
144
+ );
145
+
146
+ if (modelTypeNames.length === 0) {
147
+ return { modelTypeNames: [] };
148
+ }
149
+
150
+ const [swiftModels, kotlinModels] = await Promise.all([
151
+ generateModelsForLanguage({
152
+ typeNames: modelTypeNames,
153
+ schema: parsedSchema,
154
+ lang: 'swift',
155
+ kotlinPackageName,
156
+ }),
157
+ generateModelsForLanguage({
158
+ typeNames: modelTypeNames,
159
+ schema: parsedSchema,
160
+ lang: 'kotlin',
161
+ kotlinPackageName,
162
+ }),
163
+ ]);
164
+
165
+ return {
166
+ swiftModels,
167
+ kotlinModels,
168
+ modelTypeNames,
169
+ };
170
+ }
@@ -0,0 +1,123 @@
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+
4
+ import type { MethodSignature } from '../types.js';
5
+
6
+ export function generateTurboModuleSpec(methods: MethodSignature[]): string {
7
+ const methodSignatures = methods
8
+ .map((method) => {
9
+ const params = method.params
10
+ .map((param) => `${param.name}${param.optional ? '?' : ''}: ${param.type}`)
11
+ .join(', ');
12
+ return ` ${method.name}(${params}): ${method.returnType};`;
13
+ })
14
+ .join('\n');
15
+
16
+ return `import { TurboModuleRegistry, type TurboModule } from 'react-native';
17
+
18
+ export interface Spec extends TurboModule {
19
+ ${methodSignatures}
20
+ }
21
+
22
+ export default TurboModuleRegistry.getEnforcing<Spec>(
23
+ 'NativeBrownfieldNavigation'
24
+ );
25
+ `;
26
+ }
27
+
28
+ export function generateIndexTs(methods: MethodSignature[]): string {
29
+ const functionImplementations = methods
30
+ .map((method) => {
31
+ const params = method.params
32
+ .map((param) => `${param.name}${param.optional ? '?' : ''}: ${param.type}`)
33
+ .join(', ');
34
+ const args = method.params.map((param) => param.name).join(', ');
35
+ const returnType = method.returnType === 'void' ? '' : `: ${method.returnType}`;
36
+
37
+ if (method.isAsync) {
38
+ return ` ${method.name}: async (${params})${returnType} => {
39
+ return NativeBrownfieldNavigation.${method.name}(${args});
40
+ }`;
41
+ }
42
+
43
+ return ` ${method.name}: (${params})${returnType} => {
44
+ NativeBrownfieldNavigation.${method.name}(${args});
45
+ }`;
46
+ })
47
+ .join(',\n');
48
+
49
+ return `import NativeBrownfieldNavigation from './NativeBrownfieldNavigation';
50
+
51
+ const BrownfieldNavigation = {
52
+ ${functionImplementations},
53
+ };
54
+
55
+ export default BrownfieldNavigation;
56
+ `;
57
+ }
58
+
59
+ export function transpileWithConsumerBabel(
60
+ tsCode: string,
61
+ projectRoot: string,
62
+ packageRoot: string
63
+ ): string {
64
+ const nodeRequire = createRequire(path.join(projectRoot, 'package.json'));
65
+ const moduleCandidates = [projectRoot, packageRoot];
66
+
67
+ function resolveOrThrow(moduleName: string): string {
68
+ for (const modulePath of moduleCandidates) {
69
+ try {
70
+ return nodeRequire.resolve(moduleName, { paths: [modulePath] });
71
+ } catch {
72
+ // Continue with remaining candidates.
73
+ }
74
+ }
75
+
76
+ throw new Error(
77
+ `Could not resolve "${moduleName}". Install it in your app devDependencies.`
78
+ );
79
+ }
80
+
81
+ const babelCorePath = resolveOrThrow('@babel/core');
82
+ const rnPresetPath = resolveOrThrow('@react-native/babel-preset');
83
+ const babelCore = nodeRequire(babelCorePath) as {
84
+ transformSync: (
85
+ source: string,
86
+ options: Record<string, unknown>
87
+ ) => { code?: string | null } | null;
88
+ };
89
+
90
+ const transformed = babelCore.transformSync(tsCode, {
91
+ filename: 'index.ts',
92
+ babelrc: false,
93
+ configFile: false,
94
+ comments: false,
95
+ compact: true,
96
+ minified: true,
97
+ presets: [[rnPresetPath, {}]],
98
+ });
99
+
100
+ if (!transformed?.code) {
101
+ throw new Error('Babel transpilation failed for generated index.ts');
102
+ }
103
+
104
+ return transformed.code;
105
+ }
106
+
107
+ export function generateIndexDts(methods: MethodSignature[]): string {
108
+ const methodSignatures = methods
109
+ .map((method) => {
110
+ const params = method.params
111
+ .map((param) => `${param.name}${param.optional ? '?' : ''}: ${param.type}`)
112
+ .join(', ');
113
+ return ` ${method.name}: (${params}) => ${method.returnType};`;
114
+ })
115
+ .join('\n');
116
+
117
+ return `declare const BrownfieldNavigation: {
118
+ ${methodSignatures}
119
+ };
120
+
121
+ export default BrownfieldNavigation;
122
+ `;
123
+ }
@@ -0,0 +1,17 @@
1
+ import { isNavigationInstalled } from '../config.js';
2
+ import { isNavigationSpecPresent } from '../spec-discovery.js';
3
+ import { runNavigationCodegen } from '../runner.js';
4
+
5
+ export async function runNavigationCodegenIfApplicable(
6
+ projectRoot: string,
7
+ specPath?: string
8
+ ): Promise<{ hasNavigation: boolean; hasSpec: boolean }> {
9
+ const hasNavigation = isNavigationInstalled(projectRoot);
10
+ const hasSpec = hasNavigation && isNavigationSpecPresent(specPath, projectRoot);
11
+
12
+ if (hasSpec) {
13
+ await runNavigationCodegen({ specPath, projectRoot });
14
+ }
15
+
16
+ return { hasNavigation, hasSpec };
17
+ }
@@ -0,0 +1,10 @@
1
+ import { styleText } from 'node:util';
2
+
3
+ import * as Commands from './commands/index.js';
4
+
5
+ export const groupName = `${styleText(['bold', 'blueBright'], '@callstack/brownfield-navigation')}${styleText('whiteBright', ' - native codegen utilities for Brownfield Navigation')}`;
6
+
7
+ export { Commands };
8
+ export type * from './types.js';
9
+ export * from './runner.js';
10
+ export default Commands;
@@ -0,0 +1,43 @@
1
+ import fs from 'node:fs';
2
+ import { Project } from 'ts-morph';
3
+
4
+ import type { MethodParam, MethodSignature } from './types.js';
5
+
6
+ export function parseNavigationSpec(specPath: string): MethodSignature[] {
7
+ if (!fs.existsSync(specPath)) {
8
+ throw new Error(`Spec file not found: ${specPath}`);
9
+ }
10
+
11
+ const project = new Project({ skipAddingFilesFromTsConfig: true });
12
+ const sourceFile = project.addSourceFileAtPath(specPath);
13
+ const specInterface =
14
+ sourceFile.getInterface('BrownfieldNavigationSpec') ??
15
+ sourceFile.getInterface('Spec');
16
+
17
+ if (!specInterface) {
18
+ throw new Error(
19
+ 'Could not find BrownfieldNavigationSpec or Spec interface in spec file'
20
+ );
21
+ }
22
+
23
+ return specInterface.getMethods().map((method): MethodSignature => {
24
+ const name = method.getName();
25
+ const params: MethodParam[] = method.getParameters().map((param) => {
26
+ const typeNode = param.getTypeNode();
27
+ return {
28
+ name: param.getName(),
29
+ type: typeNode?.getText() ?? 'unknown',
30
+ optional: param.isOptional(),
31
+ };
32
+ });
33
+ const returnTypeNode = method.getReturnTypeNode();
34
+ const returnType = returnTypeNode?.getText() ?? 'void';
35
+
36
+ return {
37
+ name,
38
+ params,
39
+ returnType,
40
+ isAsync: returnType.startsWith('Promise<'),
41
+ };
42
+ });
43
+ }