Skip to content

Commit 1f0a531

Browse files
committed
feat: native code generation
1 parent c60339b commit 1f0a531

12 files changed

Lines changed: 112 additions & 192 deletions

File tree

apps/AppleApp/Brownfield Apple App/ContentView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import ReactBrownfield
22
import Brownie
33
import SwiftUI
44

5+
let initialState = BrownfieldStore(
6+
counter: 0,
7+
user: User(name: "Username")
8+
)
9+
10+
511
struct ContentView: View {
612
var body: some View {
713
NavigationView {

apps/RNApp/App.tsx

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { useEffect } from 'react';
2-
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
3-
import { useStore } from '@callstack/brownie';
1+
import React, { useEffect } from 'react';
2+
import { StyleSheet, Text, View, Button } from 'react-native';
43
import {
54
createNativeStackNavigator,
65
type NativeStackScreenProps,
76
} from '@react-navigation/native-stack';
87
import ReactNativeBrownfield from '@callstack/react-native-brownfield';
98
import { NavigationContainer } from '@react-navigation/native';
10-
import './BrownfieldStore.brownie';
119

1210
const getRandomValue = () => Math.round(Math.random() * 255);
1311
const getRandomTheme = () => {
@@ -28,8 +26,6 @@ type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
2826

2927
function HomeScreen({ navigation, route }: Props) {
3028
const colors = route.params?.theme || getRandomTheme();
31-
const [counter, setState] = useStore('BrownfieldStore', (s) => s.counter);
32-
const [user] = useStore('BrownfieldStore', (s) => s.user);
3329

3430
useEffect(() => {
3531
const unsubscribe = navigation.addListener('focus', () => {
@@ -41,29 +37,10 @@ function HomeScreen({ navigation, route }: Props) {
4137

4238
return (
4339
<View style={[styles.container, { backgroundColor: colors.primary }]}>
44-
<Text style={[styles.title, { color: colors.secondary }]}>
45-
React Native Screen
46-
</Text>
47-
4840
<Text style={[styles.text, { color: colors.secondary }]}>
49-
Count: {counter}
41+
React Native Screen
5042
</Text>
5143

52-
<TextInput
53-
style={styles.input}
54-
value={user.name}
55-
onChangeText={(text) =>
56-
setState((prev) => ({ user: { ...prev.user, name: text } }))
57-
}
58-
placeholder="User name"
59-
/>
60-
61-
<Button
62-
onPress={() => setState((prev) => ({ counter: prev.counter + 1 }))}
63-
color={colors.secondary}
64-
title="Increment"
65-
/>
66-
6744
<Button
6845
onPress={() => {
6946
navigation.push('Home', {
@@ -110,22 +87,9 @@ const styles = StyleSheet.create({
11087
justifyContent: 'center',
11188
alignItems: 'center',
11289
},
113-
title: {
90+
text: {
11491
fontSize: 30,
11592
fontWeight: 'bold',
11693
margin: 10,
11794
},
118-
text: {
119-
fontSize: 16,
120-
margin: 5,
121-
},
122-
input: {
123-
borderWidth: 1,
124-
borderColor: '#ccc',
125-
borderRadius: 8,
126-
padding: 10,
127-
width: 200,
128-
marginVertical: 10,
129-
backgroundColor: '#fff',
130-
},
13195
});

apps/RNApp/ios/BrownfieldLib/Generated/BrownfieldStore.swift

Lines changed: 0 additions & 35 deletions
This file was deleted.

apps/RNApp/ios/BrownfieldLib/Generated/SettingsStore.swift

Lines changed: 0 additions & 29 deletions
This file was deleted.

apps/RNApp/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"lint": "eslint .",
1414
"start": "react-native start",
1515
"test": "jest",
16-
"codegen": "brownie codegen"
16+
"codegen": "brownfield codegen"
1717
},
1818
"dependencies": {
1919
"@callstack/brownie": "*",
@@ -56,7 +56,6 @@
5656
"node": ">=20"
5757
},
5858
"brownie": {
59-
"swift": "./ios/BrownfieldLib/Generated",
6059
"kotlin": "./android/BrownfieldLib/src/main/java/com/rnapp/brownfieldlib/Generated",
6160
"kotlinPackageName": "com.rnapp.brownfieldlib"
6261
}

packages/brownie/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated store types (created by brownie codegen)
2+
ios/Generated/

packages/cli/src/brownfield/commands/packageIos.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { Command } from 'commander';
1717

1818
import { hasBrownieConfig } from '../../brownie/config.js';
19+
import { runCodegen } from '../../brownie/commands/codegen.js';
1920
import { getProjectInfo } from '../utils/index.js';
2021
import {
2122
actionRunner,
@@ -73,7 +74,7 @@ export const packageIosCommand = curryOptions(
7374
);
7475

7576
if (hasBrownieConfig(projectRoot)) {
76-
logger.log('Brownie config detected, building Brownie.xcframework...');
77+
await runCodegen({ platform: 'swift' });
7778

7879
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
7980
const brownieOutputPath = path.join(packageDir, 'Brownie.xcframework');
@@ -97,8 +98,8 @@ export const packageIosCommand = curryOptions(
9798
outputPath: brownieOutputPath,
9899
});
99100

100-
logger.log(
101-
`Brownie.xcframework available at: ${colorLink(relativeToCwd(brownieOutputPath))}`
101+
logger.success(
102+
`Brownie.xcframework created at ${colorLink(relativeToCwd(brownieOutputPath))}`
102103
);
103104
}
104105
})

packages/cli/src/brownie/__tests__/commands/codegen.test.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@ import { runCodegen } from '../../commands/codegen.js';
77
import * as swiftGenerator from '../../generators/swift.js';
88
import * as kotlinGenerator from '../../generators/kotlin.js';
99
import * as storeDiscovery from '../../store-discovery.js';
10+
import * as config from '../../config.js';
1011

1112
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1213
const FIXTURES_DIR = path.join(__dirname, '../../__fixtures__');
1314

1415
vi.mock('../../generators/swift');
1516
vi.mock('../../generators/kotlin');
1617
vi.mock('../../store-discovery');
18+
vi.mock('../../config', async (importOriginal) => {
19+
const actual = await importOriginal<typeof config>();
20+
return {
21+
...actual,
22+
getSwiftOutputPath: vi.fn(() => '/mock/brownie/ios/Generated'),
23+
};
24+
});
1725
vi.mock('@rock-js/tools', async (importOriginal) => {
1826
const actual = await importOriginal<typeof rockTools>();
1927
return {
@@ -69,9 +77,7 @@ describe('runCodegen', () => {
6977

7078
it('generates swift files for discovered store', async () => {
7179
tempDir = createTempPackageJson({
72-
brownie: {
73-
swift: './Generated',
74-
},
80+
brownie: {},
7581
});
7682
mockCwd.mockReturnValue(tempDir);
7783

@@ -81,7 +87,7 @@ describe('runCodegen', () => {
8187
name: 'TestStore',
8288
schemaPath: '/path/to/TestStore.brownie.ts',
8389
typeName: 'TestStore',
84-
outputPath: 'Generated/TestStore.swift',
90+
outputPath: '/mock/brownie/ios/Generated/TestStore.swift',
8591
});
8692
expect(mockGenerateKotlin).not.toHaveBeenCalled();
8793
});
@@ -104,13 +110,12 @@ describe('runCodegen', () => {
104110
outputPath: 'Generated/TestStore.kt',
105111
packageName: 'com.test',
106112
});
107-
expect(mockGenerateSwift).not.toHaveBeenCalled();
113+
expect(mockGenerateSwift).toHaveBeenCalled();
108114
});
109115

110116
it('generates both swift and kotlin when configured', async () => {
111117
tempDir = createTempPackageJson({
112118
brownie: {
113-
swift: './Generated',
114119
kotlin: './Generated',
115120
},
116121
});
@@ -125,7 +130,6 @@ describe('runCodegen', () => {
125130
it('generates only specified platform', async () => {
126131
tempDir = createTempPackageJson({
127132
brownie: {
128-
swift: './Generated',
129133
kotlin: './Generated',
130134
},
131135
});
@@ -139,9 +143,7 @@ describe('runCodegen', () => {
139143

140144
it('generates for multiple discovered stores', async () => {
141145
tempDir = createTempPackageJson({
142-
brownie: {
143-
swift: './Generated',
144-
},
146+
brownie: {},
145147
});
146148
mockCwd.mockReturnValue(tempDir);
147149

@@ -160,21 +162,19 @@ describe('runCodegen', () => {
160162
name: 'UserStore',
161163
schemaPath: '/path/to/UserStore.brownie.ts',
162164
typeName: 'UserStore',
163-
outputPath: 'Generated/UserStore.swift',
165+
outputPath: '/mock/brownie/ios/Generated/UserStore.swift',
164166
});
165167
expect(mockGenerateSwift).toHaveBeenNthCalledWith(2, {
166168
name: 'SettingsStore',
167169
schemaPath: '/path/to/SettingsStore.brownie.ts',
168170
typeName: 'SettingsStore',
169-
outputPath: 'Generated/SettingsStore.swift',
171+
outputPath: '/mock/brownie/ios/Generated/SettingsStore.swift',
170172
});
171173
});
172174

173175
it('exits with error for invalid platform', async () => {
174176
tempDir = createTempPackageJson({
175-
brownie: {
176-
swift: './Generated',
177-
},
177+
brownie: {},
178178
});
179179
mockCwd.mockReturnValue(tempDir);
180180

@@ -187,9 +187,7 @@ describe('runCodegen', () => {
187187

188188
it('exits with error when generator fails', async () => {
189189
tempDir = createTempPackageJson({
190-
brownie: {
191-
swift: './Generated',
192-
},
190+
brownie: {},
193191
});
194192
mockCwd.mockReturnValue(tempDir);
195193
mockGenerateSwift.mockRejectedValue(new Error('Generation failed'));
@@ -198,11 +196,9 @@ describe('runCodegen', () => {
198196
expect(mockLoggerError).toHaveBeenCalled();
199197
});
200198

201-
it('warns when store has no output paths for selected platform', async () => {
199+
it('skips kotlin when not configured', async () => {
202200
tempDir = createTempPackageJson({
203-
brownie: {
204-
swift: './Generated',
205-
},
201+
brownie: {},
206202
});
207203
mockCwd.mockReturnValue(tempDir);
208204

0 commit comments

Comments
 (0)