@callstack/brownfield-cli 1.0.1 → 1.0.2
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 +13 -0
- package/dist/brownfield/commands/packageIos.d.ts.map +1 -1
- package/dist/brownfield/commands/packageIos.js +5 -1
- package/dist/brownfield/utils/index.d.ts +1 -0
- package/dist/brownfield/utils/index.d.ts.map +1 -1
- package/dist/brownfield/utils/index.js +1 -0
- package/dist/brownfield/utils/stripFrameworkBinary.d.ts +9 -0
- package/dist/brownfield/utils/stripFrameworkBinary.d.ts.map +1 -0
- package/dist/brownfield/utils/stripFrameworkBinary.js +107 -0
- package/dist/main.js +0 -0
- package/package.json +2 -2
- package/src/brownfield/commands/packageIos.ts +6 -1
- package/src/brownfield/utils/index.ts +1 -0
- package/src/brownfield/utils/stripFrameworkBinary.ts +149 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @callstack/brownfield-cli
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`2a8563f`](https://github.com/callstack/react-native-brownfield/commit/2a8563f65ed152054ad1290caf963791a368ee9a) Thanks [@okwasniewski](https://github.com/okwasniewski)! - feat: strip framework binaries to avoid duplicate symbol errors
|
|
8
|
+
|
|
9
|
+
## 1.0.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#198](https://github.com/callstack/react-native-brownfield/pull/198) [`c8c903d`](https://github.com/callstack/react-native-brownfield/commit/c8c903d0d2b78a8c06a41213dfbe781a2daf3d25) Thanks [@artus9033](https://github.com/artus9033)! - docs: added README files to all packages
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageIos.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageIos.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"packageIos.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageIos.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,iBAAiB,SAuF7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,cAG7B,CAAC"}
|
|
@@ -5,7 +5,7 @@ import { colorLink, getReactNativeVersion, logger, relativeToCwd, } from '@rock-
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { isBrownieInstalled } from '../../brownie/config.js';
|
|
7
7
|
import { runCodegen } from '../../brownie/commands/codegen.js';
|
|
8
|
-
import { getProjectInfo } from '../utils/index.js';
|
|
8
|
+
import { getProjectInfo, stripFrameworkBinary } from '../utils/index.js';
|
|
9
9
|
import { actionRunner, curryOptions, ExampleUsage, } from '../../shared/index.js';
|
|
10
10
|
export const packageIosCommand = curryOptions(new Command('package:ios').description('Build iOS XCFramework'), getBuildOptions({ platformName: 'ios' }).map((option) => option.name.startsWith('--build-folder')
|
|
11
11
|
? {
|
|
@@ -51,6 +51,10 @@ export const packageIosCommand = curryOptions(new Command('package:ios').descrip
|
|
|
51
51
|
],
|
|
52
52
|
outputPath: brownieOutputPath,
|
|
53
53
|
});
|
|
54
|
+
// Strip the binary from Brownie.xcframework to make it interface-only.
|
|
55
|
+
// This avoids duplicate symbols when consumer apps embed both BrownfieldLib
|
|
56
|
+
// (which contains Brownie symbols) and Brownie.xcframework.
|
|
57
|
+
await stripFrameworkBinary(brownieOutputPath);
|
|
54
58
|
logger.success(`Brownie.xcframework created at ${colorLink(relativeToCwd(brownieOutputPath))}`);
|
|
55
59
|
}
|
|
56
60
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips the binary from an xcframework, keeping only Swift module interfaces.
|
|
3
|
+
* This creates an "interface-only" framework where consumers can import the module
|
|
4
|
+
* but the actual symbols must come from another framework (e.g., BrownfieldLib).
|
|
5
|
+
*
|
|
6
|
+
* @param xcframeworkPath - Path to the .xcframework directory
|
|
7
|
+
*/
|
|
8
|
+
export declare function stripFrameworkBinary(xcframeworkPath: string): void;
|
|
9
|
+
//# sourceMappingURL=stripFrameworkBinary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripFrameworkBinary.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/stripFrameworkBinary.ts"],"names":[],"mappings":"AAuFA;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAsDlE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { logger } from '@rock-js/tools';
|
|
6
|
+
const SLICE_CONFIGS = {
|
|
7
|
+
'ios-arm64': {
|
|
8
|
+
target: 'arm64-apple-ios15.0',
|
|
9
|
+
},
|
|
10
|
+
'ios-arm64_x86_64-simulator': {
|
|
11
|
+
target: 'arm64-apple-ios15.0-simulator',
|
|
12
|
+
additionalTargets: ['x86_64-apple-ios15.0-simulator'],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Creates an empty static library for the given target.
|
|
17
|
+
*/
|
|
18
|
+
function createEmptyStaticLib(target) {
|
|
19
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'framework-strip-'));
|
|
20
|
+
const tempObj = path.join(tempDir, 'empty.o');
|
|
21
|
+
const tempLib = path.join(tempDir, 'empty.a');
|
|
22
|
+
try {
|
|
23
|
+
execSync(`echo "" | xcrun clang -x c -c - -o "${tempObj}" -target ${target}`, {
|
|
24
|
+
stdio: 'pipe',
|
|
25
|
+
});
|
|
26
|
+
execSync(`xcrun ar rcs "${tempLib}" "${tempObj}"`, {
|
|
27
|
+
stdio: 'pipe',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
32
|
+
throw new Error(`Failed to create empty static library for target ${target}: ${error instanceof Error ? error.message : error}`);
|
|
33
|
+
}
|
|
34
|
+
fs.unlinkSync(tempObj);
|
|
35
|
+
return tempLib;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates a fat static library combining multiple architectures.
|
|
39
|
+
*/
|
|
40
|
+
function createFatStaticLib(targets) {
|
|
41
|
+
const libs = targets.map((target) => createEmptyStaticLib(target));
|
|
42
|
+
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'framework-strip-fat-'));
|
|
43
|
+
const outputLib = path.join(outputDir, 'fat.a');
|
|
44
|
+
try {
|
|
45
|
+
execSync(`xcrun lipo -create ${libs.map((l) => `"${l}"`).join(' ')} -output "${outputLib}"`, {
|
|
46
|
+
stdio: 'pipe',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
libs.forEach((lib) => fs.rmSync(path.dirname(lib), { recursive: true }));
|
|
51
|
+
fs.rmSync(outputDir, { recursive: true });
|
|
52
|
+
throw new Error(`Failed to create fat static library: ${error instanceof Error ? error.message : error}`);
|
|
53
|
+
}
|
|
54
|
+
libs.forEach((lib) => {
|
|
55
|
+
fs.unlinkSync(lib);
|
|
56
|
+
fs.rmSync(path.dirname(lib), { recursive: true });
|
|
57
|
+
});
|
|
58
|
+
return outputLib;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Strips the binary from an xcframework, keeping only Swift module interfaces.
|
|
62
|
+
* This creates an "interface-only" framework where consumers can import the module
|
|
63
|
+
* but the actual symbols must come from another framework (e.g., BrownfieldLib).
|
|
64
|
+
*
|
|
65
|
+
* @param xcframeworkPath - Path to the .xcframework directory
|
|
66
|
+
*/
|
|
67
|
+
export function stripFrameworkBinary(xcframeworkPath) {
|
|
68
|
+
if (!fs.existsSync(xcframeworkPath)) {
|
|
69
|
+
throw new Error(`XCFramework not found at: ${xcframeworkPath}`);
|
|
70
|
+
}
|
|
71
|
+
const frameworkName = path.basename(xcframeworkPath, '.xcframework');
|
|
72
|
+
logger.info(`Stripping binary from ${frameworkName}.xcframework (interface-only)...`);
|
|
73
|
+
const slices = fs.readdirSync(xcframeworkPath).filter((entry) => {
|
|
74
|
+
const fullPath = path.join(xcframeworkPath, entry);
|
|
75
|
+
return fs.statSync(fullPath).isDirectory() && entry.startsWith('ios-');
|
|
76
|
+
});
|
|
77
|
+
for (const sliceName of slices) {
|
|
78
|
+
const frameworkDir = path.join(xcframeworkPath, sliceName, `${frameworkName}.framework`);
|
|
79
|
+
const binaryPath = path.join(frameworkDir, frameworkName);
|
|
80
|
+
if (!fs.existsSync(binaryPath)) {
|
|
81
|
+
logger.warn(`No binary found at ${binaryPath}, skipping`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const config = SLICE_CONFIGS[sliceName];
|
|
85
|
+
if (!config) {
|
|
86
|
+
logger.warn(`Unknown slice type: ${sliceName}, skipping`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let emptyLib;
|
|
90
|
+
if (config.additionalTargets) {
|
|
91
|
+
// Create fat library for multiple architectures
|
|
92
|
+
emptyLib = createFatStaticLib([
|
|
93
|
+
config.target,
|
|
94
|
+
...config.additionalTargets,
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Create single-arch library
|
|
99
|
+
emptyLib = createEmptyStaticLib(config.target);
|
|
100
|
+
}
|
|
101
|
+
// Replace original binary with empty stub
|
|
102
|
+
fs.copyFileSync(emptyLib, binaryPath);
|
|
103
|
+
fs.unlinkSync(emptyLib);
|
|
104
|
+
fs.rmSync(path.dirname(emptyLib), { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
logger.success(`${frameworkName}.xcframework is now interface-only`);
|
|
107
|
+
}
|
package/dist/main.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ import { Command } from 'commander';
|
|
|
17
17
|
|
|
18
18
|
import { isBrownieInstalled } from '../../brownie/config.js';
|
|
19
19
|
import { runCodegen } from '../../brownie/commands/codegen.js';
|
|
20
|
-
import { getProjectInfo } from '../utils/index.js';
|
|
20
|
+
import { getProjectInfo, stripFrameworkBinary } from '../utils/index.js';
|
|
21
21
|
import {
|
|
22
22
|
actionRunner,
|
|
23
23
|
curryOptions,
|
|
@@ -101,6 +101,11 @@ export const packageIosCommand = curryOptions(
|
|
|
101
101
|
outputPath: brownieOutputPath,
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
// Strip the binary from Brownie.xcframework to make it interface-only.
|
|
105
|
+
// This avoids duplicate symbols when consumer apps embed both BrownfieldLib
|
|
106
|
+
// (which contains Brownie symbols) and Brownie.xcframework.
|
|
107
|
+
await stripFrameworkBinary(brownieOutputPath);
|
|
108
|
+
|
|
104
109
|
logger.success(
|
|
105
110
|
`Brownie.xcframework created at ${colorLink(relativeToCwd(brownieOutputPath))}`
|
|
106
111
|
);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { logger } from '@rock-js/tools';
|
|
6
|
+
|
|
7
|
+
interface SliceConfig {
|
|
8
|
+
target: string;
|
|
9
|
+
/** Additional targets for fat binaries */
|
|
10
|
+
additionalTargets?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SLICE_CONFIGS: Record<string, SliceConfig> = {
|
|
14
|
+
'ios-arm64': {
|
|
15
|
+
target: 'arm64-apple-ios15.0',
|
|
16
|
+
},
|
|
17
|
+
'ios-arm64_x86_64-simulator': {
|
|
18
|
+
target: 'arm64-apple-ios15.0-simulator',
|
|
19
|
+
additionalTargets: ['x86_64-apple-ios15.0-simulator'],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates an empty static library for the given target.
|
|
25
|
+
*/
|
|
26
|
+
function createEmptyStaticLib(target: string): string {
|
|
27
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'framework-strip-'));
|
|
28
|
+
const tempObj = path.join(tempDir, 'empty.o');
|
|
29
|
+
const tempLib = path.join(tempDir, 'empty.a');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
execSync(
|
|
33
|
+
`echo "" | xcrun clang -x c -c - -o "${tempObj}" -target ${target}`,
|
|
34
|
+
{
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
execSync(`xcrun ar rcs "${tempLib}" "${tempObj}"`, {
|
|
40
|
+
stdio: 'pipe',
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Failed to create empty static library for target ${target}: ${error instanceof Error ? error.message : error}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fs.unlinkSync(tempObj);
|
|
50
|
+
|
|
51
|
+
return tempLib;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a fat static library combining multiple architectures.
|
|
56
|
+
*/
|
|
57
|
+
function createFatStaticLib(targets: string[]): string {
|
|
58
|
+
const libs = targets.map((target) => createEmptyStaticLib(target));
|
|
59
|
+
|
|
60
|
+
const outputDir = fs.mkdtempSync(
|
|
61
|
+
path.join(os.tmpdir(), 'framework-strip-fat-')
|
|
62
|
+
);
|
|
63
|
+
const outputLib = path.join(outputDir, 'fat.a');
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
execSync(
|
|
67
|
+
`xcrun lipo -create ${libs.map((l) => `"${l}"`).join(' ')} -output "${outputLib}"`,
|
|
68
|
+
{
|
|
69
|
+
stdio: 'pipe',
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
libs.forEach((lib) => fs.rmSync(path.dirname(lib), { recursive: true }));
|
|
74
|
+
fs.rmSync(outputDir, { recursive: true });
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Failed to create fat static library: ${error instanceof Error ? error.message : error}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
libs.forEach((lib) => {
|
|
81
|
+
fs.unlinkSync(lib);
|
|
82
|
+
fs.rmSync(path.dirname(lib), { recursive: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return outputLib;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Strips the binary from an xcframework, keeping only Swift module interfaces.
|
|
90
|
+
* This creates an "interface-only" framework where consumers can import the module
|
|
91
|
+
* but the actual symbols must come from another framework (e.g., BrownfieldLib).
|
|
92
|
+
*
|
|
93
|
+
* @param xcframeworkPath - Path to the .xcframework directory
|
|
94
|
+
*/
|
|
95
|
+
export function stripFrameworkBinary(xcframeworkPath: string): void {
|
|
96
|
+
if (!fs.existsSync(xcframeworkPath)) {
|
|
97
|
+
throw new Error(`XCFramework not found at: ${xcframeworkPath}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const frameworkName = path.basename(xcframeworkPath, '.xcframework');
|
|
101
|
+
|
|
102
|
+
logger.info(
|
|
103
|
+
`Stripping binary from ${frameworkName}.xcframework (interface-only)...`
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const slices = fs.readdirSync(xcframeworkPath).filter((entry) => {
|
|
107
|
+
const fullPath = path.join(xcframeworkPath, entry);
|
|
108
|
+
return fs.statSync(fullPath).isDirectory() && entry.startsWith('ios-');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for (const sliceName of slices) {
|
|
112
|
+
const frameworkDir = path.join(
|
|
113
|
+
xcframeworkPath,
|
|
114
|
+
sliceName,
|
|
115
|
+
`${frameworkName}.framework`
|
|
116
|
+
);
|
|
117
|
+
const binaryPath = path.join(frameworkDir, frameworkName);
|
|
118
|
+
|
|
119
|
+
if (!fs.existsSync(binaryPath)) {
|
|
120
|
+
logger.warn(`No binary found at ${binaryPath}, skipping`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const config = SLICE_CONFIGS[sliceName];
|
|
125
|
+
if (!config) {
|
|
126
|
+
logger.warn(`Unknown slice type: ${sliceName}, skipping`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let emptyLib: string;
|
|
131
|
+
if (config.additionalTargets) {
|
|
132
|
+
// Create fat library for multiple architectures
|
|
133
|
+
emptyLib = createFatStaticLib([
|
|
134
|
+
config.target,
|
|
135
|
+
...config.additionalTargets,
|
|
136
|
+
]);
|
|
137
|
+
} else {
|
|
138
|
+
// Create single-arch library
|
|
139
|
+
emptyLib = createEmptyStaticLib(config.target);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Replace original binary with empty stub
|
|
143
|
+
fs.copyFileSync(emptyLib, binaryPath);
|
|
144
|
+
fs.unlinkSync(emptyLib);
|
|
145
|
+
fs.rmSync(path.dirname(emptyLib), { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
logger.success(`${frameworkName}.xcframework is now interface-only`);
|
|
149
|
+
}
|