@callstack/brownfield-cli 3.11.0 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/brownfield/commands/packageAndroid.d.ts.map +1 -1
- package/dist/brownfield/commands/packageAndroid.js +3 -3
- package/dist/brownfield/commands/packageIos.d.ts.map +1 -1
- package/dist/brownfield/commands/packageIos.js +4 -2
- package/dist/brownfield/commands/publishAndroid.d.ts.map +1 -1
- package/dist/brownfield/commands/publishAndroid.js +3 -1
- package/dist/brownie/commands/codegen.d.ts +4 -2
- package/dist/brownie/commands/codegen.d.ts.map +1 -1
- package/dist/brownie/commands/codegen.js +15 -7
- package/dist/brownie/config.d.ts +19 -5
- package/dist/brownie/config.d.ts.map +1 -1
- package/dist/brownie/config.js +45 -9
- package/dist/brownie/helpers/runBrownieCodegenIfApplicable.d.ts +1 -1
- package/dist/brownie/helpers/runBrownieCodegenIfApplicable.d.ts.map +1 -1
- package/dist/brownie/helpers/runBrownieCodegenIfApplicable.js +1 -1
- package/dist/brownie/index.d.ts +0 -1
- package/dist/brownie/index.d.ts.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +72 -0
- package/dist/navigation/commands/codegen.d.ts.map +1 -1
- package/dist/navigation/generators/ts.d.ts.map +1 -1
- package/dist/navigation/generators/ts.js +53 -4
- package/dist/navigation/runner.d.ts.map +1 -1
- package/dist/navigation/runner.js +16 -12
- package/dist/shared/utils/cli.d.ts +1 -1
- package/dist/shared/utils/cli.d.ts.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +9 -1
- package/src/brownfield/commands/packageAndroid.ts +5 -6
- package/src/brownfield/commands/packageIos.ts +18 -18
- package/src/brownfield/commands/publishAndroid.ts +4 -1
- package/src/brownie/commands/codegen.ts +35 -10
- package/src/brownie/config.ts +72 -15
- package/src/brownie/helpers/runBrownieCodegenIfApplicable.ts +2 -2
- package/src/brownie/index.ts +0 -1
- package/src/config.ts +119 -0
- package/src/navigation/commands/codegen.ts +1 -3
- package/src/navigation/generators/ts.ts +61 -3
- package/src/navigation/runner.ts +17 -12
- package/src/shared/utils/cli.ts +4 -2
- package/src/types.ts +51 -0
- package/dist/brownie/types.d.ts +0 -2
- package/dist/brownie/types.d.ts.map +0 -1
- package/src/brownie/types.ts +0 -1
- /package/dist/{brownie/types.js → types.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callstack/brownfield-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Artur Morys-Magiera <artus9033@gmail.com>",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"types": "./dist/navigation/index.d.ts",
|
|
38
38
|
"default": "./dist/navigation/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./types": {
|
|
41
|
+
"source": "./src/types.ts",
|
|
42
|
+
"types": "./dist/types.d.ts",
|
|
43
|
+
"default": "./dist/types.js"
|
|
44
|
+
},
|
|
40
45
|
"./package.json": "./package.json"
|
|
41
46
|
},
|
|
42
47
|
"scripts": {
|
|
@@ -45,6 +50,7 @@
|
|
|
45
50
|
"build:brownfield": "yarn run build",
|
|
46
51
|
"build": "node -e \"const fs=require('fs'),p=require('path');const d=p.join('dist','index.d.ts');if(!fs.existsSync(d)){try{fs.unlinkSync('tsconfig.tsbuildinfo')}catch{}}\" && tsc -p tsconfig.json",
|
|
47
52
|
"dev": "tsc -p tsconfig.json --watch",
|
|
53
|
+
"generate:schema": "ts-json-schema-generator --path src/types.ts --type BrownfieldConfig --out schema.json && prettier --write schema.json",
|
|
48
54
|
"test": "vitest run"
|
|
49
55
|
},
|
|
50
56
|
"keywords": [
|
|
@@ -81,6 +87,7 @@
|
|
|
81
87
|
"@rock-js/plugin-brownfield-android": "^0.13.3",
|
|
82
88
|
"@rock-js/plugin-brownfield-ios": "^0.13.3",
|
|
83
89
|
"@rock-js/tools": "^0.13.3",
|
|
90
|
+
"ajv": "^8.20.0",
|
|
84
91
|
"commander": "^14.0.3",
|
|
85
92
|
"quicktype-core": "^23.2.6",
|
|
86
93
|
"quicktype-typescript-input": "^23.2.6",
|
|
@@ -100,6 +107,7 @@
|
|
|
100
107
|
"eslint": "^9.39.3",
|
|
101
108
|
"globals": "^17.3.0",
|
|
102
109
|
"nodemon": "^3.1.14",
|
|
110
|
+
"ts-json-schema-generator": "^2.9.0",
|
|
103
111
|
"typescript": "5.9.3",
|
|
104
112
|
"vitest": "^4.1.4"
|
|
105
113
|
},
|
|
@@ -15,16 +15,15 @@ import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
|
|
|
15
15
|
import { getProjectInfo } from '../utils/project.js';
|
|
16
16
|
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
|
|
17
17
|
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
|
|
18
|
+
import { mergeBrownfieldConfigWithOptions } from '../../config.js';
|
|
18
19
|
|
|
19
20
|
export const packageAndroidCommand = curryOptions(
|
|
20
21
|
new Command('package:android').description('Build Android AAR'),
|
|
21
|
-
packageAarOptions
|
|
22
|
-
option.name.startsWith('--variant')
|
|
23
|
-
? { ...option, default: 'debug' }
|
|
24
|
-
: option
|
|
25
|
-
)
|
|
22
|
+
packageAarOptions
|
|
26
23
|
).action(
|
|
27
|
-
actionRunner(async (
|
|
24
|
+
actionRunner(async (cliOptions: PackageAarFlags) => {
|
|
25
|
+
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
|
|
26
|
+
|
|
28
27
|
const { projectRoot, platformConfig } = getProjectInfo('android');
|
|
29
28
|
await runExpoPrebuildIfNeeded({
|
|
30
29
|
projectRoot,
|
|
@@ -4,7 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import {
|
|
5
5
|
getBuildOptions,
|
|
6
6
|
mergeFrameworks,
|
|
7
|
-
type BuildFlags as AppleBuildFlags,
|
|
8
7
|
} from '@rock-js/platform-apple-helpers';
|
|
9
8
|
import { packageIosAction } from '@rock-js/plugin-brownfield-ios';
|
|
10
9
|
import {
|
|
@@ -30,7 +29,9 @@ import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNa
|
|
|
30
29
|
import { copyDebugBundleToSimulatorSlice } from '../utils/copyDebugBundleToSimulatorSlice.js';
|
|
31
30
|
import { resolvePackagedFrameworkName } from '../utils/resolvePackagedFrameworkName.js';
|
|
32
31
|
import { stripFrameworkBinary } from '../utils/stripFrameworkBinary.js';
|
|
32
|
+
import type { PackageIosOptions } from '../../types.js';
|
|
33
33
|
import { createLocalSpmPackage } from '../utils/createLocalSpmPackage.js';
|
|
34
|
+
import { mergeBrownfieldConfigWithOptions } from '../../config.js';
|
|
34
35
|
|
|
35
36
|
/** Help text for `--use-prebuilt-rn-core` (keep in sync with docs/docs/docs/getting-started/ios.mdx, "React Native Prebuilts" section). */
|
|
36
37
|
const USE_PREBUILT_RN_CORE_HELP =
|
|
@@ -69,13 +70,6 @@ function getPackagedFrameworkResolutionFailureMessage({
|
|
|
69
70
|
: 'could not resolve the packaged framework output automatically; pass --scheme explicitly';
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
type PackageIosCliFlags = AppleBuildFlags & {
|
|
73
|
-
/** Set when `--use-prebuilt-rn-core` is passed; omitted when the flag is absent (Rock applies RN version defaults). */
|
|
74
|
-
usePrebuiltRnCore?: boolean;
|
|
75
|
-
/** When set, generate a local Swift Package Manager manifest next to the packaged XCFramework outputs. */
|
|
76
|
-
addSpmPackage?: boolean;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
73
|
export const packageIosCommand = curryOptions(
|
|
80
74
|
new Command('package:ios').description('Build iOS XCFramework'),
|
|
81
75
|
getBuildOptions({ platformName: 'ios' }).map((option) =>
|
|
@@ -101,7 +95,9 @@ export const packageIosCommand = curryOptions(
|
|
|
101
95
|
)
|
|
102
96
|
)
|
|
103
97
|
.action(
|
|
104
|
-
actionRunner(async (
|
|
98
|
+
actionRunner(async (cliOptions: PackageIosOptions) => {
|
|
99
|
+
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'ios');
|
|
100
|
+
|
|
105
101
|
const { projectRoot, platformConfig, userConfig } = getProjectInfo('ios');
|
|
106
102
|
|
|
107
103
|
const prebuiltRNCoreSupport = supportsPrebuiltRNCore({ projectRoot });
|
|
@@ -196,10 +192,12 @@ export const packageIosCommand = curryOptions(
|
|
|
196
192
|
|
|
197
193
|
if (!frameworkName && options.addSpmPackage) {
|
|
198
194
|
throw new RockError(
|
|
199
|
-
`Cannot generate local SPM package: ${getPackagedFrameworkResolutionFailureMessage(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
195
|
+
`Cannot generate local SPM package: ${getPackagedFrameworkResolutionFailureMessage(
|
|
196
|
+
{
|
|
197
|
+
resolution,
|
|
198
|
+
candidates,
|
|
199
|
+
}
|
|
200
|
+
)}`
|
|
203
201
|
);
|
|
204
202
|
}
|
|
205
203
|
|
|
@@ -231,10 +229,12 @@ export const packageIosCommand = curryOptions(
|
|
|
231
229
|
}
|
|
232
230
|
} else if (configuration.includes('Debug')) {
|
|
233
231
|
logger.warn(
|
|
234
|
-
`Skipping Debug simulator JS bundle copy: ${getPackagedFrameworkResolutionFailureMessage(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
`Skipping Debug simulator JS bundle copy: ${getPackagedFrameworkResolutionFailureMessage(
|
|
233
|
+
{
|
|
234
|
+
resolution,
|
|
235
|
+
candidates,
|
|
236
|
+
}
|
|
237
|
+
)}`
|
|
238
238
|
);
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -326,7 +326,7 @@ export const packageIosCommand = curryOptions(
|
|
|
326
326
|
`Add the local package folder in Xcode: ${colorLink(relativeToCwd(packageDir))}`
|
|
327
327
|
);
|
|
328
328
|
logger.info(
|
|
329
|
-
|
|
329
|
+
'In Xcode, choose File > Add Package Dependencies..., click Add Local..., and select that folder.'
|
|
330
330
|
);
|
|
331
331
|
}
|
|
332
332
|
})
|
|
@@ -15,6 +15,7 @@ import { getProjectInfo } from '../utils/project.js';
|
|
|
15
15
|
import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
|
|
16
16
|
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
|
|
17
17
|
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
|
|
18
|
+
import { mergeBrownfieldConfigWithOptions } from '../../config.js';
|
|
18
19
|
|
|
19
20
|
export const publishAndroidCommand = curryOptions(
|
|
20
21
|
new Command('publish:android').description(
|
|
@@ -22,7 +23,9 @@ export const publishAndroidCommand = curryOptions(
|
|
|
22
23
|
),
|
|
23
24
|
publishLocalAarOptions
|
|
24
25
|
).action(
|
|
25
|
-
actionRunner(async (
|
|
26
|
+
actionRunner(async (cliOptions: PublishLocalAarFlags) => {
|
|
27
|
+
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
|
|
28
|
+
|
|
26
29
|
const { projectRoot, platformConfig } = getProjectInfo('android');
|
|
27
30
|
await runExpoPrebuildIfNeeded({
|
|
28
31
|
projectRoot,
|
|
@@ -7,15 +7,15 @@ import { intro, logger, outro } from '@rock-js/tools';
|
|
|
7
7
|
import { QuickTypeError } from 'quicktype-core';
|
|
8
8
|
import { actionRunner } from '../../shared/index.js';
|
|
9
9
|
import {
|
|
10
|
-
loadConfig,
|
|
11
10
|
getSwiftOutputPath,
|
|
12
|
-
|
|
11
|
+
resolveBrownieCodegenConfig,
|
|
13
12
|
} from '../config.js';
|
|
13
|
+
import { findProjectRoot } from '../../brownfield/utils/paths.js';
|
|
14
14
|
import { generateSwift } from '../generators/swift.js';
|
|
15
15
|
import { generateKotlin } from '../generators/kotlin.js';
|
|
16
16
|
import { discoverStores, type DiscoveredStore } from '../store-discovery.js';
|
|
17
|
-
import type { Platform } from '../types.js';
|
|
18
17
|
import { NoBrownieStoresError } from '../errors/NoBrownieStoresError.js';
|
|
18
|
+
import type { BrownieConfig, Platform } from '../../types.js';
|
|
19
19
|
|
|
20
20
|
function getOutputPath(dir: string, name: string, ext: string): string {
|
|
21
21
|
return path.join(dir, `${name}.${ext}`);
|
|
@@ -34,7 +34,8 @@ async function generateForStore(
|
|
|
34
34
|
store: DiscoveredStore,
|
|
35
35
|
config: BrownieConfig,
|
|
36
36
|
platforms: Platform[],
|
|
37
|
-
showLabel: boolean
|
|
37
|
+
showLabel: boolean,
|
|
38
|
+
projectRoot: string
|
|
38
39
|
): Promise<void> {
|
|
39
40
|
const { name, schemaPath } = store;
|
|
40
41
|
const storeLabel = showLabel ? ` [${name}]` : '';
|
|
@@ -45,7 +46,7 @@ async function generateForStore(
|
|
|
45
46
|
let outputPath: string;
|
|
46
47
|
|
|
47
48
|
if (p === 'swift') {
|
|
48
|
-
const swiftOutputDir = getSwiftOutputPath();
|
|
49
|
+
const swiftOutputDir = getSwiftOutputPath(projectRoot);
|
|
49
50
|
outputPath = getOutputPath(swiftOutputDir, name, 'swift');
|
|
50
51
|
} else {
|
|
51
52
|
const kotlinOutputDir = config.kotlin;
|
|
@@ -84,17 +85,35 @@ async function generateForStore(
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
export type RunCodegenOptions = {
|
|
88
|
+
export type RunCodegenOptions = {
|
|
89
|
+
platform?: Platform;
|
|
90
|
+
brownie?: BrownieConfig;
|
|
91
|
+
projectRoot?: string;
|
|
92
|
+
};
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
95
|
* Runs the codegen command with the given arguments.
|
|
91
96
|
*/
|
|
92
|
-
export async function runCodegen({
|
|
97
|
+
export async function runCodegen({
|
|
98
|
+
platform,
|
|
99
|
+
brownie,
|
|
100
|
+
projectRoot,
|
|
101
|
+
}: RunCodegenOptions) {
|
|
93
102
|
intro(
|
|
94
103
|
`Running Brownie codegen for ${platform ? `platform ${platform}` : 'all platforms'}`
|
|
95
104
|
);
|
|
96
105
|
|
|
97
|
-
const
|
|
106
|
+
const resolvedProjectRoot = projectRoot ?? findProjectRoot();
|
|
107
|
+
const { config, usedLegacyConfig } = resolveBrownieCodegenConfig({
|
|
108
|
+
brownie,
|
|
109
|
+
projectRoot: resolvedProjectRoot,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (usedLegacyConfig) {
|
|
113
|
+
logger.warn(
|
|
114
|
+
'You are using legacy Brownie configuration. Please migrate to the new configuration format. See the documentation for more details: https://oss.callstack.com/react-native-brownfield/docs/api-reference/configuration#migrating-from-legacy-brownie-configuration'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
98
117
|
|
|
99
118
|
if (platform && !['swift', 'kotlin'].includes(platform)) {
|
|
100
119
|
logger.error(`Invalid platform: ${platform}. Must be 'swift' or 'kotlin'`);
|
|
@@ -102,7 +121,7 @@ export async function runCodegen({ platform }: RunCodegenOptions) {
|
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
try {
|
|
105
|
-
const stores = discoverStores();
|
|
124
|
+
const stores = discoverStores(resolvedProjectRoot);
|
|
106
125
|
const isMultipleStores = stores.length > 1;
|
|
107
126
|
const schemaList = stores
|
|
108
127
|
.map((s) => path.basename(s.schemaPath))
|
|
@@ -122,7 +141,13 @@ export async function runCodegen({ platform }: RunCodegenOptions) {
|
|
|
122
141
|
platforms = ['swift', 'kotlin'];
|
|
123
142
|
}
|
|
124
143
|
|
|
125
|
-
await generateForStore(
|
|
144
|
+
await generateForStore(
|
|
145
|
+
store,
|
|
146
|
+
config,
|
|
147
|
+
platforms,
|
|
148
|
+
isMultipleStores,
|
|
149
|
+
resolvedProjectRoot
|
|
150
|
+
);
|
|
126
151
|
}
|
|
127
152
|
} catch (error) {
|
|
128
153
|
if (error instanceof NoBrownieStoresError) {
|
package/src/brownie/config.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
+
import { findProjectRoot } from '../brownfield/utils/paths.js';
|
|
5
|
+
import { loadBrownfieldConfig } from '../config.js';
|
|
6
|
+
import type { BrownieConfig } from '../types.js';
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
kotlinPackageName?: string;
|
|
8
|
-
}
|
|
8
|
+
const LEGACY_AND_NEW_BROWNIE_CONFIG_ERROR =
|
|
9
|
+
'Cannot use both legacy and new Brownie configuration formats simultaneously. Please migrate to the new configuration format and remove legacy configuration files: https://oss.callstack.com/react-native-brownfield/docs/api-reference/configuration#migrating-from-legacy-brownie-configuration';
|
|
9
10
|
|
|
10
11
|
interface PackageJson {
|
|
11
12
|
brownie?: BrownieConfig;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function loadPackageJson(projectRoot: string = findProjectRoot()): PackageJson {
|
|
16
|
+
const packageJsonPath = path.resolve(projectRoot, 'package.json');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
19
|
+
throw new Error('package.json not found');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageJson;
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
/**
|
|
15
26
|
* Checks if @callstack/brownie package is installed.
|
|
16
27
|
*/
|
|
17
28
|
export function isBrownieInstalled(
|
|
18
|
-
projectRoot: string =
|
|
29
|
+
projectRoot: string = findProjectRoot()
|
|
19
30
|
): boolean {
|
|
20
31
|
const require = createRequire(path.join(projectRoot, 'package.json'));
|
|
21
32
|
try {
|
|
@@ -30,7 +41,7 @@ export function isBrownieInstalled(
|
|
|
30
41
|
* Resolves the path to the @callstack/brownie package.
|
|
31
42
|
*/
|
|
32
43
|
export function getBrowniePackagePath(
|
|
33
|
-
projectRoot: string =
|
|
44
|
+
projectRoot: string = findProjectRoot()
|
|
34
45
|
): string {
|
|
35
46
|
const require = createRequire(path.join(projectRoot, 'package.json'));
|
|
36
47
|
try {
|
|
@@ -48,24 +59,70 @@ export function getBrowniePackagePath(
|
|
|
48
59
|
* Returns the output path for generated Swift files.
|
|
49
60
|
*/
|
|
50
61
|
export function getSwiftOutputPath(
|
|
51
|
-
projectRoot: string =
|
|
62
|
+
projectRoot: string = findProjectRoot()
|
|
52
63
|
): string {
|
|
53
64
|
const browniePath = getBrowniePackagePath(projectRoot);
|
|
54
65
|
return path.join(browniePath, 'ios', 'Generated');
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Returns whether package.json contains legacy brownie config.
|
|
70
|
+
*/
|
|
71
|
+
export function hasLegacyConfig(
|
|
72
|
+
projectRoot: string = findProjectRoot()
|
|
73
|
+
): boolean {
|
|
74
|
+
const packageJson = loadPackageJson(projectRoot);
|
|
75
|
+
|
|
76
|
+
return Object.prototype.hasOwnProperty.call(packageJson, 'brownie');
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
/**
|
|
58
80
|
* Loads brownie config from package.json in the current working directory.
|
|
59
81
|
*/
|
|
60
|
-
export function loadConfig(
|
|
61
|
-
|
|
82
|
+
export function loadConfig(
|
|
83
|
+
projectRoot: string = findProjectRoot()
|
|
84
|
+
): BrownieConfig {
|
|
85
|
+
const packageJson = loadPackageJson(projectRoot);
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
87
|
+
return packageJson.brownie ?? {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ResolveBrownieCodegenConfigOptions = {
|
|
91
|
+
brownie?: BrownieConfig;
|
|
92
|
+
projectRoot?: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type ResolvedBrownieCodegenConfig = {
|
|
96
|
+
config: BrownieConfig;
|
|
97
|
+
usedLegacyConfig: boolean;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolves Brownie codegen settings from explicit options, brownfield config,
|
|
102
|
+
* or legacy package.json `brownie` config.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveBrownieCodegenConfig({
|
|
105
|
+
brownie,
|
|
106
|
+
projectRoot = findProjectRoot(),
|
|
107
|
+
}: ResolveBrownieCodegenConfigOptions = {}): ResolvedBrownieCodegenConfig {
|
|
108
|
+
const legacyConfig = hasLegacyConfig(projectRoot)
|
|
109
|
+
? loadConfig(projectRoot)
|
|
110
|
+
: undefined;
|
|
111
|
+
const brownfieldBrownie = loadBrownfieldConfig(projectRoot).brownie;
|
|
112
|
+
|
|
113
|
+
if (legacyConfig !== undefined && (brownie ?? brownfieldBrownie)) {
|
|
114
|
+
throw new Error(LEGACY_AND_NEW_BROWNIE_CONFIG_ERROR);
|
|
65
115
|
}
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
if (legacyConfig !== undefined) {
|
|
118
|
+
return {
|
|
119
|
+
config: legacyConfig,
|
|
120
|
+
usedLegacyConfig: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
config: brownie ?? brownfieldBrownie ?? {},
|
|
126
|
+
usedLegacyConfig: false,
|
|
127
|
+
};
|
|
71
128
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runCodegen } from '../commands/codegen.js';
|
|
2
2
|
import { isBrownieInstalled } from '../config.js';
|
|
3
3
|
|
|
4
|
-
import type { Platform } from '
|
|
4
|
+
import type { Platform } from '../../types.js';
|
|
5
5
|
|
|
6
6
|
export async function runBrownieCodegenIfApplicable(
|
|
7
7
|
projectRoot: string,
|
|
@@ -9,7 +9,7 @@ export async function runBrownieCodegenIfApplicable(
|
|
|
9
9
|
) {
|
|
10
10
|
const hasBrownie = isBrownieInstalled(projectRoot);
|
|
11
11
|
if (hasBrownie) {
|
|
12
|
-
await runCodegen({ platform });
|
|
12
|
+
await runCodegen({ platform, projectRoot });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
return { hasBrownie };
|
package/src/brownie/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
|
|
3
3
|
import * as Commands from './commands/index.js';
|
|
4
4
|
|
|
5
|
-
export type * from './types.js';
|
|
6
5
|
export * from './store-discovery.js';
|
|
7
6
|
|
|
8
7
|
export const groupName = `${styleText(['bold', 'blueBright'], '@callstack/brownie')}${styleText('whiteBright', ' - Shared state management CLI for React Native Brownfield')}`;
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import Ajv from 'ajv';
|
|
6
|
+
|
|
7
|
+
import type { BrownfieldConfig } from './types.js';
|
|
8
|
+
import { findProjectRoot } from './brownfield/utils/paths.js';
|
|
9
|
+
|
|
10
|
+
import BrownfieldSchema from '../schema.json' with { type: 'json' };
|
|
11
|
+
import { logger } from '@rock-js/tools';
|
|
12
|
+
|
|
13
|
+
const CONFIG_BASE_NAME = 'brownfield';
|
|
14
|
+
const JS_CONFIG_FILE_NAME = `${CONFIG_BASE_NAME}.config.js`;
|
|
15
|
+
const JSON_CONFIG_FILE_NAME = `${CONFIG_BASE_NAME}.config.json`;
|
|
16
|
+
|
|
17
|
+
const SEPARATOR = '\n● ';
|
|
18
|
+
|
|
19
|
+
const ajv = new Ajv({ allErrors: true });
|
|
20
|
+
const validateBrownfieldConfig = ajv.compile(BrownfieldSchema);
|
|
21
|
+
|
|
22
|
+
export function validateBrownfieldCLIConfig(config: unknown): void {
|
|
23
|
+
if (!validateBrownfieldConfig(config)) {
|
|
24
|
+
logger.warn(
|
|
25
|
+
`Brownfield configuration has some issues: ${SEPARATOR}${ajv.errorsText(validateBrownfieldConfig.errors, { separator: SEPARATOR, dataVar: 'config' })}.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadBrownfieldConfig(
|
|
31
|
+
projectRoot: string = findProjectRoot()
|
|
32
|
+
): BrownfieldConfig {
|
|
33
|
+
const require = createRequire(path.join(projectRoot, 'package.json'));
|
|
34
|
+
|
|
35
|
+
const jsConfigFilePath = path.join(projectRoot, JS_CONFIG_FILE_NAME);
|
|
36
|
+
const jsonConfigFilePath = path.join(projectRoot, JSON_CONFIG_FILE_NAME);
|
|
37
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
38
|
+
const packageJson = require(packageJsonPath) as Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
[
|
|
42
|
+
fs.existsSync(jsConfigFilePath),
|
|
43
|
+
fs.existsSync(jsonConfigFilePath),
|
|
44
|
+
packageJson[CONFIG_BASE_NAME],
|
|
45
|
+
].filter(Boolean).length > 1
|
|
46
|
+
) {
|
|
47
|
+
throw new Error('Project has multiple Brownfield configuration files');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(jsConfigFilePath)) {
|
|
51
|
+
return require(jsConfigFilePath) as BrownfieldConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(jsonConfigFilePath)) {
|
|
55
|
+
return require(jsonConfigFilePath) as BrownfieldConfig;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return packageJson[CONFIG_BASE_NAME] || {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type BrownfieldPlatform = 'android' | 'ios';
|
|
62
|
+
type ConfigurableOptions = Record<string, unknown>;
|
|
63
|
+
|
|
64
|
+
function getSharedConfig(config: BrownfieldConfig): ConfigurableOptions {
|
|
65
|
+
return config.verbose === undefined ? {} : { verbose: config.verbose };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatConfigValue(value: unknown): string {
|
|
69
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function areConfigValuesEqual(configValue: unknown, optionValue: unknown) {
|
|
73
|
+
return JSON.stringify(configValue) === JSON.stringify(optionValue);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function mergeBrownfieldConfigWithOptions<T extends object>(
|
|
77
|
+
options: T,
|
|
78
|
+
platform: BrownfieldPlatform
|
|
79
|
+
): T {
|
|
80
|
+
const reactNativeBrownfieldConfig = loadBrownfieldConfig();
|
|
81
|
+
|
|
82
|
+
validateBrownfieldCLIConfig(reactNativeBrownfieldConfig);
|
|
83
|
+
|
|
84
|
+
const platformConfig: ConfigurableOptions = {
|
|
85
|
+
...getSharedConfig(reactNativeBrownfieldConfig),
|
|
86
|
+
...reactNativeBrownfieldConfig[platform],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const cliOptions = Object.fromEntries(
|
|
90
|
+
Object.entries(options).filter(([, value]) => value !== undefined)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
for (const [key, value] of Object.entries(cliOptions)) {
|
|
94
|
+
const configValue = platformConfig[key];
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
configValue !== undefined &&
|
|
98
|
+
!areConfigValuesEqual(configValue, value)
|
|
99
|
+
) {
|
|
100
|
+
logger.warn(
|
|
101
|
+
'CLI option "%s" is overriding the react-native-brownfield config value: %s -> %s.',
|
|
102
|
+
key,
|
|
103
|
+
formatConfigValue(configValue),
|
|
104
|
+
formatConfigValue(value)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const mergedOptions = {
|
|
110
|
+
...platformConfig,
|
|
111
|
+
...cliOptions,
|
|
112
|
+
} as T & { verbose?: unknown };
|
|
113
|
+
|
|
114
|
+
if (typeof mergedOptions.verbose === 'boolean') {
|
|
115
|
+
logger.setVerbose(mergedOptions.verbose);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return mergedOptions;
|
|
119
|
+
}
|
|
@@ -42,9 +42,7 @@ export const navigationCodegenCommand = new Command('navigation:codegen')
|
|
|
42
42
|
const specPath = typeof args[0] === 'string' ? args[0] : undefined;
|
|
43
43
|
const options =
|
|
44
44
|
args.find(
|
|
45
|
-
(
|
|
46
|
-
arg
|
|
47
|
-
): arg is RunNavigationCodegenCommandOptions =>
|
|
45
|
+
(arg): arg is RunNavigationCodegenCommandOptions =>
|
|
48
46
|
typeof arg === 'object' && arg !== null && 'dryRun' in arg
|
|
49
47
|
) ?? {};
|
|
50
48
|
|
|
@@ -56,14 +56,72 @@ export default TurboModuleRegistry.getEnforcing<Spec>(
|
|
|
56
56
|
`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
const SKIP_TYPE_TOKENS = new Set([
|
|
60
|
+
'Array',
|
|
61
|
+
'Date',
|
|
62
|
+
'Map',
|
|
63
|
+
'Object',
|
|
64
|
+
'Promise',
|
|
65
|
+
'ReadonlyArray',
|
|
66
|
+
'Record',
|
|
67
|
+
'Set',
|
|
68
|
+
'any',
|
|
69
|
+
'boolean',
|
|
70
|
+
'false',
|
|
71
|
+
'null',
|
|
72
|
+
'number',
|
|
73
|
+
'object',
|
|
74
|
+
'string',
|
|
75
|
+
'true',
|
|
76
|
+
'undefined',
|
|
77
|
+
'unknown',
|
|
78
|
+
'void',
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
function collectTypesUsedByMethods(methods: MethodSignature[]): Set<string> {
|
|
82
|
+
const used = new Set<string>();
|
|
83
|
+
|
|
84
|
+
for (const method of methods) {
|
|
85
|
+
const typeTexts = [
|
|
86
|
+
method.returnType,
|
|
87
|
+
...method.params.map((param) => param.type),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const typeText of typeTexts) {
|
|
91
|
+
const matches = typeText.match(/\b[A-Za-z_]\w*\b/g);
|
|
92
|
+
if (!matches) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const name of matches) {
|
|
97
|
+
if (!SKIP_TYPE_TOKENS.has(name)) {
|
|
98
|
+
used.add(name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return used;
|
|
105
|
+
}
|
|
106
|
+
|
|
59
107
|
function buildTypeImportLine(
|
|
108
|
+
methods: MethodSignature[],
|
|
60
109
|
referencedTypeDeclarations: TypeDeclaration[]
|
|
61
110
|
): string {
|
|
62
111
|
if (referencedTypeDeclarations.length === 0) {
|
|
63
112
|
return '';
|
|
64
113
|
}
|
|
65
114
|
|
|
66
|
-
const
|
|
115
|
+
const usedTypes = collectTypesUsedByMethods(methods);
|
|
116
|
+
const names = referencedTypeDeclarations
|
|
117
|
+
.map((entry) => entry.name)
|
|
118
|
+
.filter((name) => usedTypes.has(name))
|
|
119
|
+
.join(', ');
|
|
120
|
+
|
|
121
|
+
if (names.length === 0) {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
|
|
67
125
|
return `import type { ${names} } from './NativeBrownfieldNavigation';\n`;
|
|
68
126
|
}
|
|
69
127
|
|
|
@@ -71,7 +129,7 @@ export function generateIndexTs(
|
|
|
71
129
|
methods: MethodSignature[],
|
|
72
130
|
referencedTypeDeclarations: TypeDeclaration[] = []
|
|
73
131
|
): string {
|
|
74
|
-
const typeImportLine = buildTypeImportLine(referencedTypeDeclarations);
|
|
132
|
+
const typeImportLine = buildTypeImportLine(methods, referencedTypeDeclarations);
|
|
75
133
|
const functionImplementations = methods
|
|
76
134
|
.map((method) => {
|
|
77
135
|
const params = method.params
|
|
@@ -155,7 +213,7 @@ export function generateIndexDts(
|
|
|
155
213
|
methods: MethodSignature[],
|
|
156
214
|
referencedTypeDeclarations: TypeDeclaration[] = []
|
|
157
215
|
): string {
|
|
158
|
-
const typeImportLine = buildTypeImportLine(referencedTypeDeclarations);
|
|
216
|
+
const typeImportLine = buildTypeImportLine(methods, referencedTypeDeclarations);
|
|
159
217
|
const methodSignatures = methods
|
|
160
218
|
.map((method) => {
|
|
161
219
|
const params = method.params
|