@callstack/brownfield-cli 3.9.0 → 3.11.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 +18 -0
- package/dist/brownfield/commands/packageIos.d.ts +1 -0
- package/dist/brownfield/commands/packageIos.d.ts.map +1 -1
- package/dist/brownfield/commands/packageIos.js +97 -6
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.d.ts +8 -0
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.d.ts.map +1 -0
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.js +21 -0
- package/dist/brownfield/utils/createLocalSpmPackage.d.ts +10 -0
- package/dist/brownfield/utils/createLocalSpmPackage.d.ts.map +1 -0
- package/dist/brownfield/utils/createLocalSpmPackage.js +138 -0
- package/dist/brownfield/utils/prepareLocalSpmArtifacts.d.ts +8 -0
- package/dist/brownfield/utils/prepareLocalSpmArtifacts.d.ts.map +1 -0
- package/dist/brownfield/utils/prepareLocalSpmArtifacts.js +77 -0
- package/dist/brownfield/utils/project.d.ts +1 -0
- package/dist/brownfield/utils/project.d.ts.map +1 -1
- package/dist/brownfield/utils/project.js +8 -0
- package/dist/brownfield/utils/resolvePackagedFrameworkName.d.ts +14 -0
- package/dist/brownfield/utils/resolvePackagedFrameworkName.d.ts.map +1 -0
- package/dist/brownfield/utils/resolvePackagedFrameworkName.js +61 -0
- package/dist/brownfield/utils/supportsPrebuiltRNCore.d.ts +18 -0
- package/dist/brownfield/utils/supportsPrebuiltRNCore.d.ts.map +1 -0
- package/dist/brownfield/utils/supportsPrebuiltRNCore.js +36 -0
- package/package.json +6 -6
- package/src/brownfield/commands/packageIos.ts +288 -129
- package/src/brownfield/utils/copyDebugBundleToSimulatorSlice.ts +58 -0
- package/src/brownfield/utils/createLocalSpmPackage.ts +208 -0
- package/src/brownfield/utils/prepareLocalSpmArtifacts.ts +117 -0
- package/src/brownfield/utils/project.ts +9 -0
- package/src/brownfield/utils/resolvePackagedFrameworkName.ts +98 -0
- package/src/brownfield/utils/supportsPrebuiltRNCore.ts +67 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
prepareLocalSpmArtifacts,
|
|
6
|
+
SPM_ARTIFACTS_DIR_NAME,
|
|
7
|
+
} from './prepareLocalSpmArtifacts.js';
|
|
8
|
+
|
|
9
|
+
type CreateLocalSpmPackageOptions = {
|
|
10
|
+
packageDir: string;
|
|
11
|
+
frameworkName?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type CreateLocalSpmPackageResult = {
|
|
15
|
+
packageManifestPath: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const RESERVED_FRAMEWORK_NAMES = new Set([
|
|
19
|
+
'hermes',
|
|
20
|
+
'hermesvm',
|
|
21
|
+
'ReactBrownfield',
|
|
22
|
+
'Brownie',
|
|
23
|
+
'BrownfieldNavigation',
|
|
24
|
+
'React',
|
|
25
|
+
'ReactNativeDependencies',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function requireXcframework(packageDir: string, name: string) {
|
|
29
|
+
const xcframeworkPath = path.join(packageDir, `${name}.xcframework`);
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(xcframeworkPath)) {
|
|
32
|
+
throw new Error(`Missing required XCFramework: ${name}.xcframework`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function optionalXcframework(packageDir: string, name: string) {
|
|
39
|
+
return fs.existsSync(path.join(packageDir, `${name}.xcframework`))
|
|
40
|
+
? name
|
|
41
|
+
: null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireHermesXcframework(packageDir: string) {
|
|
45
|
+
return (
|
|
46
|
+
optionalXcframework(packageDir, 'hermesvm') ??
|
|
47
|
+
requireXcframework(packageDir, 'hermes')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveAppFrameworkName(
|
|
52
|
+
packageDir: string,
|
|
53
|
+
explicitFrameworkName?: string
|
|
54
|
+
) {
|
|
55
|
+
if (explicitFrameworkName) {
|
|
56
|
+
return requireXcframework(packageDir, explicitFrameworkName);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const candidates = fs
|
|
60
|
+
.readdirSync(packageDir, { withFileTypes: true })
|
|
61
|
+
.filter(
|
|
62
|
+
(entry) =>
|
|
63
|
+
entry.isDirectory() &&
|
|
64
|
+
entry.name.endsWith('.xcframework') &&
|
|
65
|
+
!RESERVED_FRAMEWORK_NAMES.has(path.basename(entry.name, '.xcframework'))
|
|
66
|
+
)
|
|
67
|
+
.map((entry) => path.basename(entry.name, '.xcframework'))
|
|
68
|
+
.sort();
|
|
69
|
+
|
|
70
|
+
if (candidates.length === 1 && candidates[0]) {
|
|
71
|
+
return candidates[0];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (candidates.length === 0) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Could not resolve the packaged app XCFramework automatically. Pass --scheme explicitly when packaging.'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Found multiple packaged app XCFramework candidates (${candidates.join(', ')}). Pass --scheme explicitly when packaging.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderPackageSwift({
|
|
86
|
+
packageName,
|
|
87
|
+
libraryName,
|
|
88
|
+
targetNames,
|
|
89
|
+
}: {
|
|
90
|
+
packageName: string;
|
|
91
|
+
libraryName: string;
|
|
92
|
+
targetNames: string[];
|
|
93
|
+
}) {
|
|
94
|
+
const binaryTargets = targetNames
|
|
95
|
+
.map(
|
|
96
|
+
(targetName) =>
|
|
97
|
+
` .binaryTarget(name: "${targetName}", path: "./${SPM_ARTIFACTS_DIR_NAME}/${targetName}.xcframework")`
|
|
98
|
+
)
|
|
99
|
+
.join(',\n');
|
|
100
|
+
|
|
101
|
+
const targetDependencies = targetNames
|
|
102
|
+
.map((targetName) => `"${targetName}"`)
|
|
103
|
+
.join(', ');
|
|
104
|
+
|
|
105
|
+
return `// swift-tools-version: 5.9
|
|
106
|
+
|
|
107
|
+
import PackageDescription
|
|
108
|
+
|
|
109
|
+
let package = Package(
|
|
110
|
+
name: "${packageName}",
|
|
111
|
+
platforms: [
|
|
112
|
+
.iOS(.v14),
|
|
113
|
+
],
|
|
114
|
+
products: [
|
|
115
|
+
.library(name: "${libraryName}", targets: [${targetDependencies}]),
|
|
116
|
+
],
|
|
117
|
+
targets: [
|
|
118
|
+
${binaryTargets}
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderReadme({
|
|
125
|
+
packageName,
|
|
126
|
+
libraryName,
|
|
127
|
+
targetNames,
|
|
128
|
+
}: {
|
|
129
|
+
packageName: string;
|
|
130
|
+
libraryName: string;
|
|
131
|
+
targetNames: string[];
|
|
132
|
+
}) {
|
|
133
|
+
const frameworks = targetNames.map((targetName) => `- \`${targetName}\``).join('\n');
|
|
134
|
+
|
|
135
|
+
return `# ${packageName}
|
|
136
|
+
|
|
137
|
+
This is a generated local Swift Package Manager package for the packaged React Native brownfield artifacts.
|
|
138
|
+
|
|
139
|
+
## Product
|
|
140
|
+
|
|
141
|
+
- Library product: \`${libraryName}\`
|
|
142
|
+
|
|
143
|
+
## Included Binary Targets
|
|
144
|
+
|
|
145
|
+
${frameworks}
|
|
146
|
+
|
|
147
|
+
## How To Use
|
|
148
|
+
|
|
149
|
+
1. In Xcode, choose **File > Add Package Dependencies...**
|
|
150
|
+
2. Click **Add Local...**
|
|
151
|
+
3. Select this folder, the one containing \`Package.swift\` and the \`spm-artifacts\` directory
|
|
152
|
+
4. Add the \`${libraryName}\` library product to your app target
|
|
153
|
+
|
|
154
|
+
## Troubleshooting
|
|
155
|
+
|
|
156
|
+
If Xcode builds your host app but the simulator installation fails, check the host app target first:
|
|
157
|
+
|
|
158
|
+
- Make sure the target still includes its app entry point source files, such as \`App.swift\`, \`SceneDelegate\`, \`AppDelegate\`, or equivalent startup files
|
|
159
|
+
- Make sure the host target uses the same React Native module name that the packaged app registers for its brownfield surface. This repo's example apps use \`RNApp\` for both the plain React Native and Expo variants
|
|
160
|
+
- If the install error says the app is "missing its bundle executable", the host app target produced an invalid \`.app\` bundle and the issue is not in this generated SPM package
|
|
161
|
+
- If you are migrating an existing target from direct \`*.xcframework\` linking to local SPM, remove the old direct XCFramework references before building again
|
|
162
|
+
|
|
163
|
+
This folder is generated by \`brownfield package:ios --add-spm-package\`. Re-run that command whenever the packaged XCFrameworks change so this package stays in sync.
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createLocalSpmPackage({
|
|
168
|
+
packageDir,
|
|
169
|
+
frameworkName,
|
|
170
|
+
}: CreateLocalSpmPackageOptions): CreateLocalSpmPackageResult {
|
|
171
|
+
const resolvedFrameworkName = resolveAppFrameworkName(
|
|
172
|
+
packageDir,
|
|
173
|
+
frameworkName
|
|
174
|
+
);
|
|
175
|
+
const targetNames = [
|
|
176
|
+
resolvedFrameworkName,
|
|
177
|
+
requireHermesXcframework(packageDir),
|
|
178
|
+
requireXcframework(packageDir, 'ReactBrownfield'),
|
|
179
|
+
optionalXcframework(packageDir, 'Brownie'),
|
|
180
|
+
optionalXcframework(packageDir, 'BrownfieldNavigation'),
|
|
181
|
+
optionalXcframework(packageDir, 'React'),
|
|
182
|
+
optionalXcframework(packageDir, 'ReactNativeDependencies'),
|
|
183
|
+
].filter((targetName): targetName is string => targetName !== null);
|
|
184
|
+
|
|
185
|
+
const packageManifestPath = path.join(packageDir, 'Package.swift');
|
|
186
|
+
const readmePath = path.join(packageDir, 'README.md');
|
|
187
|
+
prepareLocalSpmArtifacts({
|
|
188
|
+
packageDir,
|
|
189
|
+
targetNames,
|
|
190
|
+
});
|
|
191
|
+
const manifest = renderPackageSwift({
|
|
192
|
+
packageName: `${resolvedFrameworkName}Package`,
|
|
193
|
+
libraryName: resolvedFrameworkName,
|
|
194
|
+
targetNames,
|
|
195
|
+
});
|
|
196
|
+
const readme = renderReadme({
|
|
197
|
+
packageName: `${resolvedFrameworkName}Package`,
|
|
198
|
+
libraryName: resolvedFrameworkName,
|
|
199
|
+
targetNames,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(packageManifestPath, manifest, 'utf8');
|
|
203
|
+
fs.writeFileSync(readmePath, readme, 'utf8');
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
packageManifestPath,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
type PrepareLocalSpmArtifactsOptions = {
|
|
6
|
+
packageDir: string;
|
|
7
|
+
targetNames: string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const SPM_ARTIFACTS_DIR_NAME = 'spm-artifacts';
|
|
11
|
+
|
|
12
|
+
function removeCodeSignatureArtifacts(dirPath: string) {
|
|
13
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
15
|
+
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
if (entry.name === '_CodeSignature') {
|
|
18
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
removeCodeSignatureArtifacts(entryPath);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (entry.name === 'CodeResources') {
|
|
27
|
+
fs.rmSync(entryPath, { force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveFrameworkExecutablePath(
|
|
33
|
+
frameworkDir: string,
|
|
34
|
+
frameworkName: string
|
|
35
|
+
) {
|
|
36
|
+
const directExecutablePath = path.join(frameworkDir, frameworkName);
|
|
37
|
+
if (fs.existsSync(directExecutablePath)) {
|
|
38
|
+
return directExecutablePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const versionedExecutablePath = path.join(
|
|
42
|
+
frameworkDir,
|
|
43
|
+
'Versions',
|
|
44
|
+
'Current',
|
|
45
|
+
frameworkName
|
|
46
|
+
);
|
|
47
|
+
if (fs.existsSync(versionedExecutablePath)) {
|
|
48
|
+
return versionedExecutablePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function removeExecutableSignature(executablePath: string) {
|
|
55
|
+
try {
|
|
56
|
+
execFileSync('codesign', ['--remove-signature', executablePath], {
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (
|
|
61
|
+
error instanceof Error &&
|
|
62
|
+
error.message.includes('code object is not signed at all')
|
|
63
|
+
) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeCopiedXcframeworkSignature(xcframeworkPath: string) {
|
|
72
|
+
removeCodeSignatureArtifacts(xcframeworkPath);
|
|
73
|
+
|
|
74
|
+
for (const sliceName of fs.readdirSync(xcframeworkPath)) {
|
|
75
|
+
const slicePath = path.join(xcframeworkPath, sliceName);
|
|
76
|
+
if (!fs.statSync(slicePath).isDirectory()) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const entry of fs.readdirSync(slicePath)) {
|
|
81
|
+
if (!entry.endsWith('.framework')) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const frameworkName = path.basename(entry, '.framework');
|
|
86
|
+
const frameworkDir = path.join(slicePath, entry);
|
|
87
|
+
const executablePath = resolveFrameworkExecutablePath(
|
|
88
|
+
frameworkDir,
|
|
89
|
+
frameworkName
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (executablePath) {
|
|
93
|
+
removeExecutableSignature(executablePath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function prepareLocalSpmArtifacts({
|
|
100
|
+
packageDir,
|
|
101
|
+
targetNames,
|
|
102
|
+
}: PrepareLocalSpmArtifactsOptions) {
|
|
103
|
+
const spmArtifactsDir = path.join(packageDir, SPM_ARTIFACTS_DIR_NAME);
|
|
104
|
+
|
|
105
|
+
fs.rmSync(spmArtifactsDir, { recursive: true, force: true });
|
|
106
|
+
fs.mkdirSync(spmArtifactsDir, { recursive: true });
|
|
107
|
+
|
|
108
|
+
for (const targetName of targetNames) {
|
|
109
|
+
const sourcePath = path.join(packageDir, `${targetName}.xcframework`);
|
|
110
|
+
const destinationPath = path.join(spmArtifactsDir, `${targetName}.xcframework`);
|
|
111
|
+
|
|
112
|
+
fs.renameSync(sourcePath, destinationPath);
|
|
113
|
+
normalizeCopiedXcframeworkSignature(destinationPath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return spmArtifactsDir;
|
|
117
|
+
}
|
|
@@ -56,6 +56,15 @@ export function isExpoProject(projectRoot: string): boolean {
|
|
|
56
56
|
return hasExpoConfig && dependsOnExpo;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export function getExpoSdkMajor(projectRoot: string): number | null {
|
|
60
|
+
const rawExpoVersion = getExpoConfigIfIsExpo(projectRoot)?.exp.sdkVersion;
|
|
61
|
+
if (!rawExpoVersion) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const expoSdkMajor = parseInt(rawExpoVersion.split('.')[0], 10);
|
|
65
|
+
return Number.isFinite(expoSdkMajor) ? expoSdkMajor : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
/**
|
|
60
69
|
* Fills the RNC CLI project config from the Expo config by mutating the passed in `options.projectConfig` object in place
|
|
61
70
|
*/
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
type Resolution = 'explicit' | 'detected' | 'not_found' | 'ambiguous';
|
|
5
|
+
|
|
6
|
+
export interface ResolvePackagedFrameworkNameResult {
|
|
7
|
+
frameworkName: string | null;
|
|
8
|
+
resolution: Resolution;
|
|
9
|
+
candidates?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ResolvePackagedFrameworkNameOptions {
|
|
13
|
+
explicitScheme?: string;
|
|
14
|
+
productsPath: string;
|
|
15
|
+
configuration: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function collectFrameworkCandidates(configurationProductsPath: string): string[] {
|
|
19
|
+
if (!fs.existsSync(configurationProductsPath)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const discoveredFrameworks = new Set<string>();
|
|
24
|
+
|
|
25
|
+
for (const entry of fs.readdirSync(configurationProductsPath, { withFileTypes: true })) {
|
|
26
|
+
const entryPath = path.join(configurationProductsPath, entry.name);
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory() && entry.name.endsWith('.framework')) {
|
|
29
|
+
const frameworkName = path.basename(entry.name, '.framework');
|
|
30
|
+
const bundlePath = path.join(entryPath, 'main.jsbundle');
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(bundlePath)) {
|
|
33
|
+
discoveredFrameworks.add(frameworkName);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!entry.isDirectory()) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const nestedEntry of fs.readdirSync(entryPath, { withFileTypes: true })) {
|
|
44
|
+
if (!nestedEntry.isDirectory() || !nestedEntry.name.endsWith('.framework')) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const frameworkName = path.basename(nestedEntry.name, '.framework');
|
|
49
|
+
const bundlePath = path.join(entryPath, nestedEntry.name, 'main.jsbundle');
|
|
50
|
+
|
|
51
|
+
if (fs.existsSync(bundlePath)) {
|
|
52
|
+
discoveredFrameworks.add(frameworkName);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [...discoveredFrameworks].sort();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolvePackagedFrameworkName({
|
|
61
|
+
explicitScheme,
|
|
62
|
+
productsPath,
|
|
63
|
+
configuration,
|
|
64
|
+
}: ResolvePackagedFrameworkNameOptions): ResolvePackagedFrameworkNameResult {
|
|
65
|
+
if (explicitScheme) {
|
|
66
|
+
return {
|
|
67
|
+
frameworkName: explicitScheme,
|
|
68
|
+
resolution: 'explicit',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const configurationProductsPath = path.join(
|
|
73
|
+
productsPath,
|
|
74
|
+
`${configuration}-iphoneos`
|
|
75
|
+
);
|
|
76
|
+
const candidates = collectFrameworkCandidates(configurationProductsPath);
|
|
77
|
+
|
|
78
|
+
if (candidates.length === 1) {
|
|
79
|
+
return {
|
|
80
|
+
frameworkName: candidates[0] ?? null,
|
|
81
|
+
resolution: 'detected',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (candidates.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
frameworkName: null,
|
|
88
|
+
resolution: 'not_found',
|
|
89
|
+
candidates,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
frameworkName: null,
|
|
95
|
+
resolution: 'ambiguous',
|
|
96
|
+
candidates,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getReactNativeVersion, versionCompare } from '@rock-js/tools';
|
|
2
|
+
|
|
3
|
+
import { getExpoSdkMajor, isExpoProject } from './project.js';
|
|
4
|
+
|
|
5
|
+
/** Minimum RN version that can opt in to prebuilts via `--use-prebuilt-rn-core`. */
|
|
6
|
+
export const MIN_REACT_NATIVE_VERSION_FOR_OPT_IN_PREBUILT_RN_CORE = '0.81.0';
|
|
7
|
+
/** Minimum RN version where Brownfield enables prebuilts by default (vanilla projects). */
|
|
8
|
+
export const MIN_REACT_NATIVE_VERSION_FOR_PREBUILT_RN_CORE_BY_DEFAULT =
|
|
9
|
+
'0.84.0';
|
|
10
|
+
export const MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_RN_CORE_BY_DEFAULT = 55;
|
|
11
|
+
|
|
12
|
+
export type PrebuiltRNCoreSupportResult =
|
|
13
|
+
| { supported: true; enabledByDefault: boolean; reason?: never }
|
|
14
|
+
| { supported: false; enabledByDefault?: never; reason: string };
|
|
15
|
+
|
|
16
|
+
export function supportsPrebuiltRNCore({
|
|
17
|
+
projectRoot,
|
|
18
|
+
}: {
|
|
19
|
+
projectRoot: string;
|
|
20
|
+
}): PrebuiltRNCoreSupportResult {
|
|
21
|
+
const reactNativeVersion = getReactNativeVersion(projectRoot);
|
|
22
|
+
|
|
23
|
+
if (reactNativeVersion === 'unknown') {
|
|
24
|
+
return {
|
|
25
|
+
supported: false,
|
|
26
|
+
reason:
|
|
27
|
+
'Cannot use --use-prebuilt-rn-core: unable to resolve the installed react-native version.',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
versionCompare(
|
|
33
|
+
reactNativeVersion,
|
|
34
|
+
MIN_REACT_NATIVE_VERSION_FOR_OPT_IN_PREBUILT_RN_CORE
|
|
35
|
+
) < 0
|
|
36
|
+
) {
|
|
37
|
+
return {
|
|
38
|
+
supported: false,
|
|
39
|
+
reason: `--use-prebuilt-rn-core requires React Native ${MIN_REACT_NATIVE_VERSION_FOR_OPT_IN_PREBUILT_RN_CORE} or newer (found ${reactNativeVersion}).`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isExpoProject(projectRoot)) {
|
|
44
|
+
const expoSdkMajor = getExpoSdkMajor(projectRoot);
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
expoSdkMajor === null ||
|
|
48
|
+
expoSdkMajor < MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_RN_CORE_BY_DEFAULT
|
|
49
|
+
) {
|
|
50
|
+
const sdkLabel = expoSdkMajor === null ? 'unknown' : String(expoSdkMajor);
|
|
51
|
+
return {
|
|
52
|
+
supported: false,
|
|
53
|
+
reason: `--use-prebuilt-rn-core is unsupported in Expo SDK ${sdkLabel}: packaging brownfield with prebuilts requires Expo SDK ${MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_RN_CORE_BY_DEFAULT} or newer.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { supported: true, enabledByDefault: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const enabledByDefault =
|
|
61
|
+
versionCompare(
|
|
62
|
+
reactNativeVersion,
|
|
63
|
+
MIN_REACT_NATIVE_VERSION_FOR_PREBUILT_RN_CORE_BY_DEFAULT
|
|
64
|
+
) >= 0;
|
|
65
|
+
|
|
66
|
+
return { supported: true, enabledByDefault };
|
|
67
|
+
}
|