@callstack/brownfield-cli 6.0.0 → 6.0.1
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/packageIos.d.ts +2 -0
- package/dist/brownfield/commands/packageIos.d.ts.map +1 -1
- package/dist/brownfield/commands/packageIos.js +102 -18
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.d.ts +4 -1
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.d.ts.map +1 -1
- package/dist/brownfield/utils/copyDebugBundleToSimulatorSlice.js +6 -1
- package/dist/brownfield/utils/destinationSdks.d.ts +36 -0
- package/dist/brownfield/utils/destinationSdks.d.ts.map +1 -0
- package/dist/brownfield/utils/destinationSdks.js +70 -0
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.d.ts +9 -1
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.d.ts.map +1 -1
- package/dist/brownfield/utils/emitExpoSupportXcframeworks.js +27 -5
- package/dist/brownfield/utils/expo.d.ts +7 -0
- package/dist/brownfield/utils/expo.d.ts.map +1 -1
- package/dist/brownfield/utils/expo.js +28 -1
- package/dist/brownfield/utils/project.d.ts +14 -0
- package/dist/brownfield/utils/project.d.ts.map +1 -1
- package/dist/brownfield/utils/project.js +47 -0
- package/dist/brownfield/utils/resolvePackagedFrameworkName.d.ts +4 -1
- package/dist/brownfield/utils/resolvePackagedFrameworkName.d.ts.map +1 -1
- package/dist/brownfield/utils/resolvePackagedFrameworkName.js +7 -3
- package/dist/brownfield/utils/supportsPrebuiltExpo.d.ts +20 -0
- package/dist/brownfield/utils/supportsPrebuiltExpo.d.ts.map +1 -1
- package/dist/brownfield/utils/supportsPrebuiltExpo.js +68 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/package.json +6 -6
- package/src/brownfield/commands/packageIos.ts +143 -49
- package/src/brownfield/utils/copyDebugBundleToSimulatorSlice.ts +10 -0
- package/src/brownfield/utils/destinationSdks.ts +122 -0
- package/src/brownfield/utils/emitExpoSupportXcframeworks.ts +48 -4
- package/src/brownfield/utils/expo.ts +38 -1
- package/src/brownfield/utils/project.ts +65 -0
- package/src/brownfield/utils/resolvePackagedFrameworkName.ts +16 -5
- package/src/brownfield/utils/supportsPrebuiltExpo.ts +98 -2
- package/src/index.ts +12 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AppleSdk } from './destinationSdks.js';
|
|
1
2
|
type Resolution = 'explicit' | 'detected' | 'not_found' | 'ambiguous';
|
|
2
3
|
export interface ResolvePackagedFrameworkNameResult {
|
|
3
4
|
frameworkName: string | null;
|
|
@@ -8,7 +9,9 @@ interface ResolvePackagedFrameworkNameOptions {
|
|
|
8
9
|
explicitScheme?: string;
|
|
9
10
|
productsPath: string;
|
|
10
11
|
configuration: string;
|
|
12
|
+
/** Slices the build produced; defaults to both. */
|
|
13
|
+
sdks?: AppleSdk[];
|
|
11
14
|
}
|
|
12
|
-
export declare function resolvePackagedFrameworkName({ explicitScheme, productsPath, configuration, }: ResolvePackagedFrameworkNameOptions): ResolvePackagedFrameworkNameResult;
|
|
15
|
+
export declare function resolvePackagedFrameworkName({ explicitScheme, productsPath, configuration, sdks, }: ResolvePackagedFrameworkNameOptions): ResolvePackagedFrameworkNameResult;
|
|
13
16
|
export {};
|
|
14
17
|
//# sourceMappingURL=resolvePackagedFrameworkName.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolvePackagedFrameworkName.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/resolvePackagedFrameworkName.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,kCAAkC;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,UAAU,mCAAmC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"resolvePackagedFrameworkName.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/resolvePackagedFrameworkName.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErE,KAAK,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,kCAAkC;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,UAAU,mCAAmC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC;CACnB;AA4CD,wBAAgB,4BAA4B,CAAC,EAC3C,cAAc,EACd,YAAY,EACZ,aAAa,EACb,IAAqB,GACtB,EAAE,mCAAmC,GAAG,kCAAkC,CAwC1E"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ALL_APPLE_SDKS } from './destinationSdks.js';
|
|
3
4
|
function collectFrameworkCandidates(configurationProductsPath) {
|
|
4
5
|
if (!fs.existsSync(configurationProductsPath)) {
|
|
5
6
|
return [];
|
|
@@ -31,15 +32,18 @@ function collectFrameworkCandidates(configurationProductsPath) {
|
|
|
31
32
|
}
|
|
32
33
|
return [...discoveredFrameworks].sort();
|
|
33
34
|
}
|
|
34
|
-
export function resolvePackagedFrameworkName({ explicitScheme, productsPath, configuration, }) {
|
|
35
|
+
export function resolvePackagedFrameworkName({ explicitScheme, productsPath, configuration, sdks = ALL_APPLE_SDKS, }) {
|
|
35
36
|
if (explicitScheme) {
|
|
36
37
|
return {
|
|
37
38
|
frameworkName: explicitScheme,
|
|
38
39
|
resolution: 'explicit',
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
// the JS bundle is emitted per slice, so scan every slice that was built rather
|
|
43
|
+
// than assuming the device one exists (`--destination simulator` skips it)
|
|
44
|
+
const candidates = [
|
|
45
|
+
...new Set(sdks.flatMap((sdk) => collectFrameworkCandidates(path.join(productsPath, `${configuration}-${sdk}`)))),
|
|
46
|
+
].sort();
|
|
43
47
|
if (candidates.length === 1) {
|
|
44
48
|
return {
|
|
45
49
|
frameworkName: candidates[0] ?? null,
|
|
@@ -2,13 +2,33 @@ export declare const MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO = 56;
|
|
|
2
2
|
export type PrebuiltExpoSupportResult = {
|
|
3
3
|
supported: true;
|
|
4
4
|
enabledByDefault: boolean;
|
|
5
|
+
/** Why prebuilts are off by default even though they are supported. */
|
|
6
|
+
defaultDisabledReason?: string;
|
|
5
7
|
reason?: never;
|
|
6
8
|
} | {
|
|
7
9
|
supported: false;
|
|
8
10
|
enabledByDefault?: never;
|
|
9
11
|
reason: string;
|
|
10
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* The React Native version the installed Expo SDK pins. Expo ships its
|
|
15
|
+
* precompiled modules built against this version.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getExpoReactNativeVersion(projectRoot: string): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Describes a React Native version the Expo prebuilts were not built for, or
|
|
20
|
+
* returns null when they match. Prebuilt Expo modules link against React
|
|
21
|
+
* Native's C++ API, which changes between minor versions, so a mismatch
|
|
22
|
+
* crashes at runtime.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getPrebuiltExpoReactNativeMismatch(projectRoot: string): string | null;
|
|
11
25
|
export declare function supportsPrebuiltExpo({ projectRoot, }: {
|
|
12
26
|
projectRoot: string;
|
|
13
27
|
}): PrebuiltExpoSupportResult;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the project's Podfile turns Expo's precompiled modules off. Expo
|
|
30
|
+
* Podfiles force `EXPO_USE_PRECOMPILED_MODULES` to '0' when
|
|
31
|
+
* `ios/Podfile.properties.json` sets it to "false", whatever the environment says.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isPrecompiledExpoDisabledByPodfile(projectRoot: string): boolean;
|
|
14
34
|
//# sourceMappingURL=supportsPrebuiltExpo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supportsPrebuiltExpo.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/supportsPrebuiltExpo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"supportsPrebuiltExpo.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/supportsPrebuiltExpo.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,oCAAoC,KAAK,CAAC;AAEvD,MAAM,MAAM,yBAAyB,GACjC;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,GACD;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMnE;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc5E;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CAYf;AAED,wBAAgB,oBAAoB,CAAC,EACnC,WAAW,GACZ,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,yBAAyB,CAsC5B;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,GAClB,OAAO,CAYT"}
|
|
@@ -1,5 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getReactNativeVersion } from '@rock-js/tools';
|
|
5
|
+
import { getExpoIosUsePrecompiledModules, getExpoSdkMajor, isExpoProject } from './project.js';
|
|
2
6
|
export const MIN_EXPO_SDK_MAJOR_FOR_PREBUILT_EXPO = 56;
|
|
7
|
+
function majorMinor(version) {
|
|
8
|
+
return version.split('.').slice(0, 2).join('.');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The React Native version the installed Expo SDK pins. Expo ships its
|
|
12
|
+
* precompiled modules built against this version.
|
|
13
|
+
*/
|
|
14
|
+
export function getExpoReactNativeVersion(projectRoot) {
|
|
15
|
+
try {
|
|
16
|
+
const require = createRequire(path.join(projectRoot, 'package.json'));
|
|
17
|
+
const expoPackageJson = require.resolve('expo/package.json');
|
|
18
|
+
const bundledNativeModules = JSON.parse(fs.readFileSync(path.join(path.dirname(expoPackageJson), 'bundledNativeModules.json'), 'utf8'));
|
|
19
|
+
return bundledNativeModules['react-native'] ?? null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Describes a React Native version the Expo prebuilts were not built for, or
|
|
27
|
+
* returns null when they match. Prebuilt Expo modules link against React
|
|
28
|
+
* Native's C++ API, which changes between minor versions, so a mismatch
|
|
29
|
+
* crashes at runtime.
|
|
30
|
+
*/
|
|
31
|
+
export function getPrebuiltExpoReactNativeMismatch(projectRoot) {
|
|
32
|
+
const expected = getExpoReactNativeVersion(projectRoot);
|
|
33
|
+
const found = getReactNativeVersion(projectRoot);
|
|
34
|
+
if (!expected || found === 'unknown') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (majorMinor(expected) === majorMinor(found)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return `Expo's precompiled modules are built for React Native ${majorMinor(expected)}, but this project uses ${found}.`;
|
|
41
|
+
}
|
|
3
42
|
export function supportsPrebuiltExpo({ projectRoot, }) {
|
|
4
43
|
if (!isExpoProject(projectRoot)) {
|
|
5
44
|
return { supported: true, enabledByDefault: false };
|
|
@@ -13,8 +52,36 @@ export function supportsPrebuiltExpo({ projectRoot, }) {
|
|
|
13
52
|
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.`,
|
|
14
53
|
};
|
|
15
54
|
}
|
|
55
|
+
const reactNativeMismatch = getPrebuiltExpoReactNativeMismatch(projectRoot);
|
|
56
|
+
if (reactNativeMismatch) {
|
|
57
|
+
return {
|
|
58
|
+
supported: true,
|
|
59
|
+
enabledByDefault: false,
|
|
60
|
+
defaultDisabledReason: reactNativeMismatch,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (getExpoIosUsePrecompiledModules(projectRoot) === false) {
|
|
64
|
+
return {
|
|
65
|
+
supported: false,
|
|
66
|
+
reason: "--use-prebuilt-expo is unsupported: expo-build-properties sets ios.usePrecompiledModules to false in your Expo config, so prebuilt Expo XCFrameworks are never produced. Set it to true (or remove it) and run 'pod install', or pass --use-prebuilt-expo false to build Expo modules from source.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
16
69
|
return {
|
|
17
70
|
supported: true,
|
|
18
71
|
enabledByDefault: true,
|
|
19
72
|
};
|
|
20
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether the project's Podfile turns Expo's precompiled modules off. Expo
|
|
76
|
+
* Podfiles force `EXPO_USE_PRECOMPILED_MODULES` to '0' when
|
|
77
|
+
* `ios/Podfile.properties.json` sets it to "false", whatever the environment says.
|
|
78
|
+
*/
|
|
79
|
+
export function isPrecompiledExpoDisabledByPodfile(projectRoot) {
|
|
80
|
+
try {
|
|
81
|
+
const properties = JSON.parse(fs.readFileSync(path.join(projectRoot, 'ios', 'Podfile.properties.json'), 'utf8'));
|
|
82
|
+
return properties.EXPO_USE_PRECOMPILED_MODULES === 'false';
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6FA,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAM3C"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,16 @@ import { ExampleUsage } from './shared/index.js';
|
|
|
5
5
|
import brownfieldCommands, { groupName as brownfieldCommandsGroupName, } from './brownfield/index.js';
|
|
6
6
|
import brownieCommands, { groupName as brownieCommandsGroupName, } from './brownie/index.js';
|
|
7
7
|
import navigationCommands, { groupName as navigationCommandsGroupName, } from './navigation/index.js';
|
|
8
|
+
// CocoaPods (spawned by Rock) raises Encoding::CompatibilityError under a non-UTF-8
|
|
9
|
+
// locale. Ruby resolves the character encoding as LC_ALL > LC_CTYPE > LANG, so check
|
|
10
|
+
// them in that precedence. Default to UTF-8 only when the effective value is missing
|
|
11
|
+
// or non-UTF-8.
|
|
12
|
+
const inheritedCharset = process.env.LC_ALL || process.env.LC_CTYPE || process.env.LANG || '';
|
|
13
|
+
if (!/utf-?8/i.test(inheritedCharset)) {
|
|
14
|
+
process.env.LANG = 'en_US.UTF-8';
|
|
15
|
+
process.env.LC_CTYPE = 'en_US.UTF-8';
|
|
16
|
+
process.env.LC_ALL = 'en_US.UTF-8';
|
|
17
|
+
}
|
|
8
18
|
const program = new Command();
|
|
9
19
|
program
|
|
10
20
|
.name(styleText('magenta', 'brownfield'))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callstack/brownfield-cli",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Artur Morys-Magiera <artus9033@gmail.com>",
|
|
6
6
|
"bin": {
|
|
@@ -88,11 +88,11 @@
|
|
|
88
88
|
"@expo/config": "^12.0.13",
|
|
89
89
|
"@react-native-community/cli-config": "^20.0.0",
|
|
90
90
|
"@react-native-community/cli-config-android": "^20.0.0",
|
|
91
|
-
"@rock-js/platform-android": "^0.
|
|
92
|
-
"@rock-js/platform-apple-helpers": "^0.
|
|
93
|
-
"@rock-js/plugin-brownfield-android": "^0.
|
|
94
|
-
"@rock-js/plugin-brownfield-ios": "^0.
|
|
95
|
-
"@rock-js/tools": "^0.
|
|
91
|
+
"@rock-js/platform-android": "^0.15.4",
|
|
92
|
+
"@rock-js/platform-apple-helpers": "^0.15.4",
|
|
93
|
+
"@rock-js/plugin-brownfield-android": "^0.15.4",
|
|
94
|
+
"@rock-js/plugin-brownfield-ios": "^0.15.4",
|
|
95
|
+
"@rock-js/tools": "^0.15.4",
|
|
96
96
|
"ajv": "^8.20.0",
|
|
97
97
|
"commander": "^14.0.3",
|
|
98
98
|
"quicktype-core": "^23.2.6",
|
|
@@ -18,8 +18,11 @@ import { Command, Option } from 'commander';
|
|
|
18
18
|
|
|
19
19
|
import { runExpoPrebuildIfNeeded } from '../utils/expo.js';
|
|
20
20
|
import { findProjectRoot } from '../utils/paths.js';
|
|
21
|
-
import { getProjectInfo } from '../utils/project.js';
|
|
22
|
-
import {
|
|
21
|
+
import { getProjectInfo, isExpoProject } from '../utils/project.js';
|
|
22
|
+
import {
|
|
23
|
+
isPrecompiledExpoDisabledByPodfile,
|
|
24
|
+
supportsPrebuiltExpo,
|
|
25
|
+
} from '../utils/supportsPrebuiltExpo.js';
|
|
23
26
|
import { supportsPrebuiltRNCore } from '../utils/supportsPrebuiltRNCore.js';
|
|
24
27
|
import {
|
|
25
28
|
actionRunner,
|
|
@@ -29,6 +32,10 @@ import {
|
|
|
29
32
|
import { runBrownieCodegenIfApplicable } from '../../brownie/helpers/runBrownieCodegenIfApplicable.js';
|
|
30
33
|
import { runNavigationCodegenIfApplicable } from '../../navigation/helpers/runNavigationCodegenIfApplicable.js';
|
|
31
34
|
import { copyDebugBundleToSimulatorSlice } from '../utils/copyDebugBundleToSimulatorSlice.js';
|
|
35
|
+
import {
|
|
36
|
+
collectFrameworkPaths,
|
|
37
|
+
resolveDestinationSdks,
|
|
38
|
+
} from '../utils/destinationSdks.js';
|
|
32
39
|
import { resolvePackagedFrameworkName } from '../utils/resolvePackagedFrameworkName.js';
|
|
33
40
|
import { stripFrameworkBinary } from '../utils/stripFrameworkBinary.js';
|
|
34
41
|
import type { PackageIosOptions } from '../../types.js';
|
|
@@ -75,9 +82,7 @@ export function parseUsePrebuiltRnCoreArgument(
|
|
|
75
82
|
return parseBooleanOptionArgument('--use-prebuilt-rn-core', value);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
export function parseUsePrebuiltExpoArgument(
|
|
79
|
-
value: string | boolean
|
|
80
|
-
): boolean {
|
|
85
|
+
export function parseUsePrebuiltExpoArgument(value: string | boolean): boolean {
|
|
81
86
|
return parseBooleanOptionArgument('--use-prebuilt-expo', value);
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -125,6 +130,11 @@ export const packageIosCommand = curryOptions(
|
|
|
125
130
|
.action(
|
|
126
131
|
actionRunner(async (cliOptions: PackageIosOptions) => {
|
|
127
132
|
const options = mergeBrownfieldConfigWithOptions(cliOptions, 'ios');
|
|
133
|
+
// captured before the version-aware default below, so that a value from
|
|
134
|
+
// brownfield.config.json counts as explicit
|
|
135
|
+
const usePrebuiltExpoExplicit = isUsePrebuiltExpoExplicit(
|
|
136
|
+
options.usePrebuiltExpo
|
|
137
|
+
);
|
|
128
138
|
const projectRoot = findProjectRoot();
|
|
129
139
|
|
|
130
140
|
await runExpoPrebuildIfNeeded({ projectRoot, platform: 'ios' });
|
|
@@ -139,10 +149,68 @@ export const packageIosCommand = curryOptions(
|
|
|
139
149
|
? prebuiltRNCoreSupport.enabledByDefault
|
|
140
150
|
: false;
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
const usePrebuiltExpoWasPassed = options.usePrebuiltExpo !== undefined;
|
|
153
|
+
const precompiledExpoDisabledByPodfile =
|
|
154
|
+
isExpoProject(projectRoot) &&
|
|
155
|
+
isPrecompiledExpoDisabledByPodfile(projectRoot);
|
|
156
|
+
|
|
157
|
+
if (precompiledExpoDisabledByPodfile) {
|
|
158
|
+
if (options.usePrebuiltExpo) {
|
|
159
|
+
logger.warn(
|
|
160
|
+
'ios/Podfile.properties.json sets EXPO_USE_PRECOMPILED_MODULES to "false", so pod install builds Expo modules from source and --use-prebuilt-expo will fail. Remove that property or pass --use-prebuilt-expo false.'
|
|
161
|
+
);
|
|
162
|
+
} else if (!usePrebuiltExpoWasPassed) {
|
|
163
|
+
logger.info(
|
|
164
|
+
'ios/Podfile.properties.json turns Expo precompiled modules off. Building Expo modules from source.'
|
|
165
|
+
);
|
|
166
|
+
options.usePrebuiltExpo = false;
|
|
167
|
+
}
|
|
168
|
+
} else if (
|
|
169
|
+
!usePrebuiltExpoWasPassed &&
|
|
170
|
+
isExpoProject(projectRoot) &&
|
|
171
|
+
process.env.EXPO_USE_PRECOMPILED_MODULES === '0'
|
|
172
|
+
) {
|
|
173
|
+
// Respect an exported opt-out, the same way Expo's Podfile does.
|
|
174
|
+
logger.info(
|
|
175
|
+
'EXPO_USE_PRECOMPILED_MODULES is set to 0. Building Expo modules from source.'
|
|
176
|
+
);
|
|
177
|
+
options.usePrebuiltExpo = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const usePrebuiltExpoWasChosen =
|
|
181
|
+
usePrebuiltExpoWasPassed || options.usePrebuiltExpo !== undefined;
|
|
182
|
+
|
|
183
|
+
// version-aware default when the flag is omitted; Expo's precompiled
|
|
184
|
+
// modules also need the prebuilt React Native core
|
|
185
|
+
if (options.usePrebuiltExpo === undefined) {
|
|
186
|
+
options.usePrebuiltExpo = prebuiltExpoSupport.supported
|
|
187
|
+
? prebuiltExpoSupport.enabledByDefault && !!options.usePrebuiltRnCore
|
|
188
|
+
: false;
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
prebuiltExpoSupport.enabledByDefault &&
|
|
192
|
+
!options.usePrebuiltRnCore
|
|
193
|
+
) {
|
|
194
|
+
logger.info(
|
|
195
|
+
'Expo precompiled modules need the prebuilt React Native core, which is off. Building Expo modules from source.'
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
prebuiltExpoSupport.supported &&
|
|
202
|
+
prebuiltExpoSupport.defaultDisabledReason
|
|
203
|
+
) {
|
|
204
|
+
if (usePrebuiltExpoWasChosen && options.usePrebuiltExpo) {
|
|
205
|
+
logger.warn(
|
|
206
|
+
`${prebuiltExpoSupport.defaultDisabledReason} The packaged app will likely crash at runtime. Pass --use-prebuilt-expo false to build Expo modules from source.`
|
|
207
|
+
);
|
|
208
|
+
} else if (!usePrebuiltExpoWasChosen) {
|
|
209
|
+
logger.info(
|
|
210
|
+
`${prebuiltExpoSupport.defaultDisabledReason} Building Expo modules from source.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
146
214
|
|
|
147
215
|
if (prebuiltRNCoreSupport) {
|
|
148
216
|
logger.info(
|
|
@@ -167,6 +235,31 @@ export const packageIosCommand = curryOptions(
|
|
|
167
235
|
throw new RockError(prebuiltExpoSupport.reason);
|
|
168
236
|
}
|
|
169
237
|
|
|
238
|
+
if (isExpoProject(projectRoot)) {
|
|
239
|
+
// Read by Expo's autolinking during `pod install`. Expo-generated
|
|
240
|
+
// Podfiles default it to on, but a React Native CLI app's Podfile
|
|
241
|
+
// never sets it, so the flag has to drive it.
|
|
242
|
+
process.env.EXPO_USE_PRECOMPILED_MODULES = options.usePrebuiltExpo
|
|
243
|
+
? '1'
|
|
244
|
+
: '0';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
options.usePrebuiltExpo &&
|
|
249
|
+
!options.extraParams?.some((param) =>
|
|
250
|
+
param.startsWith('SWIFT_ENABLE_EXPLICIT_MODULES=')
|
|
251
|
+
)
|
|
252
|
+
) {
|
|
253
|
+
// Precompiled Expo builds the React Native pods as static libraries.
|
|
254
|
+
// With explicit modules off (a leftover older React Native versions
|
|
255
|
+
// wrote into app projects), Xcode cannot find their module maps when
|
|
256
|
+
// verifying the framework's Swift interface.
|
|
257
|
+
options.extraParams = [
|
|
258
|
+
...(options.extraParams ?? []),
|
|
259
|
+
'SWIFT_ENABLE_EXPLICIT_MODULES=YES',
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
|
|
170
263
|
if (!userConfig.project.ios) {
|
|
171
264
|
throw new Error('iOS project not found.');
|
|
172
265
|
}
|
|
@@ -197,6 +290,9 @@ export const packageIosCommand = curryOptions(
|
|
|
197
290
|
// Reference: https://github.com/facebook/react-native/blob/490c5e8dcc6cdb19c334cc39e93a39a48ba71e96/packages/react-native/scripts/cocoapods/new_architecture.rb#L171
|
|
198
291
|
const packageDir = path.join(dotBrownfieldDir, 'package', 'build');
|
|
199
292
|
const configuration = options.configuration ?? 'Debug';
|
|
293
|
+
// `--destination` narrows which slices Xcode emits, so every path below has to
|
|
294
|
+
// look at those slices only instead of assuming a device + simulator pair
|
|
295
|
+
const sdks = resolveDestinationSdks(options.destination);
|
|
200
296
|
|
|
201
297
|
const { hasBrownie } = await runBrownieCodegenIfApplicable(
|
|
202
298
|
projectRoot,
|
|
@@ -225,6 +321,11 @@ export const packageIosCommand = curryOptions(
|
|
|
225
321
|
projectRoot,
|
|
226
322
|
packageDir,
|
|
227
323
|
usePrebuiltExpo: options.usePrebuiltExpo,
|
|
324
|
+
usePrebuiltExpoExplicit,
|
|
325
|
+
// keep the generated SPM manifest consistent with the emitted XCFrameworks
|
|
326
|
+
onDegradeToSource: () => {
|
|
327
|
+
options.usePrebuiltExpo = false;
|
|
328
|
+
},
|
|
228
329
|
});
|
|
229
330
|
|
|
230
331
|
const productsPath = path.join(options.buildFolder, 'Build', 'Products');
|
|
@@ -233,6 +334,7 @@ export const packageIosCommand = curryOptions(
|
|
|
233
334
|
explicitScheme: options.scheme,
|
|
234
335
|
productsPath,
|
|
235
336
|
configuration,
|
|
337
|
+
sdks,
|
|
236
338
|
});
|
|
237
339
|
|
|
238
340
|
if (!frameworkName && options.addSpmPackage) {
|
|
@@ -251,24 +353,19 @@ export const packageIosCommand = curryOptions(
|
|
|
251
353
|
productsPath,
|
|
252
354
|
configuration,
|
|
253
355
|
frameworkName,
|
|
356
|
+
sdks,
|
|
254
357
|
});
|
|
255
358
|
|
|
256
359
|
if (configuration.includes('Debug')) {
|
|
257
360
|
// Re-merge only Debug frameworks so the simulator slice includes main.jsbundle.
|
|
258
361
|
await mergeFrameworks({
|
|
259
362
|
sourceDir: userConfig.project.ios.sourceDir,
|
|
260
|
-
frameworkPaths:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
path.join(
|
|
267
|
-
productsPath,
|
|
268
|
-
`${configuration}-iphonesimulator`,
|
|
269
|
-
`${frameworkName}.framework`
|
|
270
|
-
),
|
|
271
|
-
],
|
|
363
|
+
frameworkPaths: collectFrameworkPaths({
|
|
364
|
+
productsPath,
|
|
365
|
+
configuration,
|
|
366
|
+
sdks,
|
|
367
|
+
frameworkName,
|
|
368
|
+
}),
|
|
272
369
|
outputPath: path.join(packageDir, `${frameworkName}.xcframework`),
|
|
273
370
|
});
|
|
274
371
|
}
|
|
@@ -300,20 +397,13 @@ export const packageIosCommand = curryOptions(
|
|
|
300
397
|
|
|
301
398
|
await mergeFrameworks({
|
|
302
399
|
sourceDir: userConfig.project.ios.sourceDir,
|
|
303
|
-
frameworkPaths:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
path.join(
|
|
311
|
-
productsPath,
|
|
312
|
-
`${configuration}-iphonesimulator`,
|
|
313
|
-
'Brownie',
|
|
314
|
-
'Brownie.framework'
|
|
315
|
-
),
|
|
316
|
-
],
|
|
400
|
+
frameworkPaths: collectFrameworkPaths({
|
|
401
|
+
productsPath,
|
|
402
|
+
configuration,
|
|
403
|
+
sdks,
|
|
404
|
+
frameworkName: 'Brownie',
|
|
405
|
+
productSubDir: 'Brownie',
|
|
406
|
+
}),
|
|
317
407
|
outputPath: brownieOutputPath,
|
|
318
408
|
});
|
|
319
409
|
|
|
@@ -335,20 +425,13 @@ export const packageIosCommand = curryOptions(
|
|
|
335
425
|
|
|
336
426
|
await mergeFrameworks({
|
|
337
427
|
sourceDir: userConfig.project.ios.sourceDir,
|
|
338
|
-
frameworkPaths:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
path.join(
|
|
346
|
-
productsPath,
|
|
347
|
-
`${configuration}-iphonesimulator`,
|
|
348
|
-
'BrownfieldNavigation',
|
|
349
|
-
'BrownfieldNavigation.framework'
|
|
350
|
-
),
|
|
351
|
-
],
|
|
428
|
+
frameworkPaths: collectFrameworkPaths({
|
|
429
|
+
productsPath,
|
|
430
|
+
configuration,
|
|
431
|
+
sdks,
|
|
432
|
+
frameworkName: 'BrownfieldNavigation',
|
|
433
|
+
productSubDir: 'BrownfieldNavigation',
|
|
434
|
+
}),
|
|
352
435
|
outputPath: brownfieldNavigationOutputPath,
|
|
353
436
|
});
|
|
354
437
|
|
|
@@ -378,9 +461,20 @@ export const packageIosCommand = curryOptions(
|
|
|
378
461
|
'In Xcode, choose File > Add Package Dependencies..., click Add Local..., and select that folder.'
|
|
379
462
|
);
|
|
380
463
|
}
|
|
464
|
+
|
|
465
|
+
// The dependency prints its own "Success" before the post-build steps above,
|
|
466
|
+
// so log a final line marking the true end of the run.
|
|
467
|
+
logger.success(
|
|
468
|
+
`package:ios finished. Artifacts are in ${colorLink(relativeToCwd(packageDir))}`
|
|
469
|
+
);
|
|
381
470
|
})
|
|
382
471
|
);
|
|
383
472
|
|
|
473
|
+
/** Whether `--use-prebuilt-expo` was chosen (CLI or brownfield.config.json) rather than inferred. */
|
|
474
|
+
export function isUsePrebuiltExpoExplicit(value: boolean | undefined) {
|
|
475
|
+
return value !== undefined;
|
|
476
|
+
}
|
|
477
|
+
|
|
384
478
|
export const packageIosExample = new ExampleUsage(
|
|
385
479
|
'package:ios --scheme BrownfieldLib --configuration Release',
|
|
386
480
|
"Build iOS XCFramework for 'BrownfieldLib' scheme in Release configuration"
|
|
@@ -3,21 +3,31 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { colorLink, logger, relativeToCwd } from '@rock-js/tools';
|
|
5
5
|
|
|
6
|
+
import { ALL_APPLE_SDKS, type AppleSdk } from './destinationSdks.js';
|
|
7
|
+
|
|
6
8
|
interface CopyDebugBundleToSimulatorSliceOptions {
|
|
7
9
|
productsPath: string;
|
|
8
10
|
configuration: string;
|
|
9
11
|
frameworkName: string;
|
|
12
|
+
/** Slices the build produced; defaults to both. */
|
|
13
|
+
sdks?: AppleSdk[];
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export function copyDebugBundleToSimulatorSlice({
|
|
13
17
|
productsPath,
|
|
14
18
|
configuration,
|
|
15
19
|
frameworkName,
|
|
20
|
+
sdks = ALL_APPLE_SDKS,
|
|
16
21
|
}: CopyDebugBundleToSimulatorSliceOptions) {
|
|
17
22
|
if (!configuration.includes('Debug')) {
|
|
18
23
|
return;
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
// nothing to copy across when only one slice was built
|
|
27
|
+
if (!sdks.includes('iphoneos') || !sdks.includes('iphonesimulator')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const deviceBundlePath = path.join(
|
|
22
32
|
productsPath,
|
|
23
33
|
`${configuration}-iphoneos`,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { RockError } from '@rock-js/tools';
|
|
5
|
+
|
|
6
|
+
/** Xcode SDK a build product directory belongs to (`Release-iphoneos`, `Release-iphonesimulator`). */
|
|
7
|
+
export type AppleSdk = 'iphoneos' | 'iphonesimulator';
|
|
8
|
+
|
|
9
|
+
/** Both slices, in the order Rock's `packageIosAction` builds them. */
|
|
10
|
+
export const ALL_APPLE_SDKS: AppleSdk[] = ['iphoneos', 'iphonesimulator'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Map a single `--destination` value onto the SDK(s) its build products land in.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors `resolveDestination` in `@rock-js/platform-apple-helpers`: the aliases
|
|
16
|
+
* `device`/`simulator` plus any raw `xcodebuild -destination` string. A value we
|
|
17
|
+
* cannot classify (`id=<udid>`, `name=iPhone 17`) may resolve to either SDK, so it
|
|
18
|
+
* widens to both rather than silently dropping a slice.
|
|
19
|
+
*/
|
|
20
|
+
function resolveSdksForDestination(destination: string): AppleSdk[] {
|
|
21
|
+
const normalized = destination.trim().toLowerCase();
|
|
22
|
+
|
|
23
|
+
if (normalized === 'device') {
|
|
24
|
+
return ['iphoneos'];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (normalized === 'simulator' || normalized.includes('simulator')) {
|
|
28
|
+
return ['iphonesimulator'];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (normalized.includes('platform=ios')) {
|
|
32
|
+
return ['iphoneos'];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return ALL_APPLE_SDKS;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SDK slices a `package:ios` run produces for the given `--destination` values.
|
|
40
|
+
*
|
|
41
|
+
* Omitting `--destination` builds both slices, matching Rock's default.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveDestinationSdks(
|
|
44
|
+
destination?: string[] | null
|
|
45
|
+
): AppleSdk[] {
|
|
46
|
+
if (!destination || destination.length === 0) {
|
|
47
|
+
return ALL_APPLE_SDKS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sdks = new Set(destination.flatMap(resolveSdksForDestination));
|
|
51
|
+
|
|
52
|
+
// keep a stable order regardless of the order the flags were passed in
|
|
53
|
+
return ALL_APPLE_SDKS.filter((sdk) => sdks.has(sdk));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether `directoryPath` holds a build product `mergeFrameworks` can consume:
|
|
58
|
+
* either a real `.framework` bundle or the static library it synthesizes one from.
|
|
59
|
+
*/
|
|
60
|
+
export function hasFrameworkBuildProduct(
|
|
61
|
+
directoryPath: string,
|
|
62
|
+
frameworkName: string
|
|
63
|
+
) {
|
|
64
|
+
return (
|
|
65
|
+
fs.existsSync(path.join(directoryPath, `${frameworkName}.framework`)) ||
|
|
66
|
+
fs.existsSync(path.join(directoryPath, `lib${frameworkName}.a`))
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface CollectFrameworkPathsOptions {
|
|
71
|
+
productsPath: string;
|
|
72
|
+
configuration: string;
|
|
73
|
+
sdks: AppleSdk[];
|
|
74
|
+
frameworkName: string;
|
|
75
|
+
/** Sub-directory of the configuration products dir, e.g. `Brownie` for a Pod target. */
|
|
76
|
+
productSubDir?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* `.framework` paths to merge into an XCFramework, limited to the slices that were
|
|
81
|
+
* actually built. Slices with no build product on disk are dropped, so a
|
|
82
|
+
* `--destination simulator` run merges the simulator slice alone instead of failing
|
|
83
|
+
* on a missing `-iphoneos` directory.
|
|
84
|
+
*
|
|
85
|
+
* Throws when no slice has a build product: passing an empty list to
|
|
86
|
+
* `mergeFrameworks` would surface as xcodebuild's opaque
|
|
87
|
+
* "at least one framework or library must be specified".
|
|
88
|
+
*/
|
|
89
|
+
export function collectFrameworkPaths({
|
|
90
|
+
productsPath,
|
|
91
|
+
configuration,
|
|
92
|
+
sdks,
|
|
93
|
+
frameworkName,
|
|
94
|
+
productSubDir,
|
|
95
|
+
}: CollectFrameworkPathsOptions): string[] {
|
|
96
|
+
const searchedDirectories = sdks.map((sdk) =>
|
|
97
|
+
path.join(
|
|
98
|
+
productsPath,
|
|
99
|
+
`${configuration}-${sdk}`,
|
|
100
|
+
...(productSubDir ? [productSubDir] : [])
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const frameworkPaths = searchedDirectories
|
|
105
|
+
.filter((directoryPath) =>
|
|
106
|
+
hasFrameworkBuildProduct(directoryPath, frameworkName)
|
|
107
|
+
)
|
|
108
|
+
.map((directoryPath) =>
|
|
109
|
+
path.join(directoryPath, `${frameworkName}.framework`)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (frameworkPaths.length === 0) {
|
|
113
|
+
throw new RockError(
|
|
114
|
+
`Could not find a build product for ${frameworkName} in the ${configuration} configuration. ` +
|
|
115
|
+
`Looked for ${frameworkName}.framework or lib${frameworkName}.a in:\n` +
|
|
116
|
+
searchedDirectories.map((dir) => ` - ${dir}`).join('\n') +
|
|
117
|
+
`\nIf the build produced an .app instead of a framework, the wrong scheme was built; pass --scheme with your brownfield framework scheme.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return frameworkPaths;
|
|
122
|
+
}
|