@callstack/brownfield-cli 3.13.1 → 3.14.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 +16 -0
- package/dist/brownfield/commands/packageAndroid.d.ts.map +1 -1
- package/dist/brownfield/commands/packageAndroid.js +3 -1
- 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 +37 -4
- package/dist/brownfield/commands/publishAndroid.d.ts.map +1 -1
- package/dist/brownfield/commands/publishAndroid.js +3 -1
- package/dist/brownfield/utils/createLocalSpmPackage.d.ts +2 -1
- package/dist/brownfield/utils/createLocalSpmPackage.d.ts.map +1 -1
- package/dist/brownfield/utils/createLocalSpmPackage.js +8 -2
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.d.ts +9 -0
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.d.ts.map +1 -0
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.js +70 -0
- package/dist/brownfield/utils/normalizeCopiedXcframework.d.ts +3 -0
- package/dist/brownfield/utils/normalizeCopiedXcframework.d.ts.map +1 -0
- package/dist/brownfield/utils/normalizeCopiedXcframework.js +64 -0
- package/dist/brownfield/utils/prepareLocalSpmArtifacts.d.ts.map +1 -1
- package/dist/brownfield/utils/prepareLocalSpmArtifacts.js +2 -63
- package/dist/brownfield/utils/project.d.ts.map +1 -1
- package/dist/brownfield/utils/project.js +20 -9
- package/dist/brownfield/utils/supportsPrebuiltExpo.d.ts +14 -0
- package/dist/brownfield/utils/supportsPrebuiltExpo.d.ts.map +1 -0
- package/dist/brownfield/utils/supportsPrebuiltExpo.js +20 -0
- package/dist/brownie/config.js +1 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -7
- package/dist/expoPluginConfig.d.ts +74 -0
- package/dist/expoPluginConfig.d.ts.map +1 -0
- package/dist/expoPluginConfig.js +103 -0
- package/dist/types.d.ts +94 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -6
- package/schema.json +98 -9
- package/src/brownfield/commands/packageAndroid.ts +4 -1
- package/src/brownfield/commands/packageIos.ts +53 -4
- package/src/brownfield/commands/publishAndroid.ts +4 -1
- package/src/brownfield/utils/createLocalSpmPackage.ts +15 -1
- package/src/brownfield/utils/emitExpoSupportXcframeworks.ts +132 -0
- package/src/brownfield/utils/normalizeCopiedXcframework.ts +90 -0
- package/src/brownfield/utils/prepareLocalSpmArtifacts.ts +7 -90
- package/src/brownfield/utils/project.ts +33 -14
- package/src/brownfield/utils/supportsPrebuiltExpo.ts +34 -0
- package/src/brownie/config.ts +1 -1
- package/src/config.ts +12 -8
- package/src/expoPluginConfig.ts +195 -0
- package/src/types.ts +112 -3
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
import { Command, Option } from 'commander';
|
|
18
18
|
|
|
19
19
|
import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
|
|
20
|
+
import { findProjectRoot } from '../utils/paths.js';
|
|
20
21
|
import { getProjectInfo } from '../utils/project.js';
|
|
22
|
+
import { supportsPrebuiltExpo } from '../utils/supportsPrebuiltExpo.js';
|
|
21
23
|
import { supportsPrebuiltRNCore } from '../utils/supportsPrebuiltRNCore.js';
|
|
22
24
|
import {
|
|
23
25
|
actionRunner,
|
|
@@ -31,7 +33,9 @@ import { resolvePackagedFrameworkName } from '../utils/resolvePackagedFrameworkN
|
|
|
31
33
|
import { stripFrameworkBinary } from '../utils/stripFrameworkBinary.js';
|
|
32
34
|
import type { PackageIosOptions } from '../../types.js';
|
|
33
35
|
import { createLocalSpmPackage } from '../utils/createLocalSpmPackage.js';
|
|
36
|
+
import { emitExpoSupportXcframeworks } from '../utils/emitExpoSupportXcframeworks.js';
|
|
34
37
|
import { mergeBrownfieldConfigWithOptions } from '../../config.js';
|
|
38
|
+
import { removeCodeSignatureArtifacts } from '../utils/normalizeCopiedXcframework.js';
|
|
35
39
|
|
|
36
40
|
/** Help text for `--use-prebuilt-rn-core` (keep in sync with docs/docs/docs/getting-started/ios.mdx, "React Native Prebuilts" section). */
|
|
37
41
|
const USE_PREBUILT_RN_CORE_HELP =
|
|
@@ -40,7 +44,14 @@ const USE_PREBUILT_RN_CORE_HELP =
|
|
|
40
44
|
'Pass true or false to force either behavior. Use the flag without a value as shorthand for true (same as `--use-prebuilt-rn-core true`). ' +
|
|
41
45
|
'See the Brownfield iOS guide for details: https://oss.callstack.com/react-native-brownfield/docs/getting-started/ios#react-native-prebuilts';
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
/** Help text for `--use-prebuilt-expo`. */
|
|
48
|
+
const USE_PREBUILT_EXPO_HELP =
|
|
49
|
+
'Whether the Xcode build for packaging should use prebuilt Expo support XCFrameworks. ' +
|
|
50
|
+
'If you omit this flag, Brownfield follows version-aware defaults for supported Expo projects. ' +
|
|
51
|
+
'Pass true or false to force either behavior. Use the flag without a value as shorthand for true (same as `--use-prebuilt-expo true`).';
|
|
52
|
+
|
|
53
|
+
function parseBooleanOptionArgument(
|
|
54
|
+
flagName: string,
|
|
44
55
|
value: string | boolean
|
|
45
56
|
): boolean {
|
|
46
57
|
if (typeof value === 'boolean') {
|
|
@@ -54,10 +65,22 @@ export function parseUsePrebuiltRnCoreArgument(
|
|
|
54
65
|
return false;
|
|
55
66
|
}
|
|
56
67
|
throw new RockError(
|
|
57
|
-
`Invalid value for
|
|
68
|
+
`Invalid value for ${flagName}: expected true or false, received "${value}"`
|
|
58
69
|
);
|
|
59
70
|
}
|
|
60
71
|
|
|
72
|
+
export function parseUsePrebuiltRnCoreArgument(
|
|
73
|
+
value: string | boolean
|
|
74
|
+
): boolean {
|
|
75
|
+
return parseBooleanOptionArgument('--use-prebuilt-rn-core', value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function parseUsePrebuiltExpoArgument(
|
|
79
|
+
value: string | boolean
|
|
80
|
+
): boolean {
|
|
81
|
+
return parseBooleanOptionArgument('--use-prebuilt-expo', value);
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
function getPackagedFrameworkResolutionFailureMessage({
|
|
62
85
|
resolution,
|
|
63
86
|
candidates,
|
|
@@ -88,6 +111,11 @@ export const packageIosCommand = curryOptions(
|
|
|
88
111
|
.preset(true)
|
|
89
112
|
.argParser(parseUsePrebuiltRnCoreArgument)
|
|
90
113
|
)
|
|
114
|
+
.addOption(
|
|
115
|
+
new Option('--use-prebuilt-expo [bool]', USE_PREBUILT_EXPO_HELP)
|
|
116
|
+
.preset(true)
|
|
117
|
+
.argParser(parseUsePrebuiltExpoArgument)
|
|
118
|
+
)
|
|
91
119
|
.addOption(
|
|
92
120
|
new Option(
|
|
93
121
|
'--add-spm-package',
|
|
@@ -97,16 +125,25 @@ export const packageIosCommand = curryOptions(
|
|
|
97
125
|
.action(
|
|
98
126
|
actionRunner(async (cliOptions: PackageIosOptions) => {
|
|
99
127
|
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'ios');
|
|
128
|
+
const projectRoot = findProjectRoot();
|
|
129
|
+
|
|
130
|
+
await runExpoPrebuildIfNeeded({ projectRoot, platform: 'ios' });
|
|
100
131
|
|
|
101
|
-
const {
|
|
132
|
+
const { platformConfig, userConfig } = getProjectInfo('ios');
|
|
102
133
|
|
|
103
134
|
const prebuiltRNCoreSupport = supportsPrebuiltRNCore({ projectRoot });
|
|
135
|
+
const prebuiltExpoSupport = supportsPrebuiltExpo({ projectRoot });
|
|
104
136
|
|
|
105
137
|
// version-aware default when the flag is omitted (see ios.mdx "React Native Prebuilts")
|
|
106
138
|
options.usePrebuiltRnCore ??= prebuiltRNCoreSupport.supported
|
|
107
139
|
? prebuiltRNCoreSupport.enabledByDefault
|
|
108
140
|
: false;
|
|
109
141
|
|
|
142
|
+
// version-aware default when the flag is omitted
|
|
143
|
+
options.usePrebuiltExpo ??= prebuiltExpoSupport.supported
|
|
144
|
+
? prebuiltExpoSupport.enabledByDefault
|
|
145
|
+
: false;
|
|
146
|
+
|
|
110
147
|
if (prebuiltRNCoreSupport) {
|
|
111
148
|
logger.info(
|
|
112
149
|
`${options.usePrebuiltRnCore ? 'Using' : 'Not using'} prebuilt RN core`
|
|
@@ -126,7 +163,9 @@ export const packageIosCommand = curryOptions(
|
|
|
126
163
|
throw new RockError(prebuiltRNCoreSupport.reason);
|
|
127
164
|
}
|
|
128
165
|
|
|
129
|
-
|
|
166
|
+
if (options.usePrebuiltExpo && !prebuiltExpoSupport.supported) {
|
|
167
|
+
throw new RockError(prebuiltExpoSupport.reason);
|
|
168
|
+
}
|
|
130
169
|
|
|
131
170
|
if (!userConfig.project.ios) {
|
|
132
171
|
throw new Error('iOS project not found.');
|
|
@@ -182,6 +221,12 @@ export const packageIosCommand = curryOptions(
|
|
|
182
221
|
platformConfig
|
|
183
222
|
);
|
|
184
223
|
|
|
224
|
+
emitExpoSupportXcframeworks({
|
|
225
|
+
projectRoot,
|
|
226
|
+
packageDir,
|
|
227
|
+
usePrebuiltExpo: options.usePrebuiltExpo,
|
|
228
|
+
});
|
|
229
|
+
|
|
185
230
|
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
|
|
186
231
|
const { frameworkName, resolution, candidates } =
|
|
187
232
|
resolvePackagedFrameworkName({
|
|
@@ -242,6 +287,7 @@ export const packageIosCommand = curryOptions(
|
|
|
242
287
|
packageDir,
|
|
243
288
|
'ReactBrownfield.xcframework'
|
|
244
289
|
);
|
|
290
|
+
|
|
245
291
|
if (fs.existsSync(reactBrownfieldXcframeworkPath)) {
|
|
246
292
|
// Strip the binary from ReactBrownfield.xcframework to make it interface-only.
|
|
247
293
|
// This avoids duplicate symbols when consumer apps embed both BrownfieldLib
|
|
@@ -313,10 +359,13 @@ export const packageIosCommand = curryOptions(
|
|
|
313
359
|
);
|
|
314
360
|
}
|
|
315
361
|
|
|
362
|
+
removeCodeSignatureArtifacts(packageDir);
|
|
363
|
+
|
|
316
364
|
if (options.addSpmPackage) {
|
|
317
365
|
const { packageManifestPath } = createLocalSpmPackage({
|
|
318
366
|
packageDir,
|
|
319
367
|
frameworkName: frameworkName ?? undefined,
|
|
368
|
+
usePrebuiltExpo: options.usePrebuiltExpo,
|
|
320
369
|
});
|
|
321
370
|
|
|
322
371
|
logger.success(
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ExampleUsage,
|
|
13
13
|
} from '../../shared/index.js';
|
|
14
14
|
import { getProjectInfo } from '../utils/project.js';
|
|
15
|
+
import { findProjectRoot } from '../utils/paths.js';
|
|
15
16
|
import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
|
|
16
17
|
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
|
|
17
18
|
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
|
|
@@ -25,13 +26,15 @@ export const publishAndroidCommand = curryOptions(
|
|
|
25
26
|
).action(
|
|
26
27
|
actionRunner(async (cliOptions: PublishLocalAarFlags) => {
|
|
27
28
|
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'android');
|
|
29
|
+
const projectRoot = findProjectRoot();
|
|
28
30
|
|
|
29
|
-
const { projectRoot, platformConfig } = getProjectInfo('android');
|
|
30
31
|
await runExpoPrebuildIfNeeded({
|
|
31
32
|
projectRoot,
|
|
32
33
|
platform: 'android',
|
|
33
34
|
});
|
|
34
35
|
|
|
36
|
+
const { platformConfig } = getProjectInfo('android');
|
|
37
|
+
|
|
35
38
|
await runBrownieCodegenIfApplicable(projectRoot, 'kotlin');
|
|
36
39
|
await runNavigationCodegenIfApplicable(projectRoot);
|
|
37
40
|
|
|
@@ -5,10 +5,15 @@ import {
|
|
|
5
5
|
prepareLocalSpmArtifacts,
|
|
6
6
|
SPM_ARTIFACTS_DIR_NAME,
|
|
7
7
|
} from './prepareLocalSpmArtifacts.js';
|
|
8
|
+
import {
|
|
9
|
+
ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES,
|
|
10
|
+
getExpoSupportXcframeworkNames,
|
|
11
|
+
} from './emitExpoSupportXcframeworks.js';
|
|
8
12
|
|
|
9
13
|
type CreateLocalSpmPackageOptions = {
|
|
10
14
|
packageDir: string;
|
|
11
15
|
frameworkName?: string;
|
|
16
|
+
usePrebuiltExpo?: boolean;
|
|
12
17
|
};
|
|
13
18
|
|
|
14
19
|
type CreateLocalSpmPackageResult = {
|
|
@@ -23,6 +28,7 @@ const RESERVED_FRAMEWORK_NAMES = new Set([
|
|
|
23
28
|
'BrownfieldNavigation',
|
|
24
29
|
'React',
|
|
25
30
|
'ReactNativeDependencies',
|
|
31
|
+
...ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES,
|
|
26
32
|
]);
|
|
27
33
|
|
|
28
34
|
function requireXcframework(packageDir: string, name: string) {
|
|
@@ -130,7 +136,9 @@ function renderReadme({
|
|
|
130
136
|
libraryName: string;
|
|
131
137
|
targetNames: string[];
|
|
132
138
|
}) {
|
|
133
|
-
const frameworks = targetNames
|
|
139
|
+
const frameworks = targetNames
|
|
140
|
+
.map((targetName) => `- \`${targetName}\``)
|
|
141
|
+
.join('\n');
|
|
134
142
|
|
|
135
143
|
return `# ${packageName}
|
|
136
144
|
|
|
@@ -167,17 +175,23 @@ This folder is generated by \`brownfield package:ios --add-spm-package\`. Re-run
|
|
|
167
175
|
export function createLocalSpmPackage({
|
|
168
176
|
packageDir,
|
|
169
177
|
frameworkName,
|
|
178
|
+
usePrebuiltExpo = true,
|
|
170
179
|
}: CreateLocalSpmPackageOptions): CreateLocalSpmPackageResult {
|
|
171
180
|
const resolvedFrameworkName = resolveAppFrameworkName(
|
|
172
181
|
packageDir,
|
|
173
182
|
frameworkName
|
|
174
183
|
);
|
|
184
|
+
const expoSupportXcframeworkNames =
|
|
185
|
+
getExpoSupportXcframeworkNames(usePrebuiltExpo);
|
|
175
186
|
const targetNames = [
|
|
176
187
|
resolvedFrameworkName,
|
|
177
188
|
requireHermesXcframework(packageDir),
|
|
178
189
|
requireXcframework(packageDir, 'ReactBrownfield'),
|
|
179
190
|
optionalXcframework(packageDir, 'Brownie'),
|
|
180
191
|
optionalXcframework(packageDir, 'BrownfieldNavigation'),
|
|
192
|
+
...expoSupportXcframeworkNames.map((frameworkName) =>
|
|
193
|
+
optionalXcframework(packageDir, frameworkName)
|
|
194
|
+
),
|
|
181
195
|
optionalXcframework(packageDir, 'React'),
|
|
182
196
|
optionalXcframework(packageDir, 'ReactNativeDependencies'),
|
|
183
197
|
].filter((targetName): targetName is string => targetName !== null);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { RockError } from '@rock-js/tools';
|
|
5
|
+
|
|
6
|
+
import { normalizeCopiedXcframework } from './normalizeCopiedXcframework.js';
|
|
7
|
+
import { getExpoSdkMajor, isExpoProject } from './project.js';
|
|
8
|
+
|
|
9
|
+
import { MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO } from './supportsPrebuiltExpo.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Swift support XCFrameworks
|
|
13
|
+
*
|
|
14
|
+
* These are required for the Expo Image module to work.
|
|
15
|
+
*
|
|
16
|
+
* TODO: This needs investigation and maybe a discussion with the Expo team.
|
|
17
|
+
*/
|
|
18
|
+
const SWIFT_SUPPORT_XCFRAMEWORK_NAMES = [
|
|
19
|
+
'SDWebImage',
|
|
20
|
+
'SDWebImageSVGCoder',
|
|
21
|
+
'SDWebImageWebPCoder',
|
|
22
|
+
'SDWebImageAVIFCoder',
|
|
23
|
+
'libavif',
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
export const EXPO_SUPPORT_XCFRAMEWORK_NAMES = [
|
|
27
|
+
'ExpoModulesJSI',
|
|
28
|
+
'ExpoFileSystem',
|
|
29
|
+
'ExpoFont',
|
|
30
|
+
'ExpoModulesCore',
|
|
31
|
+
'ExpoImage',
|
|
32
|
+
'ExpoModulesWorklets'
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export const ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES = [
|
|
36
|
+
...EXPO_SUPPORT_XCFRAMEWORK_NAMES,
|
|
37
|
+
...SWIFT_SUPPORT_XCFRAMEWORK_NAMES,
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
export function getExpoSupportXcframeworkNames(usePrebuiltExpo: boolean) {
|
|
41
|
+
return usePrebuiltExpo
|
|
42
|
+
? ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES
|
|
43
|
+
: (['ExpoModulesJSI'] as const);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type SwiftSupportXcframeworkName =
|
|
47
|
+
(typeof SWIFT_SUPPORT_XCFRAMEWORK_NAMES)[number];
|
|
48
|
+
|
|
49
|
+
type ExpoSupportXcframeworkName =
|
|
50
|
+
(typeof EXPO_SUPPORT_XCFRAMEWORK_NAMES)[number];
|
|
51
|
+
|
|
52
|
+
function resolveExpoFrameworkSourcePath(
|
|
53
|
+
projectRoot: string,
|
|
54
|
+
frameworkName: SwiftSupportXcframeworkName | ExpoSupportXcframeworkName
|
|
55
|
+
) {
|
|
56
|
+
if (frameworkName === 'ExpoModulesJSI') {
|
|
57
|
+
return path.join(
|
|
58
|
+
projectRoot,
|
|
59
|
+
'node_modules',
|
|
60
|
+
'expo-modules-jsi',
|
|
61
|
+
'apple',
|
|
62
|
+
'Products',
|
|
63
|
+
'ExpoModulesJSI.xcframework'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (SWIFT_SUPPORT_XCFRAMEWORK_NAMES.includes(frameworkName as SwiftSupportXcframeworkName)) {
|
|
68
|
+
return path.join(
|
|
69
|
+
projectRoot,
|
|
70
|
+
'ios',
|
|
71
|
+
'Pods',
|
|
72
|
+
'ExpoImage',
|
|
73
|
+
`${frameworkName}.xcframework`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (EXPO_SUPPORT_XCFRAMEWORK_NAMES.includes(frameworkName as ExpoSupportXcframeworkName)) {
|
|
78
|
+
return path.join(
|
|
79
|
+
projectRoot,
|
|
80
|
+
'ios',
|
|
81
|
+
'Pods',
|
|
82
|
+
frameworkName,
|
|
83
|
+
`${frameworkName}.xcframework`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new RockError(`Unsupported Expo XCFramework: ${frameworkName}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function emitExpoSupportXcframeworks({
|
|
91
|
+
projectRoot,
|
|
92
|
+
packageDir,
|
|
93
|
+
usePrebuiltExpo = true,
|
|
94
|
+
}: {
|
|
95
|
+
projectRoot: string;
|
|
96
|
+
packageDir: string;
|
|
97
|
+
usePrebuiltExpo?: boolean;
|
|
98
|
+
}) {
|
|
99
|
+
if (!isExpoProject(projectRoot)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const expoSdkMajor = getExpoSdkMajor(projectRoot);
|
|
104
|
+
if (
|
|
105
|
+
expoSdkMajor === null ||
|
|
106
|
+
expoSdkMajor < MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO
|
|
107
|
+
) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const frameworkName of getExpoSupportXcframeworkNames(usePrebuiltExpo)) {
|
|
112
|
+
const sourcePath = resolveExpoFrameworkSourcePath(
|
|
113
|
+
projectRoot,
|
|
114
|
+
frameworkName
|
|
115
|
+
);
|
|
116
|
+
if (!fs.existsSync(sourcePath)) {
|
|
117
|
+
throw new RockError(
|
|
118
|
+
`Expected Expo SDK ${MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO}+ XCFramework not found: ${frameworkName}.xcframework at ${path.relative(projectRoot, sourcePath)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const destinationPath = path.join(
|
|
123
|
+
packageDir,
|
|
124
|
+
`${frameworkName}.xcframework`
|
|
125
|
+
);
|
|
126
|
+
fs.rmSync(destinationPath, { recursive: true, force: true });
|
|
127
|
+
fs.cpSync(sourcePath, destinationPath, { recursive: true });
|
|
128
|
+
normalizeCopiedXcframework(destinationPath);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
export function removeCodeSignatureArtifacts(dirPath: string) {
|
|
6
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
7
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
8
|
+
|
|
9
|
+
if (entry.isDirectory()) {
|
|
10
|
+
if (entry.name === '_CodeSignature') {
|
|
11
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
removeCodeSignatureArtifacts(entryPath);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (entry.name === 'CodeResources') {
|
|
20
|
+
fs.rmSync(entryPath, { force: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveFrameworkExecutablePath(
|
|
26
|
+
frameworkDir: string,
|
|
27
|
+
frameworkName: string
|
|
28
|
+
) {
|
|
29
|
+
const directExecutablePath = path.join(frameworkDir, frameworkName);
|
|
30
|
+
if (fs.existsSync(directExecutablePath)) {
|
|
31
|
+
return directExecutablePath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const versionedExecutablePath = path.join(
|
|
35
|
+
frameworkDir,
|
|
36
|
+
'Versions',
|
|
37
|
+
'Current',
|
|
38
|
+
frameworkName
|
|
39
|
+
);
|
|
40
|
+
if (fs.existsSync(versionedExecutablePath)) {
|
|
41
|
+
return versionedExecutablePath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function removeExecutableSignature(executablePath: string) {
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('codesign', ['--remove-signature', executablePath], {
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (
|
|
54
|
+
error instanceof Error &&
|
|
55
|
+
error.message.includes('code object is not signed at all')
|
|
56
|
+
) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeCopiedXcframework(xcframeworkPath: string) {
|
|
65
|
+
removeCodeSignatureArtifacts(xcframeworkPath);
|
|
66
|
+
|
|
67
|
+
for (const sliceName of fs.readdirSync(xcframeworkPath)) {
|
|
68
|
+
const slicePath = path.join(xcframeworkPath, sliceName);
|
|
69
|
+
if (!fs.statSync(slicePath).isDirectory()) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const entry of fs.readdirSync(slicePath)) {
|
|
74
|
+
if (!entry.endsWith('.framework')) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const frameworkName = path.basename(entry, '.framework');
|
|
79
|
+
const frameworkDir = path.join(slicePath, entry);
|
|
80
|
+
const executablePath = resolveFrameworkExecutablePath(
|
|
81
|
+
frameworkDir,
|
|
82
|
+
frameworkName
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (executablePath) {
|
|
86
|
+
removeExecutableSignature(executablePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
import { normalizeCopiedXcframework } from './normalizeCopiedXcframework.js';
|
|
4
5
|
|
|
5
6
|
type PrepareLocalSpmArtifactsOptions = {
|
|
6
7
|
packageDir: string;
|
|
@@ -9,93 +10,6 @@ type PrepareLocalSpmArtifactsOptions = {
|
|
|
9
10
|
|
|
10
11
|
export const SPM_ARTIFACTS_DIR_NAME = 'spm-artifacts';
|
|
11
12
|
|
|
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
13
|
export function prepareLocalSpmArtifacts({
|
|
100
14
|
packageDir,
|
|
101
15
|
targetNames,
|
|
@@ -107,10 +21,13 @@ export function prepareLocalSpmArtifacts({
|
|
|
107
21
|
|
|
108
22
|
for (const targetName of targetNames) {
|
|
109
23
|
const sourcePath = path.join(packageDir, `${targetName}.xcframework`);
|
|
110
|
-
const destinationPath = path.join(
|
|
24
|
+
const destinationPath = path.join(
|
|
25
|
+
spmArtifactsDir,
|
|
26
|
+
`${targetName}.xcframework`
|
|
27
|
+
);
|
|
111
28
|
|
|
112
29
|
fs.renameSync(sourcePath, destinationPath);
|
|
113
|
-
|
|
30
|
+
normalizeCopiedXcframework(destinationPath);
|
|
114
31
|
}
|
|
115
32
|
|
|
116
33
|
return spmArtifactsDir;
|
|
@@ -21,15 +21,46 @@ const cliConfig: typeof cliConfigImport =
|
|
|
21
21
|
: // @ts-expect-error: interop default
|
|
22
22
|
cliConfigImport.default;
|
|
23
23
|
|
|
24
|
+
function hasExpoAppConfig(projectRoot: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
fs.existsSync(path.join(projectRoot, 'app.json')) ||
|
|
27
|
+
fs.existsSync(path.join(projectRoot, 'app.config.js')) ||
|
|
28
|
+
fs.existsSync(path.join(projectRoot, 'app.config.ts')) ||
|
|
29
|
+
fs.existsSync(path.join(projectRoot, 'app.config.mjs'))
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function projectDependsOnExpo(projectRoot: string): boolean {
|
|
34
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const packageJson = JSON.parse(
|
|
41
|
+
fs.readFileSync(packageJsonPath, 'utf-8')
|
|
42
|
+
) as Record<string, Record<string, string>>;
|
|
43
|
+
|
|
44
|
+
return ['dependencies', 'peerDependencies', 'devDependencies'].some(
|
|
45
|
+
(key) => packageJson[key]?.expo
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
/**
|
|
25
50
|
* Gets the Expo config if the project is an Expo project
|
|
26
51
|
* @param projectRoot The project root path
|
|
27
52
|
* @returns The Expo config if the project is an Expo project, null otherwise
|
|
28
53
|
*/
|
|
29
54
|
export function getExpoConfigIfIsExpo(projectRoot: string) {
|
|
55
|
+
const hasAppConfig = hasExpoAppConfig(projectRoot);
|
|
56
|
+
|
|
30
57
|
try {
|
|
31
58
|
return getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
32
|
-
} catch {
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (hasAppConfig) {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
return null;
|
|
34
65
|
}
|
|
35
66
|
}
|
|
@@ -41,19 +72,7 @@ export function getExpoConfigIfIsExpo(projectRoot: string) {
|
|
|
41
72
|
* @returns Whether the project is an Expo project
|
|
42
73
|
*/
|
|
43
74
|
export function isExpoProject(projectRoot: string): boolean {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// additionally, it is needed to check if the project depends on Expo packages explicitly
|
|
47
|
-
// to prevent false positives in a monorepo setup
|
|
48
|
-
const rnProjectRoot = findProjectRoot();
|
|
49
|
-
const packageJsonPath = path.join(rnProjectRoot, 'package.json');
|
|
50
|
-
const dependsOnExpo =
|
|
51
|
-
fs.existsSync(packageJsonPath) &&
|
|
52
|
-
['dependencies', 'peerDependencies', 'devDependencies'].some(
|
|
53
|
-
(key) => JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))[key]?.expo
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
return hasExpoConfig && dependsOnExpo;
|
|
75
|
+
return hasExpoAppConfig(projectRoot) && projectDependsOnExpo(projectRoot);
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
export function getExpoSdkMajor(projectRoot: string): number | null {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getExpoSdkMajor, isExpoProject } from './project.js';
|
|
2
|
+
|
|
3
|
+
export const MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO = 56;
|
|
4
|
+
|
|
5
|
+
export type PrebuiltExpoSupportResult =
|
|
6
|
+
| { supported: true; enabledByDefault: boolean; reason?: never }
|
|
7
|
+
| { supported: false; enabledByDefault?: never; reason: string };
|
|
8
|
+
|
|
9
|
+
export function supportsPrebuiltExpo({
|
|
10
|
+
projectRoot,
|
|
11
|
+
}: {
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
}): PrebuiltExpoSupportResult {
|
|
14
|
+
if (!isExpoProject(projectRoot)) {
|
|
15
|
+
return { supported: true, enabledByDefault: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const expoSdkMajor = getExpoSdkMajor(projectRoot);
|
|
19
|
+
if (
|
|
20
|
+
expoSdkMajor === null ||
|
|
21
|
+
expoSdkMajor < MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO
|
|
22
|
+
) {
|
|
23
|
+
const sdkLabel = expoSdkMajor === null ? 'unknown' : String(expoSdkMajor);
|
|
24
|
+
return {
|
|
25
|
+
supported: false,
|
|
26
|
+
reason: `--use-prebuilt-expo is unsupported in Expo SDK ${sdkLabel}: packaging brownfield with Expo prebuilts requires Expo SDK ${MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO} or newer.`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
supported: true,
|
|
32
|
+
enabledByDefault: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
package/src/brownie/config.ts
CHANGED
|
@@ -108,7 +108,7 @@ export function resolveBrownieCodegenConfig({
|
|
|
108
108
|
const legacyConfig = hasLegacyConfig(projectRoot)
|
|
109
109
|
? loadConfig(projectRoot)
|
|
110
110
|
: undefined;
|
|
111
|
-
const brownfieldBrownie = loadBrownfieldConfig(projectRoot)
|
|
111
|
+
const brownfieldBrownie = loadBrownfieldConfig(projectRoot)?.brownie;
|
|
112
112
|
|
|
113
113
|
if (legacyConfig !== undefined && (brownie ?? brownfieldBrownie)) {
|
|
114
114
|
throw new Error(LEGACY_AND_NEW_BROWNIE_CONFIG_ERROR);
|