@callstack/react-native-brownfield 5.2.0 → 6.0.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 +23 -0
- package/android/build.gradle +1 -0
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/BrownfieldPerformance.kt +384 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/BrownfieldPerformanceModule.kt +15 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactDelegateWrapper.kt +8 -5
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +78 -23
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt +1 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeFragment.kt +89 -3
- package/android/src/main/java/com/callstack/reactnativebrownfield/constants/ReactNativeFragmentArgNames.kt +2 -0
- package/android/src/test/java/com/callstack/reactnativebrownfield/PerformanceMetricsTest.kt +54 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt +59 -15
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/AgpCompatibilityMatrixTest.kt +6 -8
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/LegacyConsumerConfigurationTest.kt +6 -4
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/RNBundleTaskStubs.kt +27 -0
- package/ios/BrownfieldDisplayMetrics.swift +265 -0
- package/ios/BrownfieldPerformanceBridge.h +8 -0
- package/ios/BrownfieldPerformanceBridge.mm +103 -0
- package/ios/Expo/ExpoHostRuntime.swift +58 -14
- package/ios/Expo/ExpoUpdatesStartup.swift +49 -0
- package/ios/JSBundleLoadObserver.swift +56 -19
- package/ios/JSBundleTimingObserver.h +26 -0
- package/ios/JSBundleTimingObserver.m +140 -0
- package/ios/ReactHostPreloading.swift +7 -3
- package/ios/ReactNativeBrownfield.swift +82 -14
- package/ios/ReactNativeBrownfield.xcodeproj/project.pbxproj +16 -0
- package/ios/Vanilla/ReactNativeHostRuntime.swift +1 -1
- package/ios/Views/ReactNativeView.swift +21 -3
- package/ios/Views/ReactNativeViewController.swift +44 -42
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/podfileHelpers.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/lib/commonjs/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/lib/commonjs/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/lib/commonjs/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scaffold/index.js +2 -0
- package/lib/commonjs/scaffold/index.js.map +1 -0
- package/lib/commonjs/scaffold/projectFiles.js +2 -0
- package/lib/commonjs/scaffold/projectFiles.js.map +1 -0
- package/lib/commonjs/scaffold/xcode.d.js +2 -0
- package/lib/commonjs/scaffold/xcode.d.js.map +1 -0
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/podfileHelpers.js +1 -1
- package/lib/module/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/lib/module/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/lib/module/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/lib/module/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/scaffold/index.js +2 -0
- package/lib/module/scaffold/index.js.map +1 -0
- package/lib/module/scaffold/projectFiles.js +2 -0
- package/lib/module/scaffold/projectFiles.js.map +1 -0
- package/lib/module/scaffold/xcode.d.js +2 -0
- package/lib/module/scaffold/xcode.d.js.map +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +10 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/podfileHelpers.d.ts +4 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +11 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts +6 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +7 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/scaffold/index.d.ts +23 -0
- package/lib/typescript/commonjs/src/scaffold/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts +63 -0
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts.map +1 -0
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +10 -2
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/podfileHelpers.d.ts +4 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +11 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts +6 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +7 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/scaffold/index.d.ts +23 -0
- package/lib/typescript/module/src/scaffold/index.d.ts.map +1 -0
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts +63 -0
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts.map +1 -0
- package/package.json +16 -2
- package/src/expo-config-plugin/android/withAndroidModuleFiles.ts +33 -5
- package/src/expo-config-plugin/ios/podfileHelpers.ts +24 -6
- package/src/expo-config-plugin/ios/withBrownfieldIos.ts +5 -2
- package/src/expo-config-plugin/ios/withIosFrameworkFiles.ts +21 -6
- package/src/expo-config-plugin/ios/xcodeHelpers.ts +77 -5
- package/src/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/src/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/src/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/src/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/src/index.ts +20 -1
- package/src/scaffold/index.ts +395 -0
- package/src/scaffold/projectFiles.ts +243 -0
- package/src/scaffold/xcode.d.ts +4 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { Logger } from '../expo-config-plugin/logging';
|
|
5
|
+
|
|
6
|
+
export const BROWNFIELD_RUNTIME_PACKAGE_NAME =
|
|
7
|
+
'@callstack/react-native-brownfield';
|
|
8
|
+
export const BROWNFIELD_CLI_PACKAGE_NAME = '@callstack/brownfield-cli';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Match the docs convention (getting-started/quick-start + expo.mdx): both
|
|
12
|
+
* packages are regular dependencies. The brownfield CLI ships as a dependency
|
|
13
|
+
* of the runtime package, so a consistent caret range pinned to this package's
|
|
14
|
+
* own published version keeps the two in lockstep (they are released together).
|
|
15
|
+
*/
|
|
16
|
+
export function resolveBrownfieldPackageVersionRange(): string {
|
|
17
|
+
return `^${readOwnPackageVersion()}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readOwnPackageVersion(): string {
|
|
21
|
+
// Walk up from this module to its own package.json. Works both in the
|
|
22
|
+
// published layout (lib/commonjs/scaffold -> package root) and in-repo
|
|
23
|
+
// (src/scaffold -> package root). `__dirname` is absent in true-ESM builds
|
|
24
|
+
// (and `import.meta` must not be used here — Babel keeps it verbatim in the
|
|
25
|
+
// CJS output, which is a parse SyntaxError there; see index.ts), so fall
|
|
26
|
+
// back to cwd, which is correct whenever the package is resolved from the
|
|
27
|
+
// app's own node_modules chain.
|
|
28
|
+
let dir = typeof __dirname === 'string' ? __dirname : process.cwd();
|
|
29
|
+
while (true) {
|
|
30
|
+
const packageJsonPath = path.join(dir, 'package.json');
|
|
31
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as {
|
|
33
|
+
name?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
};
|
|
36
|
+
if (pkg.name === BROWNFIELD_RUNTIME_PACKAGE_NAME) {
|
|
37
|
+
if (!pkg.version) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`${BROWNFIELD_RUNTIME_PACKAGE_NAME} package.json has no version`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return pkg.version;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const parent = path.dirname(dir);
|
|
46
|
+
if (parent === dir) break;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Could not locate the ${BROWNFIELD_RUNTIME_PACKAGE_NAME} package.json to resolve dependency versions.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type PackageJson = {
|
|
55
|
+
dependencies?: Record<string, string>;
|
|
56
|
+
devDependencies?: Record<string, string>;
|
|
57
|
+
scripts?: Record<string, string>;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function readPackageJson(projectRoot: string): PackageJson {
|
|
62
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
63
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
64
|
+
throw new Error(`Missing ${packageJsonPath}`);
|
|
65
|
+
}
|
|
66
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as PackageJson;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writePackageJson(projectRoot: string, pkg: PackageJson): void {
|
|
70
|
+
// Stable formatting (2-space indent, trailing newline), so repeated runs
|
|
71
|
+
// produce byte-identical output.
|
|
72
|
+
fs.writeFileSync(
|
|
73
|
+
path.join(projectRoot, 'package.json'),
|
|
74
|
+
JSON.stringify(pkg, null, 2) + '\n',
|
|
75
|
+
'utf8'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ensureDependency(
|
|
80
|
+
pkg: PackageJson,
|
|
81
|
+
packageName: string,
|
|
82
|
+
versionRange: string
|
|
83
|
+
): boolean {
|
|
84
|
+
// Idempotency: if the package is declared anywhere already, leave the
|
|
85
|
+
// user's version spec untouched (same spirit as the Podfile/gradle guards).
|
|
86
|
+
if (pkg.dependencies?.[packageName] ?? pkg.devDependencies?.[packageName]) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
pkg.dependencies = pkg.dependencies ?? {};
|
|
90
|
+
pkg.dependencies[packageName] = versionRange;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Adds @callstack/react-native-brownfield (the Kotlin/Swift host code the
|
|
96
|
+
* scaffolded module imports) and @callstack/brownfield-cli (the `brownfield`
|
|
97
|
+
* bin used by the package:* scripts) to the app's dependencies.
|
|
98
|
+
* Existing declarations are never overwritten. Returns the names added.
|
|
99
|
+
*/
|
|
100
|
+
export function addBrownfieldDependencies(projectRoot: string): string[] {
|
|
101
|
+
const pkg = readPackageJson(projectRoot);
|
|
102
|
+
const versionRange = resolveBrownfieldPackageVersionRange();
|
|
103
|
+
|
|
104
|
+
const added: string[] = [];
|
|
105
|
+
if (ensureDependency(pkg, BROWNFIELD_RUNTIME_PACKAGE_NAME, versionRange)) {
|
|
106
|
+
added.push(BROWNFIELD_RUNTIME_PACKAGE_NAME);
|
|
107
|
+
}
|
|
108
|
+
if (ensureDependency(pkg, BROWNFIELD_CLI_PACKAGE_NAME, versionRange)) {
|
|
109
|
+
added.push(BROWNFIELD_CLI_PACKAGE_NAME);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (added.length > 0) {
|
|
113
|
+
writePackageJson(projectRoot, pkg);
|
|
114
|
+
Logger.logDebug(
|
|
115
|
+
`Added Brownfield dependencies: ${added.join(', ')} (${versionRange})`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return added;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Adds the `package:ios` / `package:android` scripts from the docs' manual
|
|
123
|
+
* steps (settings come from the generated brownfield.config.json, so no flags
|
|
124
|
+
* are duplicated here). Existing scripts with the same names are kept.
|
|
125
|
+
*/
|
|
126
|
+
export function addBrownfieldPackageScripts(projectRoot: string): string[] {
|
|
127
|
+
const pkg = readPackageJson(projectRoot);
|
|
128
|
+
pkg.scripts = pkg.scripts ?? {};
|
|
129
|
+
|
|
130
|
+
const desiredScripts: Record<string, string> = {
|
|
131
|
+
'package:ios': 'brownfield package:ios',
|
|
132
|
+
'package:android': 'brownfield package:android',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const added: string[] = [];
|
|
136
|
+
for (const [name, command] of Object.entries(desiredScripts)) {
|
|
137
|
+
if (pkg.scripts[name] === undefined) {
|
|
138
|
+
pkg.scripts[name] = command;
|
|
139
|
+
added.push(name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (added.length > 0) {
|
|
144
|
+
writePackageJson(projectRoot, pkg);
|
|
145
|
+
Logger.logDebug(`Added Brownfield scripts: ${added.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
return added;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type BrownfieldFileConfig = {
|
|
151
|
+
$schema: string;
|
|
152
|
+
ios: {
|
|
153
|
+
scheme: string;
|
|
154
|
+
configuration: string;
|
|
155
|
+
};
|
|
156
|
+
android: {
|
|
157
|
+
moduleName: string;
|
|
158
|
+
variant: string;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Builds the brownfield.config.json content for the scaffolded targets,
|
|
164
|
+
* matching the manual steps in docs getting-started/ios.mdx (step 5) and
|
|
165
|
+
* android.mdx (step 7) and the CLI config schema (packages/cli/src/types.ts,
|
|
166
|
+
* strict additionalProperties: false).
|
|
167
|
+
*
|
|
168
|
+
* Idempotency strategy: the content is derived deterministically from the
|
|
169
|
+
* scaffolded project state (framework/module names), so re-running the
|
|
170
|
+
* scaffold overwrites with byte-identical content. Users customizing it are
|
|
171
|
+
* taking over responsibility; a re-scaffold with the same names is a no-op.
|
|
172
|
+
*/
|
|
173
|
+
export function createBrownfieldFileConfig({
|
|
174
|
+
iosFrameworkName,
|
|
175
|
+
androidModuleName,
|
|
176
|
+
}: {
|
|
177
|
+
iosFrameworkName: string;
|
|
178
|
+
androidModuleName: string;
|
|
179
|
+
}): BrownfieldFileConfig {
|
|
180
|
+
return {
|
|
181
|
+
$schema: 'https://oss.callstack.com/react-native-brownfield/schema.json',
|
|
182
|
+
ios: {
|
|
183
|
+
// iOS packaging builds the generated framework target by scheme name.
|
|
184
|
+
scheme: iosFrameworkName,
|
|
185
|
+
configuration: 'Release',
|
|
186
|
+
},
|
|
187
|
+
android: {
|
|
188
|
+
// Gradle module name of the generated library module.
|
|
189
|
+
moduleName: androidModuleName,
|
|
190
|
+
variant: 'Release',
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Writes brownfield.config.json at the project root. Deterministic output:
|
|
197
|
+
* same inputs -> same bytes, so re-running the scaffold never duplicates or
|
|
198
|
+
* drifts the file.
|
|
199
|
+
*
|
|
200
|
+
* Skips writing (returns null) when another Brownfield config source already
|
|
201
|
+
* exists: packages/cli loadBrownfieldConfig fails hard on multiple config
|
|
202
|
+
* files, so an existing brownfield.config.js or a legacy `brownfield` key in
|
|
203
|
+
* package.json takes precedence over generating a JSON one.
|
|
204
|
+
*/
|
|
205
|
+
export function writeBrownfieldFileConfig(
|
|
206
|
+
projectRoot: string,
|
|
207
|
+
names: { iosFrameworkName: string; androidModuleName: string }
|
|
208
|
+
): string | null {
|
|
209
|
+
const configPath = path.join(projectRoot, 'brownfield.config.json');
|
|
210
|
+
|
|
211
|
+
const jsConfigPath = path.join(projectRoot, 'brownfield.config.js');
|
|
212
|
+
if (fs.existsSync(jsConfigPath)) {
|
|
213
|
+
Logger.logDebug(
|
|
214
|
+
`Skipping brownfield.config.json: ${jsConfigPath} already exists.`
|
|
215
|
+
);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const pkg = readPackageJson(projectRoot);
|
|
220
|
+
if ('brownfield' in pkg) {
|
|
221
|
+
Logger.logDebug(
|
|
222
|
+
'Skipping brownfield.config.json: legacy "brownfield" key in package.json already exists.'
|
|
223
|
+
);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Trailing newline, same as writePackageJson: the idempotency guard below
|
|
228
|
+
// compares bytes, so the written content must be exactly what a
|
|
229
|
+
// newline-normalizing toolchain (editorconfig, prettier, git text eol)
|
|
230
|
+
// leaves on disk. Content + '\n' is both the written bytes and the
|
|
231
|
+
// comparison value, so the guard cannot be broken by an external normalizer.
|
|
232
|
+
const content =
|
|
233
|
+
JSON.stringify(createBrownfieldFileConfig(names), null, 2) + '\n';
|
|
234
|
+
const prev = fs.existsSync(configPath)
|
|
235
|
+
? fs.readFileSync(configPath, 'utf8')
|
|
236
|
+
: null;
|
|
237
|
+
|
|
238
|
+
if (prev !== content) {
|
|
239
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
240
|
+
Logger.logDebug(`Wrote ${configPath}`);
|
|
241
|
+
}
|
|
242
|
+
return configPath;
|
|
243
|
+
}
|