@callstack/brownfield-cli 6.1.0 → 6.2.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @callstack/brownfield-cli
2
2
 
3
+ ## 6.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#514](https://github.com/callstack/react-native-brownfield/pull/514) [`6517ec5`](https://github.com/callstack/react-native-brownfield/commit/6517ec5666269007f10c28efb7a748beba476998) Thanks [@DanielPotorCK](https://github.com/DanielPotorCK)! - Allow `ios.extraSpmXcframeworks` in brownfield config so `package:ios --add-spm-package` can include project-declared XCFrameworks as additional SPM binary targets.
8
+
9
+ ## 6.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [#519](https://github.com/callstack/react-native-brownfield/pull/519) [`a9726c1`](https://github.com/callstack/react-native-brownfield/commit/a9726c137c61fe2e59c039555a955632a5d86aec) Thanks [@RaphaelFakhri](https://github.com/RaphaelFakhri)! - `brownfield navigation:codegen` now generates `NSNumber` for optional `number` and `boolean` method parameters on iOS. Previously, the generated Swift delegate used `Double?` and `Bool?`, which can't be represented in an `@objc` protocol, so the iOS build failed.
14
+
3
15
  ## 6.1.0
4
16
 
5
17
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"packageIos.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageIos.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAU5C,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAgD/B,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GAAG,OAAO,GACtB,OAAO,CAET;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAE7E;AAcD,eAAO,MAAM,iBAAiB,SAkX3B,CAAC;AAEJ,qGAAqG;AACrG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,oBAEnE;AAED,eAAO,MAAM,iBAAiB,cAG7B,CAAC"}
1
+ {"version":3,"file":"packageIos.d.ts","sourceRoot":"","sources":["../../../src/brownfield/commands/packageIos.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAU5C,OAAO,EAGL,YAAY,EACb,MAAM,uBAAuB,CAAC;AAgD/B,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GAAG,OAAO,GACtB,OAAO,CAET;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAE7E;AAcD,eAAO,MAAM,iBAAiB,SAoX3B,CAAC;AAEJ,qGAAqG;AACrG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,oBAEnE;AAED,eAAO,MAAM,iBAAiB,cAG7B,CAAC"}
@@ -290,6 +290,8 @@ export const packageIosCommand = curryOptions(new Command('package:ios').descrip
290
290
  packageDir,
291
291
  frameworkName: frameworkName ?? undefined,
292
292
  usePrebuiltExpo: options.usePrebuiltExpo,
293
+ projectRoot,
294
+ extraSpmXcframeworks: options.extraSpmXcframeworks ?? [],
293
295
  });
294
296
  logger.success(`Local SPM package manifest created at ${colorLink(relativeToCwd(packageManifestPath))}`);
295
297
  logger.info(`Add the local package folder in Xcode: ${colorLink(relativeToCwd(packageDir))}`);
@@ -1,11 +1,14 @@
1
+ import type { ExtraSpmXcframework } from '../../types.js';
1
2
  type CreateLocalSpmPackageOptions = {
2
3
  packageDir: string;
3
4
  frameworkName?: string;
4
5
  usePrebuiltExpo?: boolean;
6
+ projectRoot?: string;
7
+ extraSpmXcframeworks?: ExtraSpmXcframework[];
5
8
  };
6
9
  type CreateLocalSpmPackageResult = {
7
10
  packageManifestPath: string;
8
11
  };
9
- export declare function createLocalSpmPackage({ packageDir, frameworkName, usePrebuiltExpo, }: CreateLocalSpmPackageOptions): CreateLocalSpmPackageResult;
12
+ export declare function createLocalSpmPackage({ packageDir, frameworkName, usePrebuiltExpo, projectRoot, extraSpmXcframeworks, }: CreateLocalSpmPackageOptions): CreateLocalSpmPackageResult;
10
13
  export {};
