Skip to content

Commit f6ef932

Browse files
committed
feat: improve cli
1 parent 8a60905 commit f6ef932

File tree

12 files changed

+431
-148183
lines changed

12 files changed

+431
-148183
lines changed

.yarn/releases/yarn-1.22.22.cjs

Lines changed: 0 additions & 148049 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.callstack.kotlinexample
2+
3+
data class BrownfieldStore (
4+
val counter: Double,
5+
val hasError: Boolean,
6+
val isLoading: Boolean,
7+
val user: String
8+
)

apps/example/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"version": "0.0.1",
44
"private": true,
55
"scripts": {
6-
"start": "react-native start"
6+
"start": "react-native start",
7+
"codegen": "brownie codegen"
78
},
89
"dependencies": {
910
"@callstack/brownie": "*",
@@ -36,7 +37,8 @@
3637
"stores": {
3738
"schema": "./brownfield-store.schema.ts",
3839
"typeName": "BrownfieldStore",
39-
"swift": "./swift/Generated/BrownfieldStore.swift"
40+
"swift": "./swift/Generated/BrownfieldStore.swift",
41+
"kotlin": "./kotlin/app/src/main/java/com/callstack/kotlinexample/BrownfieldStore.kt"
4042
}
4143
}
4244
}

packages/brownie/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
"typecheck": "tsc --noEmit",
3232
"build": "bob build && tsc -p scripts/tsconfig.json"
3333
},
34-
"bin": {
35-
"brownfield-generate-store": "./lib/scripts/generate-store.js"
36-
},
34+
"bin": "./lib/scripts/cli.js",
3735
"keywords": [
3836
"brownie",
3937
"react-native-brownfield",

packages/brownie/scripts/cli.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env node
2+
3+
import { parseArgs, styleText } from 'node:util';
4+
import fs from 'fs';
5+
import path from 'path';
6+
7+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
8+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
9+
10+
const HELP_TEXT = `
11+
${styleText('bold', 'brownie')} - Shared state management CLI for React Native Brownfield
12+
13+
${styleText('yellow', 'Usage:')}
14+
brownie <command> [options]
15+
16+
${styleText('yellow', 'Startup:')}
17+
${styleText('cyan', '-h, --help')} Show help
18+
${styleText('cyan', '-v, --version')} Show version
19+
20+
${styleText('yellow', 'Commands:')}
21+
${styleText('cyan', 'codegen')} Generate native store types from TypeScript schema
22+
23+
${styleText('yellow', 'Examples:')}
24+
${styleText('dim', 'brownie codegen')} Generate for all platforms
25+
${styleText('dim', 'brownie codegen -p swift')} Generate Swift only
26+
${styleText('dim', 'brownie codegen --platform kotlin')} Generate Kotlin only
27+
`;
28+
29+
const { values, positionals } = parseArgs({
30+
allowPositionals: true,
31+
strict: false,
32+
options: {
33+
help: { type: 'boolean', short: 'h' },
34+
version: { type: 'boolean', short: 'v' },
35+
},
36+
});
37+
38+
if (values.version) {
39+
console.log(packageJson.version);
40+
process.exit(0);
41+
}
42+
43+
if (values.help && positionals.length === 0) {
44+
console.log(HELP_TEXT);
45+
process.exit(0);
46+
}
47+
48+
if (positionals.length === 0) {
49+
console.log(HELP_TEXT);
50+
process.exit(0);
51+
}
52+
53+
const [command] = positionals;
54+
55+
async function main() {
56+
switch (command) {
57+
case 'codegen': {
58+
const codegen = await import('./commands/codegen');
59+
await codegen.runCodegen(process.argv.slice(3), packageJson.version);
60+
break;
61+
}
62+
default:
63+
console.error(styleText('red', `Unknown command: ${command}`));
64+
console.log(HELP_TEXT);
65+
process.exit(1);
66+
}
67+
}
68+
69+
main().catch((error) => {
70+
console.error(
71+
styleText('red', error instanceof Error ? error.message : String(error))
72+
);
73+
process.exit(1);
74+
});
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { parseArgs, styleText } from 'node:util';
2+
import { loadConfig } from '../config';
3+
import { generateSwift } from '../generators/swift';
4+
import { generateKotlin } from '../generators/kotlin';
5+
6+
type Platform = 'swift' | 'kotlin';
7+
8+
const HELP_TEXT = `
9+
${styleText('bold', 'brownie codegen')} - Generate native store types from TypeScript schema
10+
11+
${styleText('yellow', 'Usage:')}
12+
brownie codegen [options]
13+
14+
${styleText('yellow', 'Startup:')}
15+
${styleText('cyan', '-h, --help')} Show help
16+
${styleText('cyan', '-v, --version')} Show version
17+
18+
${styleText('yellow', 'Options:')}
19+
${styleText('cyan', '-p, --platform <platform>')} Generate for specific platform (swift, kotlin)
20+
21+
${styleText('yellow', 'Examples:')}
22+
${styleText('dim', 'brownie codegen')} Generate for all configured platforms
23+
${styleText('dim', 'brownie codegen -p swift')} Generate Swift only
24+
${styleText('dim', 'brownie codegen --platform kotlin')} Generate Kotlin only
25+
`;
26+
27+
/**
28+
* Runs the codegen command with the given arguments.
29+
*/
30+
export async function runCodegen(
31+
args: string[],
32+
version: string
33+
): Promise<void> {
34+
const { values } = parseArgs({
35+
args,
36+
options: {
37+
platform: { type: 'string', short: 'p' },
38+
help: { type: 'boolean', short: 'h' },
39+
version: { type: 'boolean', short: 'v' },
40+
},
41+
});
42+
43+
if (values.version) {
44+
console.log(version);
45+
return;
46+
}
47+
48+
if (values.help) {
49+
console.log(HELP_TEXT);
50+
return;
51+
}
52+
53+
const config = loadConfig();
54+
const platform = values.platform as Platform | undefined;
55+
56+
if (platform && !['swift', 'kotlin'].includes(platform)) {
57+
console.error(
58+
styleText(
59+
'red',
60+
`Invalid platform: ${platform}. Must be 'swift' or 'kotlin'`
61+
)
62+
);
63+
process.exit(1);
64+
}
65+
66+
const platforms: Platform[] = platform
67+
? [platform]
68+
: (['swift', 'kotlin'] as Platform[]).filter((p) => config[p]);
69+
70+
if (platforms.length === 0) {
71+
console.error(
72+
styleText('red', 'No output paths configured in brownie.stores')
73+
);
74+
process.exit(1);
75+
}
76+
77+
console.log(
78+
styleText('cyan', `Generating store types from ${config.schema}...`)
79+
);
80+
81+
for (const p of platforms) {
82+
const outputPath = config[p];
83+
if (!outputPath) {
84+
console.warn(
85+
styleText('yellow', `Skipping ${p}: no output path configured`)
86+
);
87+
continue;
88+
}
89+
90+
try {
91+
if (p === 'swift') {
92+
await generateSwift({
93+
schemaPath: config.schema,
94+
typeName: config.typeName,
95+
outputPath,
96+
});
97+
} else {
98+
await generateKotlin({
99+
schemaPath: config.schema,
100+
typeName: config.typeName,
101+
outputPath,
102+
});
103+
}
104+
console.log(styleText('green', ` ✓ ${outputPath}`));
105+
} catch (error) {
106+
console.error(
107+
styleText(
108+
'red',
109+
`Error generating ${p}: ${error instanceof Error ? error.message : error}`
110+
)
111+
);
112+
process.exit(1);
113+
}
114+
}
115+
116+
console.log(styleText('green', '\nDone!'));
117+
}

packages/brownie/scripts/config.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
export interface StoresConfig {
5+
schema: string;
6+
typeName: string;
7+
swift?: string;
8+
kotlin?: string;
9+
}
10+
11+
export interface BrownieConfig {
12+
stores?: StoresConfig;
13+
}
14+
15+
interface PackageJson {
16+
brownie?: BrownieConfig;
17+
}
18+
19+
/**
20+
* Loads brownie config from package.json in the current working directory.
21+
*/
22+
export function loadConfig(): StoresConfig {
23+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
24+
25+
if (!fs.existsSync(packageJsonPath)) {
26+
throw new Error('package.json not found');
27+
}
28+
29+
const packageJson: PackageJson = JSON.parse(
30+
fs.readFileSync(packageJsonPath, 'utf-8')
31+
);
32+
const config = packageJson.brownie?.stores;
33+
34+
if (!config) {
35+
throw new Error('brownie.stores config not found in package.json');
36+
}
37+
38+
if (!config.schema) {
39+
throw new Error('brownie.stores.schema is required');
40+
}
41+
42+
if (!config.typeName) {
43+
throw new Error('brownie.stores.typeName is required');
44+
}
45+
46+
if (!config.swift && !config.kotlin) {
47+
throw new Error(
48+
'At least one output path is required: brownie.stores.swift or brownie.stores.kotlin'
49+
);
50+
}
51+
52+
return config;
53+
}

0 commit comments

Comments
 (0)