11
14
  //# sourceMappingURL=createLocalSpmPackage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createLocalSpmPackage.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/createLocalSpmPackage.ts"],"names":[],"mappings":"AAYA,KAAK,4BAA4B,GAAG;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,KAAK,2BAA2B,GAAG;IACjC,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AA0JF,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,aAAa,EACb,eAAsB,GACvB,EAAE,4BAA4B,GAAG,2BAA2B,CA2C5D"}
1
+ {"version":3,"file":"createLocalSpmPackage.d.ts","sourceRoot":"","sources":["../../../src/brownfield/utils/createLocalSpmPackage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAU1D,KAAK,4BAA4B,GAAG;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC9C,CAAC;AAEF,KAAK,2BAA2B,GAAG;IACjC,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC;AAwOF,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,aAAa,EACb,eAAsB,EACtB,WAA2B,EAC3B,oBAAyB,GAC1B,EAAE,4BAA4B,GAAG,2BAA2B,CA2D5D"}
@@ -12,6 +12,8 @@ const RESERVED_FRAMEWORK_NAMES = new Set([
12
12
  'ReactNativeDependencies',
13
13
  ...ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES,
14
14
  ]);
15
+ const SWIFT_TARGET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
16
+ const RESERVED_FRAMEWORK_NAME_KEYS = new Set([...RESERVED_FRAMEWORK_NAMES].map((name) => name.toLowerCase()));
15
17
  function requireXcframework(packageDir, name) {
16
18
  const xcframeworkPath = path.join(packageDir, `${name}.xcframework`);
17
19
  if (!fs.existsSync(xcframeworkPath)) {
@@ -28,7 +30,7 @@ function requireHermesXcframework(packageDir) {
28
30
  return (optionalXcframework(packageDir, 'hermesvm') ??
29
31
  requireXcframework(packageDir, 'hermes'));
30
32
  }
31
- function resolveAppFrameworkName(packageDir, explicitFrameworkName) {
33
+ function resolveAppFrameworkName(packageDir, reservedFrameworkNames, explicitFrameworkName) {
32
34
  if (explicitFrameworkName) {
33
35
  return requireXcframework(packageDir, explicitFrameworkName);
34
36
  }
@@ -36,7 +38,7 @@ function resolveAppFrameworkName(packageDir, explicitFrameworkName) {
36
38
  .readdirSync(packageDir, { withFileTypes: true })
37
39
  .filter((entry) => entry.isDirectory() &&
38
40
  entry.name.endsWith('.xcframework') &&
39
- !RESERVED_FRAMEWORK_NAMES.has(path.basename(entry.name, '.xcframework')))
41
+ !reservedFrameworkNames.has(path.basename(entry.name, '.xcframework')))
40
42
  .map((entry) => path.basename(entry.name, '.xcframework'))
41
43
  .sort();
42
44
  if (candidates.length === 1 && candidates[0]) {
@@ -47,6 +49,45 @@ function resolveAppFrameworkName(packageDir, explicitFrameworkName) {
47
49
  }
48
50
  throw new Error(`Found multiple packaged app XCFramework candidates (${candidates.join(', ')}). Pass --scheme explicitly when packaging.`);
49
51
  }
52
+ function resolveExtraSpmXcframeworkPath(projectRoot, entry) {
53
+ return path.isAbsolute(entry.path)
54
+ ? entry.path
55
+ : path.resolve(projectRoot, entry.path);
56
+ }
57
+ function validateExtraSpmXcframeworks(extraSpmXcframeworks, frameworkName) {
58
+ const nameKeys = new Set();
59
+ for (const entry of extraSpmXcframeworks) {
60
+ if (!SWIFT_TARGET_NAME_PATTERN.test(entry.name)) {
61
+ throw new Error(`Invalid extra SPM XCFramework name "${entry.name}". Names must start with a letter or underscore and contain only letters, numbers, and underscores.`);
62
+ }
63
+ const nameKey = entry.name.toLowerCase();
64
+ if (RESERVED_FRAMEWORK_NAME_KEYS.has(nameKey) ||
65
+ nameKey === frameworkName?.toLowerCase()) {
66
+ throw new Error(`Extra SPM XCFramework name "${entry.name}" conflicts with a generated package target.`);
67
+ }
68
+ if (nameKeys.has(nameKey)) {
69
+ throw new Error(`Duplicate extra SPM XCFramework name: ${entry.name}`);
70
+ }
71
+ nameKeys.add(nameKey);
72
+ }
73
+ }
74
+ function stageExtraSpmXcframeworks({ packageDir, projectRoot, extraSpmXcframeworks, }) {
75
+ const stagedNames = [];
76
+ for (const entry of extraSpmXcframeworks) {
77
+ const sourcePath = resolveExtraSpmXcframeworkPath(projectRoot, entry);
78
+ if (!fs.existsSync(sourcePath)) {
79
+ throw new Error(`Missing extra SPM XCFramework "${entry.name}" at ${entry.path}`);
80
+ }
81
+ const destinationPath = path.join(packageDir, `${entry.name}.xcframework`);
82
+ fs.rmSync(destinationPath, { recursive: true, force: true });
83
+ fs.cpSync(fs.realpathSync(sourcePath), destinationPath, {
84
+ recursive: true,
85
+ verbatimSymlinks: true,
86
+ });
87
+ stagedNames.push(entry.name);
88
+ }
89
+ return stagedNames;
90
+ }
50
91
  function renderPackageSwift({ packageName, libraryName, targetNames, }) {
51
92
  const binaryTargets = targetNames
52
93
  .map((targetName) => ` .binaryTarget(name: "${targetName}", path: "./${SPM_ARTIFACTS_DIR_NAME}/${targetName}.xcframework")`)
@@ -107,19 +148,32 @@ If Xcode builds your host app but the simulator installation fails, check the ho
107
148
  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.
108
149
  `;
109
150
  }
110
- export function createLocalSpmPackage({ packageDir, frameworkName, usePrebuiltExpo = true, }) {
111
- const resolvedFrameworkName = resolveAppFrameworkName(packageDir, frameworkName);
151
+ export function createLocalSpmPackage({ packageDir, frameworkName, usePrebuiltExpo = true, projectRoot = process.cwd(), extraSpmXcframeworks = [], }) {
152
+ validateExtraSpmXcframeworks(extraSpmXcframeworks, frameworkName);
153
+ const reservedFrameworkNames = new Set([
154
+ ...RESERVED_FRAMEWORK_NAMES,
155
+ ...extraSpmXcframeworks.map((entry) => entry.name),
156
+ ]);
157
+ const extraTargetNames = stageExtraSpmXcframeworks({
158
+ packageDir,
159
+ projectRoot,
160
+ extraSpmXcframeworks,
161
+ });
162
+ const resolvedFrameworkName = resolveAppFrameworkName(packageDir, reservedFrameworkNames, frameworkName);
112
163
  const expoSupportXcframeworkNames = getExpoSupportXcframeworkNames(usePrebuiltExpo);
113
164
  const targetNames = [
114
- resolvedFrameworkName,
115
- requireHermesXcframework(packageDir),
116
- requireXcframework(packageDir, 'ReactBrownfield'),
117
- optionalXcframework(packageDir, 'Brownie'),
118
- optionalXcframework(packageDir, 'BrownfieldNavigation'),
119
- ...expoSupportXcframeworkNames.map((frameworkName) => optionalXcframework(packageDir, frameworkName)),
120
- optionalXcframework(packageDir, 'React'),
121
- optionalXcframework(packageDir, 'ReactNativeDependencies'),
122
- ].filter((targetName) => targetName !== null);
165
+ ...new Set([
166
+ resolvedFrameworkName,
167
+ requireHermesXcframework(packageDir),
168
+ requireXcframework(packageDir, 'ReactBrownfield'),
169
+ optionalXcframework(packageDir, 'Brownie'),
170
+ optionalXcframework(packageDir, 'BrownfieldNavigation'),
171
+ ...expoSupportXcframeworkNames.map((expoFrameworkName) => optionalXcframework(packageDir, expoFrameworkName)),
172
+ optionalXcframework(packageDir, 'React'),
173
+ optionalXcframework(packageDir, 'ReactNativeDependencies'),
174
+ ...extraTargetNames,
175
+ ].filter((targetName) => targetName !== null)),
176
+ ];
123
177
  const packageManifestPath = path.join(packageDir, 'Package.swift');
124
178
  const readmePath = path.join(packageDir, 'README.md');
125
179
  prepareLocalSpmArtifacts({
@@ -1 +1 @@
1
- {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,aAAa,CAAC;AAkBhE,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAuCD,UAAU,uBAAuB;IAC/B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,qBAAsB,SAAQ,sBAAsB;CAAG;AA2CjE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CA+BR;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkCR"}
1
+ {"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../../src/navigation/generators/ios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,aAAa,CAAC;AAyBhE,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AA2CD,UAAU,uBAAuB;IAC/B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,UAAU,qBAAsB,SAAQ,sBAAsB;CAAG;AA+CjE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,uBAA4B,GACpC,MAAM,CA+BR;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkCR"}
@@ -12,6 +12,12 @@ const TS_TO_SWIFT_TYPE = {
12
12
  void: 'Void',
13
13
  Object: '[String: Any]',
14
14
  };
15
+ /**
16
+ * Optional `number` and `boolean` values are boxed in `NSNumber`, both in the
17
+ * React Native codegen spec and because Swift `Double?` and `Bool?` can't be
18
+ * represented in an `@objc` protocol.
19
+ */
20
+ const BOXED_OPTIONAL_TS_TYPES = new Set(['number', 'boolean']);
15
21
  function mapTsTypeToObjC(tsType, nullable = false, options = {}) {
16
22
  if (tsType.startsWith('Promise<')) {
17
23
  return 'void';
@@ -19,6 +25,9 @@ function mapTsTypeToObjC(tsType, nullable = false, options = {}) {
19
25
  if (options.modelTypeNames?.includes(tsType)) {
20
26
  return nullable ? 'NSDictionary * _Nullable' : 'NSDictionary *';
21
27
  }
28
+ if (nullable && BOXED_OPTIONAL_TS_TYPES.has(tsType)) {
29
+ return 'NSNumber * _Nullable';
30
+ }
22
31
  const mapped = TS_TO_OBJC_TYPE[tsType];
23
32
  if (mapped) {
24
33
  if (nullable && mapped.includes('*')) {
@@ -41,6 +50,9 @@ function mapTsTypeToSwift(tsType, optional = false, options = {}) {
41
50
  const inner = tsType.slice(8, -1);
42
51
  return mapTsTypeToSwift(inner, optional, options);
43
52
  }
53
+ if (optional && BOXED_OPTIONAL_TS_TYPES.has(tsType)) {
54
+ return 'NSNumber?';
55
+ }
44
56
  const mapped = TS_TO_SWIFT_TYPE[tsType];
45
57
  if (mapped) {
46
58
  return optional ? `${mapped}?` : mapped;
package/dist/types.d.ts CHANGED
@@ -20,6 +20,21 @@ export type BrownieConfig = {
20
20
  */
21
21
  kotlinPackageName?: string;
22
22
  };
23
+ /**
24
+ * Extra XCFramework declared for inclusion as a binary target in the
25
+ * local SPM package generated by `package:ios --add-spm-package`.
26
+ */
27
+ export type ExtraSpmXcframework = {
28
+ /**
29
+ * Binary target / module name used in Package.swift.
30
+ * @pattern ^[A-Za-z_][A-Za-z0-9_]*$
31
+ */
32
+ name: string;
33
+ /**
34
+ * Path to the XCFramework directory, absolute or relative to the project root.
35
+ */
36
+ path: string;
37
+ };
23
38
  export type PackageIosOptions = Omit<AppleBuildFlags, 'archive' | 'local'> & {
24
39
  /** Set when `--use-prebuilt-rn-core` is passed; omitted when the flag is absent (Rock applies RN version defaults). */
25
40
  usePrebuiltRnCore?: boolean;
@@ -27,6 +42,11 @@ export type PackageIosOptions = Omit<AppleBuildFlags, 'archive' | 'local'> & {
27
42
  usePrebuiltExpo?: boolean;
28
43
  /** When set, generate a local Swift Package Manager manifest next to the packaged XCFramework outputs. */
29
44
  addSpmPackage?: boolean;
45
+ /**
46
+ * Additional XCFrameworks copied into the generated local SPM package as
47
+ * binary targets. Only consumed when `--add-spm-package` is set. Defaults to [].
48
+ */
49
+ extraSpmXcframeworks?: ExtraSpmXcframework[];
30
50
  };
31
51
  export type BrownfieldPackageAndroidOptions = BrownfieldCommonOptions & Partial<PackageAarFlags>;
32
52
  export type BrownfieldPublishAndroidOptions = BrownfieldCommonOptions & Partial<PublishLocalAarFlags>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,IAAI,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAErF,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1C,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC;IAC5C;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAAC;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG;IAC3E,uHAAuH;IACvH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,sHAAsH;IACtH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,0GAA0G;IAC1G,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,uBAAuB,GACnE,OAAO,CAAC,eAAe,CAAC,CAAC;AAC3B,MAAM,MAAM,+BAA+B,GAAG,uBAAuB,GACnE,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAChC,MAAM,MAAM,2BAA2B,GAAG,uBAAuB,GAC/D,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE7B;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE9B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;IAE1D;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,IAAI,CACxC,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,EACxD,MAAM,uBAAuB,CAC9B,GAAG;IACF;;OAEG;IACH,IAAI,CAAC,EAAE,2BAA2B,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,OAAO,CAAC,iBAAiB,CAAC,EAC1B,MAAM,uBAAuB,CAC9B,GAAG;IACF;;OAEG;IACH,IAAI,CAAC,EAAE,uBAAuB,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,wBAAwB,GACrD,uBAAuB,GACvB,OAAO,CAAC;IACN;;OAEG;IACH,OAAO,EAAE,uBAAuB,CAAC;IAEjC;;OAEG;IACH,GAAG,EAAE,mBAAmB,CAAC;IAEzB;;OAEG;IACH,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@callstack/brownfield-cli",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "license": "MIT",
5
5
  "author": "Artur Morys-Magiera <artus9033@gmail.com>",
6
6
  "bin": {
package/schema.json CHANGED
@@ -183,6 +183,13 @@
183
183
  },
184
184
  "type": "array"
185
185
  },
186
+ "extraSpmXcframeworks": {
187
+ "description": "Additional XCFrameworks copied into the generated local SPM package as binary targets. Only consumed when `--add-spm-package` is set. Defaults to [].",
188
+ "items": {
189
+ "$ref": "#/definitions/ExtraSpmXcframework"
190
+ },
191
+ "type": "array"
192
+ },
186
193
  "installPods": {
187
194
  "description": "Whether to install CocoaPods.",
188
195
  "type": "boolean"
@@ -223,6 +230,23 @@
223
230
  }
224
231
  },
225
232
  "type": "object"
233
+ },
234
+ "ExtraSpmXcframework": {
235
+ "additionalProperties": false,
236
+ "description": "Extra XCFramework declared for inclusion as a binary target in the local SPM package generated by `package:ios --add-spm-package`.",
237
+ "properties": {
238
+ "name": {
239
+ "description": "Binary target / module name used in Package.swift.",
240
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
241
+ "type": "string"
242
+ },
243
+ "path": {
244
+ "description": "Path to the XCFramework directory, absolute or relative to the project root.",
245
+ "type": "string"
246
+ }
247
+ },
248
+ "required": ["name", "path"],
249
+ "type": "object"
226
250
  }
227
251
  }
228
252
  }
@@ -449,6 +449,8 @@ export const packageIosCommand = curryOptions(
449
449
  packageDir,
450
450
  frameworkName: frameworkName ?? undefined,
451
451
  usePrebuiltExpo: options.usePrebuiltExpo,
452
+ projectRoot,
453
+ extraSpmXcframeworks: options.extraSpmXcframeworks ?? [],
452
454
  });
453
455
 
454
456
  logger.success(
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ import type { ExtraSpmXcframework } from '../../types.js';
4
5
  import {
5
6
  prepareLocalSpmArtifacts,
6
7
  SPM_ARTIFACTS_DIR_NAME,
@@ -14,6 +15,8 @@ type CreateLocalSpmPackageOptions = {
14
15
  packageDir: string;
15
16
  frameworkName?: string;
16
17
  usePrebuiltExpo?: boolean;
18
+ projectRoot?: string;
19
+ extraSpmXcframeworks?: ExtraSpmXcframework[];
17
20
  };
18
21
 
19
22
  type CreateLocalSpmPackageResult = {
@@ -31,6 +34,11 @@ const RESERVED_FRAMEWORK_NAMES = new Set([
31
34
  ...ALL_EXPO_SUPPORT_XCFRAMEWORK_NAMES,
32
35
  ]);
33
36
 
37
+ const SWIFT_TARGET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
38
+ const RESERVED_FRAMEWORK_NAME_KEYS = new Set(
39
+ [...RESERVED_FRAMEWORK_NAMES].map((name) => name.toLowerCase())
40
+ );
41
+
34
42
  function requireXcframework(packageDir: string, name: string) {
35
43
  const xcframeworkPath = path.join(packageDir, `${name}.xcframework`);
36
44
 
@@ -56,6 +64,7 @@ function requireHermesXcframework(packageDir: string) {
56
64
 
57
65
  function resolveAppFrameworkName(
58
66
  packageDir: string,
67
+ reservedFrameworkNames: Set<string>,
59
68
  explicitFrameworkName?: string
60
69
  ) {
61
70
  if (explicitFrameworkName) {
@@ -68,7 +77,7 @@ function resolveAppFrameworkName(
68
77
  (entry) =>
69
78
  entry.isDirectory() &&
70
79
  entry.name.endsWith('.xcframework') &&
71
- !RESERVED_FRAMEWORK_NAMES.has(path.basename(entry.name, '.xcframework'))
80
+ !reservedFrameworkNames.has(path.basename(entry.name, '.xcframework'))
72
81
  )
73
82
  .map((entry) => path.basename(entry.name, '.xcframework'))
74
83
  .sort();
@@ -88,6 +97,78 @@ function resolveAppFrameworkName(
88
97
  );
89
98
  }
90
99
 
100
+ function resolveExtraSpmXcframeworkPath(
101
+ projectRoot: string,
102
+ entry: ExtraSpmXcframework
103
+ ) {
104
+ return path.isAbsolute(entry.path)
105
+ ? entry.path
106
+ : path.resolve(projectRoot, entry.path);
107
+ }
108
+
109
+ function validateExtraSpmXcframeworks(
110
+ extraSpmXcframeworks: ExtraSpmXcframework[],
111
+ frameworkName?: string
112
+ ) {
113
+ const nameKeys = new Set<string>();
114
+
115
+ for (const entry of extraSpmXcframeworks) {
116
+ if (!SWIFT_TARGET_NAME_PATTERN.test(entry.name)) {
117
+ throw new Error(
118
+ `Invalid extra SPM XCFramework name "${entry.name}". Names must start with a letter or underscore and contain only letters, numbers, and underscores.`
119
+ );
120
+ }
121
+
122
+ const nameKey = entry.name.toLowerCase();
123
+ if (
124
+ RESERVED_FRAMEWORK_NAME_KEYS.has(nameKey) ||
125
+ nameKey === frameworkName?.toLowerCase()
126
+ ) {
127
+ throw new Error(
128
+ `Extra SPM XCFramework name "${entry.name}" conflicts with a generated package target.`
129
+ );
130
+ }
131
+
132
+ if (nameKeys.has(nameKey)) {
133
+ throw new Error(`Duplicate extra SPM XCFramework name: ${entry.name}`);
134
+ }
135
+
136
+ nameKeys.add(nameKey);
137
+ }
138
+ }
139
+
140
+ function stageExtraSpmXcframeworks({
141
+ packageDir,
142
+ projectRoot,
143
+ extraSpmXcframeworks,
144
+ }: {
145
+ packageDir: string;
146
+ projectRoot: string;
147
+ extraSpmXcframeworks: ExtraSpmXcframework[];
148
+ }) {
149
+ const stagedNames: string[] = [];
150
+
151
+ for (const entry of extraSpmXcframeworks) {
152
+ const sourcePath = resolveExtraSpmXcframeworkPath(projectRoot, entry);
153
+
154
+ if (!fs.existsSync(sourcePath)) {
155
+ throw new Error(
156
+ `Missing extra SPM XCFramework "${entry.name}" at ${entry.path}`
157
+ );
158
+ }
159
+
160
+ const destinationPath = path.join(packageDir, `${entry.name}.xcframework`);
161
+ fs.rmSync(destinationPath, { recursive: true, force: true });
162
+ fs.cpSync(fs.realpathSync(sourcePath), destinationPath, {
163
+ recursive: true,
164
+ verbatimSymlinks: true,
165
+ });
166
+ stagedNames.push(entry.name);
167
+ }
168
+
169
+ return stagedNames;
170
+ }
171
+
91
172
  function renderPackageSwift({
92
173
  packageName,
93
174
  libraryName,
@@ -176,25 +257,43 @@ export function createLocalSpmPackage({
176
257
  packageDir,
177
258
  frameworkName,
178
259
  usePrebuiltExpo = true,
260
+ projectRoot = process.cwd(),
261
+ extraSpmXcframeworks = [],
179
262
  }: CreateLocalSpmPackageOptions): CreateLocalSpmPackageResult {
263
+ validateExtraSpmXcframeworks(extraSpmXcframeworks, frameworkName);
264
+ const reservedFrameworkNames = new Set([
265
+ ...RESERVED_FRAMEWORK_NAMES,
266
+ ...extraSpmXcframeworks.map((entry) => entry.name),
267
+ ]);
268
+ const extraTargetNames = stageExtraSpmXcframeworks({
269
+ packageDir,
270
+ projectRoot,
271
+ extraSpmXcframeworks,
272
+ });
180
273
  const resolvedFrameworkName = resolveAppFrameworkName(
181
274
  packageDir,
275
+ reservedFrameworkNames,
182
276
  frameworkName
183
277
  );
184
278
  const expoSupportXcframeworkNames =
185
279
  getExpoSupportXcframeworkNames(usePrebuiltExpo);
186
280
  const targetNames = [
187
- resolvedFrameworkName,
188
- requireHermesXcframework(packageDir),
189
- requireXcframework(packageDir, 'ReactBrownfield'),
190
- optionalXcframework(packageDir, 'Brownie'),
191
- optionalXcframework(packageDir, 'BrownfieldNavigation'),
192
- ...expoSupportXcframeworkNames.map((frameworkName) =>
193
- optionalXcframework(packageDir, frameworkName)
281
+ ...new Set(
282
+ [
283
+ resolvedFrameworkName,
284
+ requireHermesXcframework(packageDir),
285
+ requireXcframework(packageDir, 'ReactBrownfield'),
286
+ optionalXcframework(packageDir, 'Brownie'),
287
+ optionalXcframework(packageDir, 'BrownfieldNavigation'),
288
+ ...expoSupportXcframeworkNames.map((expoFrameworkName) =>
289
+ optionalXcframework(packageDir, expoFrameworkName)
290
+ ),
291
+ optionalXcframework(packageDir, 'React'),
292
+ optionalXcframework(packageDir, 'ReactNativeDependencies'),
293
+ ...extraTargetNames,
294
+ ].filter((targetName): targetName is string => targetName !== null)
194
295
  ),
195
- optionalXcframework(packageDir, 'React'),
196
- optionalXcframework(packageDir, 'ReactNativeDependencies'),
197
- ].filter((targetName): targetName is string => targetName !== null);
296
+ ];
198
297
 
199
298
  const packageManifestPath = path.join(packageDir, 'Package.swift');
200
299
  const readmePath = path.join(packageDir, 'README.md');
@@ -16,6 +16,13 @@ const TS_TO_SWIFT_TYPE: Record<string, string> = {
16
16
  Object: '[String: Any]',
17
17
  };
18
18
 
19
+ /**
20
+ * Optional `number` and `boolean` values are boxed in `NSNumber`, both in the
21
+ * React Native codegen spec and because Swift `Double?` and `Bool?` can't be
22
+ * represented in an `@objc` protocol.
23
+ */
24
+ const BOXED_OPTIONAL_TS_TYPES = new Set(['number', 'boolean']);
25
+
19
26
  interface ObjCTypeMappingOptions {
20
27
  modelTypeNames?: string[];
21
28
  }
@@ -33,6 +40,10 @@ function mapTsTypeToObjC(
33
40
  return nullable ? 'NSDictionary * _Nullable' : 'NSDictionary *';
34
41
  }
35
42
 
43
+ if (nullable && BOXED_OPTIONAL_TS_TYPES.has(tsType)) {
44
+ return 'NSNumber * _Nullable';
45
+ }
46
+
36
47
  const mapped = TS_TO_OBJC_TYPE[tsType];
37
48
  if (mapped) {
38
49
  if (nullable && mapped.includes('*')) {
@@ -73,6 +84,10 @@ function mapTsTypeToSwift(
73
84
  return mapTsTypeToSwift(inner, optional, options);
74
85
  }
75
86
 
87
+ if (optional && BOXED_OPTIONAL_TS_TYPES.has(tsType)) {
88
+ return 'NSNumber?';
89
+ }
90
+
76
91
  const mapped = TS_TO_SWIFT_TYPE[tsType];
77
92
  if (mapped) {
78
93
  return optional ? `${mapped}?` : mapped;
package/src/types.ts CHANGED
@@ -29,6 +29,23 @@ export type BrownieConfig = {
29
29
  kotlinPackageName?: string;
30
30
  };
31
31
 
32
+ /**
33
+ * Extra XCFramework declared for inclusion as a binary target in the
34
+ * local SPM package generated by `package:ios --add-spm-package`.
35
+ */
36
+ export type ExtraSpmXcframework = {
37
+ /**
38
+ * Binary target / module name used in Package.swift.
39
+ * @pattern ^[A-Za-z_][A-Za-z0-9_]*$
40
+ */
41
+ name: string;
42
+
43
+ /**
44
+ * Path to the XCFramework directory, absolute or relative to the project root.
45
+ */
46
+ path: string;
47
+ };
48
+
32
49
  export type PackageIosOptions = Omit<AppleBuildFlags, 'archive' | 'local'> & {
33
50
  /** Set when `--use-prebuilt-rn-core` is passed; omitted when the flag is absent (Rock applies RN version defaults). */
34
51
  usePrebuiltRnCore?: boolean;
@@ -38,6 +55,12 @@ export type PackageIosOptions = Omit<AppleBuildFlags, 'archive' | 'local'> & {
38
55
 
39
56
  /** When set, generate a local Swift Package Manager manifest next to the packaged XCFramework outputs. */
40
57
  addSpmPackage?: boolean;
58
+
59
+ /**
60
+ * Additional XCFrameworks copied into the generated local SPM package as
61
+ * binary targets. Only consumed when `--add-spm-package` is set. Defaults to [].
62
+ */
63
+ extraSpmXcframeworks?: ExtraSpmXcframework[];
41
64
  };
42
65
 
43
66
  export type BrownfieldPackageAndroidOptions = BrownfieldCommonOptions &