From f4f7330541acf7e60645c428bf5818d86e0c4227 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 7 Jan 2026 20:19:05 +0100 Subject: [PATCH 01/16] feat: expose RockCLIOptions type --- .../platform-android/src/lib/commands/aar/packageAar.ts | 3 ++- .../src/lib/commands/aar/publishLocalAar.ts | 3 ++- .../src/lib/commands/buildAndroid/buildAndroid.ts | 4 ++-- .../src/lib/commands/build/buildHarmony.ts | 4 ++-- packages/tools/src/index.ts | 1 + packages/tools/src/lib/types.ts | 8 ++++++++ 6 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 packages/tools/src/lib/types.ts diff --git a/packages/platform-android/src/lib/commands/aar/packageAar.ts b/packages/platform-android/src/lib/commands/aar/packageAar.ts index 9ae9f7dbb..83c066974 100644 --- a/packages/platform-android/src/lib/commands/aar/packageAar.ts +++ b/packages/platform-android/src/lib/commands/aar/packageAar.ts @@ -1,3 +1,4 @@ +import type { RockCLIOptions } from '@rock-js/tools'; import { outro } from '@rock-js/tools'; import { runGradleAar } from '../runGradle.js'; import { toPascalCase } from '../toPascalCase.js'; @@ -54,4 +55,4 @@ export const options = [ name: '--module-name ', description: 'AAR module name', }, -]; +] satisfies RockCLIOptions; diff --git a/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts b/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts index 32285d3cc..90c73d2f1 100644 --- a/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts +++ b/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts @@ -1,3 +1,4 @@ +import type { RockCLIOptions } from '@rock-js/tools'; import { outro } from '@rock-js/tools'; import { runGradleAar } from '../runGradle.js'; import type { AarProject } from './packageAar.js'; @@ -17,4 +18,4 @@ export const options = [ name: '--module-name ', description: 'AAR module name', }, -]; +] satisfies RockCLIOptions; diff --git a/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts b/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts index 20585fedb..50cfd9584 100644 --- a/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts +++ b/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts @@ -1,5 +1,5 @@ import type { AndroidProjectConfig } from '@react-native-community/cli-types'; -import type { RemoteBuildCache } from '@rock-js/tools'; +import type { RemoteBuildCache, RockCLIOptions } from '@rock-js/tools'; import { colorLink, type FingerprintSources, @@ -104,4 +104,4 @@ export const options = [ name: '--local', description: 'Force local build with Gradle wrapper.', }, -]; +] satisfies RockCLIOptions; diff --git a/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts b/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts index ec0df0b33..f0f43e904 100644 --- a/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts +++ b/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts @@ -1,4 +1,4 @@ -import type { RemoteBuildCache } from '@rock-js/tools'; +import type { RemoteBuildCache, RockCLIOptions } from '@rock-js/tools'; import { colorLink, type FingerprintSources, @@ -96,4 +96,4 @@ export const options = [ description: 'OpenHarmony product defined in build-profile.json5.', default: 'default', }, -]; +] satisfies RockCLIOptions; diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 04220a8a5..fd10eb451 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -7,6 +7,7 @@ export { default as cacheManager } from './lib/cacheManager.js'; export * from './lib/parse-args.js'; export * from './lib/path.js'; export * from './lib/project.js'; +export * from './lib/types.js'; export * from './lib/build-cache/common.js'; export * from './lib/build-cache/localBuildCache.js'; export { getBinaryPath } from './lib/build-cache/getBinaryPath.js'; diff --git a/packages/tools/src/lib/types.ts b/packages/tools/src/lib/types.ts new file mode 100644 index 000000000..7390d0a98 --- /dev/null +++ b/packages/tools/src/lib/types.ts @@ -0,0 +1,8 @@ +export type RockCLIOption = { + name: string; + description: string; + default?: string; + parse?: (args: string) => string | string[]; +}; + +export type RockCLIOptions = RockCLIOption[]; From de0a5e21040e81c879413a63f8c6f11f47561682 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 16:28:21 +0100 Subject: [PATCH 02/16] feat: expose brownfield plugin actions --- .../src/lib/pluginBrownfieldAndroid.ts | 65 +++--- .../src/lib/pluginBrownfieldIos.ts | 216 +++++++++--------- 2 files changed, 150 insertions(+), 131 deletions(-) diff --git a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts index 991b4fe5c..a9c9c8227 100644 --- a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts +++ b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts @@ -20,45 +20,58 @@ const getAarConfig = ( }; return config; }; + +export const pluginBrownfieldAndroidPackageAction = async ( + args: PackageAarFlags, + apiSubset: Pick, + pluginConfig?: AndroidProjectConfig, +) => { + intro('Creating an AAR file'); + + const androidConfig = projectConfig(apiSubset.getProjectRoot(), pluginConfig); + + if (androidConfig) { + const config = getAarConfig(args, androidConfig); + await packageAar(config, args); + } else { + throw new RockError('Android project not found.'); + } +}; + +export const pluginBrownfieldAndroidPublishAction = async ( + args: PackageAarFlags, + apiSubset: Pick, + pluginConfig?: AndroidProjectConfig, +) => { + intro('Publishing AAR'); + + const androidConfig = projectConfig(apiSubset.getProjectRoot(), pluginConfig); + + if (androidConfig) { + const config = getAarConfig(args, androidConfig); + await publishLocalAar(config); + } else { + throw new RockError('Android project not found.'); + } +}; + export const pluginBrownfieldAndroid = (pluginConfig?: AndroidProjectConfig) => (api: PluginApi): PluginOutput => { - const projectRoot = api.getProjectRoot(); - api.registerCommand({ name: 'package:aar', description: 'Produces an AAR file suitable for including React Native app in native projects.', - action: async (args: PackageAarFlags) => { - intro('Creating an AAR file'); - - const androidConfig = projectConfig(projectRoot, pluginConfig); - - if (androidConfig) { - const config = getAarConfig(args, androidConfig); - await packageAar(config, args); - } else { - throw new RockError('Android project not found.'); - } - }, + action: (args: PackageAarFlags) => + pluginBrownfieldAndroidPackageAction(args, api, pluginConfig), options: packageAarOptions, }); api.registerCommand({ name: 'publish-local:aar', description: 'Publishes a AAR to local maven repo', - action: async (args: PackageAarFlags) => { - intro('Publishing AAR'); - - const androidConfig = projectConfig(projectRoot, pluginConfig); - - if (androidConfig) { - const config = getAarConfig(args, androidConfig); - await publishLocalAar(config); - } else { - throw new RockError('Android project not found.'); - } - }, + action: async (args: PackageAarFlags) => + pluginBrownfieldAndroidPublishAction(args, api, pluginConfig), options: publishLocalAarOptions, }); diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index 6e9906d96..92790db0f 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -22,117 +22,123 @@ import { copyHermesXcframework } from './copyHermesXcframework.js'; const buildOptions = getBuildOptions({ platformName: 'ios' }); +export const pluginBrownfieldIosPackageAction = async ( + args: BuildFlags, + apiSubset: Pick< + PluginApi, + | 'getReactNativeVersion' + | 'getProjectRoot' + | 'getReactNativePath' + | 'getFingerprintOptions' + | 'getRemoteCacheProvider' + | 'getUsePrebuiltRNCore' + >, + pluginConfig?: IOSProjectConfig, +) => { + intro('Packaging iOS project'); + + // 1) Build the project + const projectRoot = apiSubset.getProjectRoot(); + const iosConfig = getValidProjectConfig('ios', projectRoot, pluginConfig); + const { derivedDataDir } = getBuildPaths('ios'); + + const destination = args.destination ?? [ + genericDestinations.ios.device, + genericDestinations.ios.simulator, + ]; + + const buildFolder = args.buildFolder ?? derivedDataDir; + const configuration = args.configuration ?? 'Debug'; + let scheme; + + try { + const { appPath, ...buildAppResult } = await buildApp({ + projectRoot, + projectConfig: iosConfig, + platformName: 'ios', + args: { ...args, destination, buildFolder }, + reactNativePath: apiSubset.getReactNativePath(), + brownfield: true, + usePrebuiltRNCore: apiSubset.getUsePrebuiltRNCore(), + }); + logger.log(`Build available at: ${colorLink(relativeToCwd(appPath))}`); + + scheme = buildAppResult.scheme; + } catch (error) { + const message = `Failed to create ${args.archive ? 'archive' : 'build'}`; + throw new RockError(message, { cause: error }); + } + + // 2) Merge the .framework outputs of the framework target + const productsPath = path.join(buildFolder, 'Build', 'Products'); + const { packageDir: frameworkTargetOutputDir } = getBuildPaths('ios'); + const { sourceDir } = iosConfig; + + await mergeFrameworks({ + sourceDir, + frameworkPaths: [ + path.join( + productsPath, + `${configuration}-iphoneos`, + `${scheme}.framework`, + ), + path.join( + productsPath, + `${configuration}-iphonesimulator`, + `${scheme}.framework`, + ), + ], + outputPath: path.join(frameworkTargetOutputDir, `${scheme}.xcframework`), + }); + + // 3) Merge React Native Brownfield paths + await mergeFrameworks({ + sourceDir, + frameworkPaths: [ + path.join( + productsPath, + `${configuration}-iphoneos`, + 'ReactBrownfield', + 'ReactBrownfield.framework', + ), + path.join( + productsPath, + `${configuration}-iphonesimulator`, + 'ReactBrownfield', + 'ReactBrownfield.framework', + ), + ], + outputPath: path.join( + frameworkTargetOutputDir, + 'ReactBrownfield.xcframework', + ), + }); + + // 4) Copy hermes xcframework to the output path + copyHermesXcframework({ + sourceDir, + destinationDir: frameworkTargetOutputDir, + reactNativeVersion: apiSubset.getReactNativeVersion(), + }); + + // 5) Inform the user + logger.log( + `XCFrameworks are available at: ${colorLink( + relativeToCwd(frameworkTargetOutputDir), + )}`, + ); + + outro('Success 🎉.'); +}; + export const pluginBrownfieldIos = (pluginConfig?: IOSProjectConfig) => (api: PluginApi): PluginOutput => { api.registerCommand({ name: 'package:ios', description: 'Emit a .xcframework file from React Native code.', - action: async (args: BuildFlags) => { - intro('Packaging iOS project'); - - // 1) Build the project - const projectRoot = api.getProjectRoot(); - const iosConfig = getValidProjectConfig( - 'ios', - projectRoot, - pluginConfig, - ); - const { derivedDataDir } = getBuildPaths('ios'); - - const destination = args.destination ?? [ - genericDestinations.ios.device, - genericDestinations.ios.simulator, - ]; - - const buildFolder = args.buildFolder ?? derivedDataDir; - const configuration = args.configuration ?? 'Debug'; - let scheme; - - try { - const { appPath, ...buildAppResult } = await buildApp({ - projectRoot, - projectConfig: iosConfig, - platformName: 'ios', - args: { ...args, destination, buildFolder }, - reactNativePath: api.getReactNativePath(), - brownfield: true, - usePrebuiltRNCore: api.getUsePrebuiltRNCore(), - }); - logger.log( - `Build available at: ${colorLink(relativeToCwd(appPath))}`, - ); - - scheme = buildAppResult.scheme; - } catch (error) { - const message = `Failed to create ${args.archive ? 'archive' : 'build'}`; - throw new RockError(message, { cause: error }); - } - - // 2) Merge the .framework outputs of the framework target - const productsPath = path.join(buildFolder, 'Build', 'Products'); - const { packageDir: frameworkTargetOutputDir } = getBuildPaths('ios'); - const { sourceDir } = iosConfig; - - await mergeFrameworks({ - sourceDir, - frameworkPaths: [ - path.join( - productsPath, - `${configuration}-iphoneos`, - `${scheme}.framework`, - ), - path.join( - productsPath, - `${configuration}-iphonesimulator`, - `${scheme}.framework`, - ), - ], - outputPath: path.join( - frameworkTargetOutputDir, - `${scheme}.xcframework`, - ), - }); - - // 3) Merge React Native Brownfield paths - await mergeFrameworks({ - sourceDir, - frameworkPaths: [ - path.join( - productsPath, - `${configuration}-iphoneos`, - 'ReactBrownfield', - 'ReactBrownfield.framework', - ), - path.join( - productsPath, - `${configuration}-iphonesimulator`, - 'ReactBrownfield', - 'ReactBrownfield.framework', - ), - ], - outputPath: path.join( - frameworkTargetOutputDir, - 'ReactBrownfield.xcframework', - ), - }); - - // 4) Copy hermes xcframework to the output path - copyHermesXcframework({ - sourceDir, - destinationDir: frameworkTargetOutputDir, - reactNativeVersion: api.getReactNativeVersion(), - }); - - // 5) Inform the user - logger.log( - `XCFrameworks are available at: ${colorLink( - relativeToCwd(frameworkTargetOutputDir), - )}`, - ); - - outro('Success 🎉.'); - }, + action: (args: BuildFlags) => + pluginBrownfieldIosPackageAction(args, api, pluginConfig), options: buildOptions, }); From 6299d52d95c0795e34795c17b37a5b4b0ac78b9f Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 16:43:50 +0100 Subject: [PATCH 03/16] refactor: use RockCLIOptions type in platform-ios --- packages/platform-ios/src/lib/commands/signIos.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/platform-ios/src/lib/commands/signIos.ts b/packages/platform-ios/src/lib/commands/signIos.ts index 3d21e88ae..6ea7e87cf 100644 --- a/packages/platform-ios/src/lib/commands/signIos.ts +++ b/packages/platform-ios/src/lib/commands/signIos.ts @@ -1,5 +1,6 @@ import type { PluginApi } from '@rock-js/config'; import { modifyApp, modifyIpa } from '@rock-js/platform-apple-helpers'; +import type { RockCLIOptions } from 'packages/tools/dist/src/index.js'; export type SignFlags = { app: string; @@ -50,7 +51,7 @@ const OPTIONS = [ description: 'Extract app bundle codesigning entitlements and combine with entitlements from new provisioning profile.', }, -]; +] satisfies RockCLIOptions; export const registerSignCommand = (api: PluginApi) => { api.registerCommand({ From 416180f19910c840c154d0b850ae43692d708449 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 16:43:58 +0100 Subject: [PATCH 04/16] chore: add changeset --- .changeset/itchy-spoons-trade.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/itchy-spoons-trade.md diff --git a/.changeset/itchy-spoons-trade.md b/.changeset/itchy-spoons-trade.md new file mode 100644 index 000000000..9aa7903a1 --- /dev/null +++ b/.changeset/itchy-spoons-trade.md @@ -0,0 +1,6 @@ +--- +'@rock-js/plugin-brownfield-android': patch +'@rock-js/plugin-brownfield-ios': patch +--- + +feat: expose logic of brownfield plugin actions From 457d29474449ed06eadd9fcb9aeb45687db08a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 9 Jan 2026 17:18:40 +0100 Subject: [PATCH 05/16] refactor fn signatures to be more explicit instead of using api --- .../src/lib/commands/aar/packageAar.ts | 29 +++------ .../platform-ios/src/lib/commands/signIos.ts | 2 +- .../src/lib/pluginBrownfieldAndroid.ts | 63 ++++++++++++------- .../src/lib/pluginBrownfieldIos.ts | 42 ++++++++----- 4 files changed, 78 insertions(+), 58 deletions(-) diff --git a/packages/platform-android/src/lib/commands/aar/packageAar.ts b/packages/platform-android/src/lib/commands/aar/packageAar.ts index 83c066974..6b7b68ab4 100644 --- a/packages/platform-android/src/lib/commands/aar/packageAar.ts +++ b/packages/platform-android/src/lib/commands/aar/packageAar.ts @@ -13,35 +13,24 @@ export type PackageAarFlags = { moduleName?: string; }; -export async function packageAar( - aarProject: AarProject, - args: PackageAarFlags, -) { - normalizeArgs(args); +export async function packageAar(aarProject: AarProject, variant: string) { + normalizeVariant(variant); + const tasks = [`assemble${toPascalCase(variant)}`]; - const tasks = [`assemble${toPascalCase(args.variant)}`]; - - await runGradleAar({ tasks, aarProject, variant: args.variant }); + await runGradleAar({ tasks, aarProject, variant }); outro('Success 🎉.'); } -export async function localPublishAar( - aarProject: AarProject, - args: PackageAarFlags, -) { +export async function localPublishAar(aarProject: AarProject, variant: string) { const tasks = ['publishToMavenLocal']; - await runGradleAar({ - tasks, - aarProject, - variant: args.variant, - }); + await runGradleAar({ tasks, aarProject, variant }); outro('Success 🎉.'); } -function normalizeArgs(args: PackageAarFlags) { - if (!args.variant) { - args.variant = 'debug'; +function normalizeVariant(variant: string) { + if (!variant) { + variant = 'debug'; } } diff --git a/packages/platform-ios/src/lib/commands/signIos.ts b/packages/platform-ios/src/lib/commands/signIos.ts index 6ea7e87cf..efd5719e2 100644 --- a/packages/platform-ios/src/lib/commands/signIos.ts +++ b/packages/platform-ios/src/lib/commands/signIos.ts @@ -1,6 +1,6 @@ import type { PluginApi } from '@rock-js/config'; import { modifyApp, modifyIpa } from '@rock-js/platform-apple-helpers'; -import type { RockCLIOptions } from 'packages/tools/dist/src/index.js'; +import type { RockCLIOptions } from '@rock-js/tools'; export type SignFlags = { app: string; diff --git a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts index a9c9c8227..f8d20d063 100644 --- a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts +++ b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts @@ -11,44 +11,54 @@ import { import { intro, RockError } from '@rock-js/tools'; const getAarConfig = ( - args: PackageAarFlags, + moduleName: string | undefined, androidConfig: AndroidProjectConfig, ) => { const config = { sourceDir: androidConfig.sourceDir, - moduleName: args.moduleName ?? '', + moduleName: moduleName ?? '', }; return config; }; -export const pluginBrownfieldAndroidPackageAction = async ( - args: PackageAarFlags, - apiSubset: Pick, - pluginConfig?: AndroidProjectConfig, -) => { +export const packageAarAction = async ({ + variant, + moduleName, + projectRoot, + pluginConfig, +}: { + variant: string; + moduleName: string | undefined; + projectRoot: string; + pluginConfig?: AndroidProjectConfig; +}) => { intro('Creating an AAR file'); - const androidConfig = projectConfig(apiSubset.getProjectRoot(), pluginConfig); + const androidConfig = projectConfig(projectRoot, pluginConfig); if (androidConfig) { - const config = getAarConfig(args, androidConfig); - await packageAar(config, args); + const config = getAarConfig(moduleName, androidConfig); + await packageAar(config, variant); } else { throw new RockError('Android project not found.'); } }; -export const pluginBrownfieldAndroidPublishAction = async ( - args: PackageAarFlags, - apiSubset: Pick, - pluginConfig?: AndroidProjectConfig, -) => { +export const publishLocalAarAction = async ({ + moduleName, + projectRoot, + pluginConfig, +}: { + moduleName: string | undefined; + projectRoot: string; + pluginConfig?: AndroidProjectConfig; +}) => { intro('Publishing AAR'); - const androidConfig = projectConfig(apiSubset.getProjectRoot(), pluginConfig); + const androidConfig = projectConfig(projectRoot, pluginConfig); if (androidConfig) { - const config = getAarConfig(args, androidConfig); + const config = getAarConfig(moduleName, androidConfig); await publishLocalAar(config); } else { throw new RockError('Android project not found.'); @@ -62,16 +72,27 @@ export const pluginBrownfieldAndroid = name: 'package:aar', description: 'Produces an AAR file suitable for including React Native app in native projects.', - action: (args: PackageAarFlags) => - pluginBrownfieldAndroidPackageAction(args, api, pluginConfig), + action: (args: PackageAarFlags) => { + return packageAarAction({ + variant: args.variant, + moduleName: args.moduleName, + projectRoot: api.getProjectRoot(), + pluginConfig, + }); + }, options: packageAarOptions, }); api.registerCommand({ name: 'publish-local:aar', description: 'Publishes a AAR to local maven repo', - action: async (args: PackageAarFlags) => - pluginBrownfieldAndroidPublishAction(args, api, pluginConfig), + action: async (args: PackageAarFlags) => { + return publishLocalAarAction({ + moduleName: args.moduleName, + projectRoot: api.getProjectRoot(), + pluginConfig, + }); + }, options: publishLocalAarOptions, }); diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index 92790db0f..ae4d58d0e 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -22,23 +22,24 @@ import { copyHermesXcframework } from './copyHermesXcframework.js'; const buildOptions = getBuildOptions({ platformName: 'ios' }); -export const pluginBrownfieldIosPackageAction = async ( +export const packageIosAction = async ( args: BuildFlags, - apiSubset: Pick< - PluginApi, - | 'getReactNativeVersion' - | 'getProjectRoot' - | 'getReactNativePath' - | 'getFingerprintOptions' - | 'getRemoteCacheProvider' - | 'getUsePrebuiltRNCore' - >, + { + projectRoot, + reactNativePath, + reactNativeVersion, + usePrebuiltRNCore, + }: { + projectRoot: string; + reactNativePath: string; + reactNativeVersion: string; + usePrebuiltRNCore: number | undefined; + }, pluginConfig?: IOSProjectConfig, ) => { intro('Packaging iOS project'); // 1) Build the project - const projectRoot = apiSubset.getProjectRoot(); const iosConfig = getValidProjectConfig('ios', projectRoot, pluginConfig); const { derivedDataDir } = getBuildPaths('ios'); @@ -57,9 +58,9 @@ export const pluginBrownfieldIosPackageAction = async ( projectConfig: iosConfig, platformName: 'ios', args: { ...args, destination, buildFolder }, - reactNativePath: apiSubset.getReactNativePath(), + reactNativePath, brownfield: true, - usePrebuiltRNCore: apiSubset.getUsePrebuiltRNCore(), + usePrebuiltRNCore, }); logger.log(`Build available at: ${colorLink(relativeToCwd(appPath))}`); @@ -118,7 +119,7 @@ export const pluginBrownfieldIosPackageAction = async ( copyHermesXcframework({ sourceDir, destinationDir: frameworkTargetOutputDir, - reactNativeVersion: apiSubset.getReactNativeVersion(), + reactNativeVersion, }); // 5) Inform the user @@ -137,8 +138,17 @@ export const pluginBrownfieldIos = api.registerCommand({ name: 'package:ios', description: 'Emit a .xcframework file from React Native code.', - action: (args: BuildFlags) => - pluginBrownfieldIosPackageAction(args, api, pluginConfig), + action: async (args: BuildFlags) => + packageIosAction( + args, + { + projectRoot: api.getProjectRoot(), + reactNativePath: api.getReactNativePath(), + reactNativeVersion: api.getReactNativeVersion(), + usePrebuiltRNCore: api.getUsePrebuiltRNCore(), + }, + pluginConfig, + ), options: buildOptions, }); From 94abea95c8fa5e6fd515b3181caffc7958cf8368 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 20:58:46 +0100 Subject: [PATCH 06/16] fix(types): add PublishLocalAarFlags --- packages/platform-android/src/index.ts | 1 + .../platform-android/src/lib/commands/aar/publishLocalAar.ts | 4 ++++ .../src/lib/pluginBrownfieldAndroid.ts | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/platform-android/src/index.ts b/packages/platform-android/src/index.ts index b1e9e1690..ed2958d8d 100644 --- a/packages/platform-android/src/index.ts +++ b/packages/platform-android/src/index.ts @@ -5,6 +5,7 @@ export { options as packageAarOptions, } from './lib/commands/aar/packageAar.js'; export { + type PublishLocalAarFlags, publishLocalAar, options as publishLocalAarOptions, } from './lib/commands/aar/publishLocalAar.js'; diff --git a/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts b/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts index 90c73d2f1..6de2ebd75 100644 --- a/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts +++ b/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts @@ -3,6 +3,10 @@ import { outro } from '@rock-js/tools'; import { runGradleAar } from '../runGradle.js'; import type { AarProject } from './packageAar.js'; +export type PublishLocalAarFlags = { + moduleName?: string; +}; + export async function publishLocalAar(aarProject: AarProject) { const tasks = ['publishToMavenLocal']; diff --git a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts index f8d20d063..17194f3df 100644 --- a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts +++ b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts @@ -6,6 +6,7 @@ import { type PackageAarFlags, packageAarOptions, publishLocalAar, + type PublishLocalAarFlags, publishLocalAarOptions, } from '@rock-js/platform-android'; import { intro, RockError } from '@rock-js/tools'; @@ -86,7 +87,7 @@ export const pluginBrownfieldAndroid = api.registerCommand({ name: 'publish-local:aar', description: 'Publishes a AAR to local maven repo', - action: async (args: PackageAarFlags) => { + action: async (args: PublishLocalAarFlags) => { return publishLocalAarAction({ moduleName: args.moduleName, projectRoot: api.getProjectRoot(), From 5bc8c24bc8cd29dae3f4219d0ea6ced28f06f206 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 21:55:03 +0100 Subject: [PATCH 07/16] feat: expose types from @rock-js/platform-apple-helpers --- packages/platform-apple-helpers/src/lib/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/platform-apple-helpers/src/lib/index.ts b/packages/platform-apple-helpers/src/lib/index.ts index f347cd664..bfcd91609 100644 --- a/packages/platform-apple-helpers/src/lib/index.ts +++ b/packages/platform-apple-helpers/src/lib/index.ts @@ -12,3 +12,4 @@ export { getValidProjectConfig } from './utils/getValidProjectConfig.js'; export { promptSigningIdentity } from './utils/signingIdentities.js'; export { buildApp } from './utils/buildApp.js'; export { mergeFrameworks } from './utils/mergeFrameworks.js'; +export type * from './types/index.js'; From d4e260a7692eed4ccd03e54f03f8685e4ae24e9f Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 21:55:28 +0100 Subject: [PATCH 08/16] feat: Rock escape hatch in plugin-brownfield-ios --- .../src/lib/pluginBrownfieldIos.ts | 28 +++++++++++++++++-- .../plugin-brownfield-ios/src/lib/types.ts | 1 + 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-brownfield-ios/src/lib/types.ts diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index ae4d58d0e..ad3975322 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -9,6 +9,7 @@ import { getBuildPaths, getValidProjectConfig, mergeFrameworks, + type ProjectConfig as AppleProjectConfig, } from '@rock-js/platform-apple-helpers'; import { colorLink, @@ -19,6 +20,7 @@ import { RockError, } from '@rock-js/tools'; import { copyHermesXcframework } from './copyHermesXcframework.js'; +import type { RequireAllOrNone } from './types.js'; const buildOptions = getBuildOptions({ platformName: 'ios' }); @@ -36,13 +38,35 @@ export const packageIosAction = async ( usePrebuiltRNCore: number | undefined; }, pluginConfig?: IOSProjectConfig, + /** + * Rock-dependent logic escape hatch. + * If this is not provided, the logic will depend on the presence of a Rock config file. + */ + { + iosConfigOverride, + derivedDataDirOverride, + }: RequireAllOrNone<{ + /** + * Override for iOS project config. + */ + iosConfigOverride: AppleProjectConfig; + + /** + * Override for derivedDataDir path. + */ + derivedDataDirOverride: string; + }> = {}, ) => { intro('Packaging iOS project'); // 1) Build the project - const iosConfig = getValidProjectConfig('ios', projectRoot, pluginConfig); - const { derivedDataDir } = getBuildPaths('ios'); + const iosConfig = + iosConfigOverride ?? + getValidProjectConfig('ios', projectRoot, pluginConfig); + const { derivedDataDir } = derivedDataDirOverride + ? { derivedDataDir: derivedDataDirOverride } + : getBuildPaths('ios'); const destination = args.destination ?? [ genericDestinations.ios.device, genericDestinations.ios.simulator, diff --git a/packages/plugin-brownfield-ios/src/lib/types.ts b/packages/plugin-brownfield-ios/src/lib/types.ts new file mode 100644 index 000000000..82c8b0e53 --- /dev/null +++ b/packages/plugin-brownfield-ios/src/lib/types.ts @@ -0,0 +1 @@ +export type RequireAllOrNone = T | { [K in keyof T]?: never }; From 7da1eb207d94eb61f3a1f7bfd9efa1caf9833548 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Fri, 9 Jan 2026 23:45:22 +0100 Subject: [PATCH 09/16] fix: debug logs in mergeFrameworks not showing path to removed framework before writing merge output --- .changeset/itchy-nails-visit.md | 5 +++++ .../platform-apple-helpers/src/lib/utils/mergeFrameworks.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/itchy-nails-visit.md diff --git a/.changeset/itchy-nails-visit.md b/.changeset/itchy-nails-visit.md new file mode 100644 index 000000000..3e125a51a --- /dev/null +++ b/.changeset/itchy-nails-visit.md @@ -0,0 +1,5 @@ +--- +'@rock-js/platform-apple-helpers': patch +--- + +fix: debug logs in mergeFrameworks not showing path to removed framework before writing merge output diff --git a/packages/platform-apple-helpers/src/lib/utils/mergeFrameworks.ts b/packages/platform-apple-helpers/src/lib/utils/mergeFrameworks.ts index 75b1f74b2..b2103bee4 100644 --- a/packages/platform-apple-helpers/src/lib/utils/mergeFrameworks.ts +++ b/packages/platform-apple-helpers/src/lib/utils/mergeFrameworks.ts @@ -20,7 +20,7 @@ export async function mergeFrameworks({ const xcframeworkName = path.basename(outputPath); if (existsSync(outputPath)) { - logger.debug(`Removing `); + logger.debug(`Removing existing merged framework output at ${outputPath}`); fs.rmSync(outputPath, { recursive: true, force: true }); } From 071af896a9e2c1df698a585ab4a52efa90e01c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Jan 2026 10:24:21 +0100 Subject: [PATCH 10/16] rename FingerprintSources to FingerprintOptions and run prettier --- .github/pr-labeler.yml | 2 +- packages/cli/README.md | 2 +- packages/cli/src/lib/plugins/fingerprint.ts | 4 +- packages/cli/src/lib/plugins/remoteCache.ts | 4 +- packages/config/src/lib/config.ts | 8 ++-- .../__tests__/initInExistingProject.test.ts | 40 ++++++++++++------- .../lib/commands/buildAndroid/buildAndroid.ts | 4 +- .../src/lib/commands/runAndroid/runAndroid.ts | 4 +- .../runAndroid/tryInstallAppOnDevice.ts | 4 +- .../runAndroid/tryLaunchAppOnDevice.ts | 4 +- .../src/lib/commands/build/createBuild.ts | 4 +- .../src/lib/commands/run/createRun.ts | 4 +- .../src/lib/commands/run/runOptions.ts | 3 +- .../src/lib/utils/buildApp.ts | 6 +-- .../src/lib/commands/build/buildHarmony.ts | 4 +- .../src/lib/commands/run/runHarmony.ts | 4 +- packages/tools/src/lib/build-cache/common.ts | 8 ++-- .../src/lib/build-cache/getBinaryPath.ts | 6 +-- packages/tools/src/lib/fingerprint/index.ts | 11 ++--- packages/tools/src/lib/hermes.ts | 7 ++-- templates/rock-template-default/tsconfig.json | 2 +- website/src/docs/remote-cache/introduction.md | 1 - 22 files changed, 70 insertions(+), 66 deletions(-) diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index ddc40cd6f..bfb2fb554 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -3,4 +3,4 @@ 'release: bug fix': - '/^fix/' 'release: breaking change': - - '/^breaking/' \ No newline at end of file + - '/^breaking/' diff --git a/packages/cli/README.md b/packages/cli/README.md index 6b77d0fe4..b99e5555a 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -32,7 +32,7 @@ Choose your path based on your current situation: ### Creating a new project -> [!TIP] +> [!TIP] > For **new projects**, we recommend starting with [Expo](https://expo.dev) for the best developer experience and similar remote caching capabilities. Rock is designed for teams who have outgrown the Community CLI. To create a fresh React Native app with Rock, open a terminal and run: diff --git a/packages/cli/src/lib/plugins/fingerprint.ts b/packages/cli/src/lib/plugins/fingerprint.ts index ab6d20873..98379451c 100644 --- a/packages/cli/src/lib/plugins/fingerprint.ts +++ b/packages/cli/src/lib/plugins/fingerprint.ts @@ -1,7 +1,7 @@ import { createHash } from 'node:crypto'; import { performance } from 'node:perf_hooks'; import type { PluginApi } from '@rock-js/config'; -import type { FingerprintInputHash, FingerprintSources } from '@rock-js/tools'; +import type { FingerprintInputHash, FingerprintOptions } from '@rock-js/tools'; import { color, intro, @@ -39,7 +39,7 @@ type NativeFingerprintCommandOptions = { export async function nativeFingerprintCommand( path: string, - { extraSources, ignorePaths, env }: FingerprintSources, + { extraSources, ignorePaths, env }: FingerprintOptions, options: NativeFingerprintCommandOptions, ) { validateOptions(options); diff --git a/packages/cli/src/lib/plugins/remoteCache.ts b/packages/cli/src/lib/plugins/remoteCache.ts index 26a83b126..8de4173e3 100644 --- a/packages/cli/src/lib/plugins/remoteCache.ts +++ b/packages/cli/src/lib/plugins/remoteCache.ts @@ -2,7 +2,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import type { PluginApi, PluginOutput } from '@rock-js/config'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { color, colorLink, @@ -48,7 +48,7 @@ async function remoteCache({ args: Flags; remoteCacheProvider: null | (() => RemoteBuildCache); projectRoot: string; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; }) { const isJsonOutput = args.json; if (!remoteCacheProvider) { diff --git a/packages/config/src/lib/config.ts b/packages/config/src/lib/config.ts index 363c38892..90787c90b 100644 --- a/packages/config/src/lib/config.ts +++ b/packages/config/src/lib/config.ts @@ -2,7 +2,7 @@ import * as fs from 'node:fs'; import { createRequire } from 'node:module'; import * as path from 'node:path'; import { pathToFileURL } from 'node:url'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { colorLink, getReactNativeVersion, logger } from '@rock-js/tools'; import type { ValidationError } from 'joi'; import { ConfigTypeSchema } from './schema.js'; @@ -54,7 +54,7 @@ export type PluginApi = { getRemoteCacheProvider: () => Promise< null | undefined | (() => RemoteBuildCache) >; - getFingerprintOptions: () => FingerprintSources; + getFingerprintOptions: () => FingerprintOptions; getBundlerStart: () => ({ args }: { args: DevServerArgs }) => void; getUsePrebuiltRNCore: () => number | undefined; }; @@ -103,7 +103,7 @@ export type ConfigType = { ignorePaths?: string[]; env?: string[]; }; - usePrebuiltRNCore?: number, + usePrebuiltRNCore?: number; }; export type ConfigOutput = { @@ -219,7 +219,7 @@ Read more: ${colorLink('https://rockjs.dev/docs/configuration#github-actions-pro return validatedConfig.remoteCacheProvider; }, getFingerprintOptions: () => - validatedConfig.fingerprint as FingerprintSources, + validatedConfig.fingerprint as FingerprintOptions, getBundlerStart: () => ({ args }: { args: DevServerArgs }) => { diff --git a/packages/create-app/src/lib/utils/__tests__/initInExistingProject.test.ts b/packages/create-app/src/lib/utils/__tests__/initInExistingProject.test.ts index 1a2a31d9b..8c3660113 100644 --- a/packages/create-app/src/lib/utils/__tests__/initInExistingProject.test.ts +++ b/packages/create-app/src/lib/utils/__tests__/initInExistingProject.test.ts @@ -2,7 +2,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { cleanup, getTempDirectory, writeFiles } from '@rock-js/test-helpers'; import * as tools from '@rock-js/tools'; -import { updateAndroidBuildGradle } from "../initInExistingProject.js"; +import { updateAndroidBuildGradle } from '../initInExistingProject.js'; const directory = getTempDirectory('test_updateAndroidBuildGradle'); @@ -61,15 +61,23 @@ describe('updateAndroidBuildGradle', () => { }, ]; - it.each(workingCases)('should update the Android build.gradle file when $name', ({ content, expected }) => { - const files = { - 'android/app/build.gradle': content, - }; - writeFiles(directory, files); - updateAndroidBuildGradle(directory, 'android'); - - expect(fs.readFileSync(path.join(directory, 'android/app/build.gradle'), 'utf8')).toStrictEqual(expected) - }); + it.each(workingCases)( + 'should update the Android build.gradle file when $name', + ({ content, expected }) => { + const files = { + 'android/app/build.gradle': content, + }; + writeFiles(directory, files); + updateAndroidBuildGradle(directory, 'android'); + + expect( + fs.readFileSync( + path.join(directory, 'android/app/build.gradle'), + 'utf8', + ), + ).toStrictEqual(expected); + }, + ); it('should not update the Android build.gradle file when cliFile is already set', () => { const content = ` @@ -84,7 +92,9 @@ describe('updateAndroidBuildGradle', () => { writeFiles(directory, files); updateAndroidBuildGradle(directory, 'android'); - expect(fs.readFileSync(path.join(directory, 'android/app/build.gradle'), 'utf8')).toStrictEqual(content) + expect( + fs.readFileSync(path.join(directory, 'android/app/build.gradle'), 'utf8'), + ).toStrictEqual(content); }); it('should display a warning when unable to update the Android build.gradle file', () => { @@ -101,10 +111,12 @@ describe('updateAndroidBuildGradle', () => { writeFiles(directory, files); updateAndroidBuildGradle(directory, 'android'); - expect(fs.readFileSync(path.join(directory, 'android/app/build.gradle'), 'utf8')).toStrictEqual(content) + expect( + fs.readFileSync(path.join(directory, 'android/app/build.gradle'), 'utf8'), + ).toStrictEqual(content); expect(warn).toHaveBeenCalledWith( - expect.stringContaining('Unable to update') - ) + expect.stringContaining('Unable to update'), + ); }); }); diff --git a/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts b/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts index 50cfd9584..703bcd0c8 100644 --- a/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts +++ b/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts @@ -2,7 +2,7 @@ import type { AndroidProjectConfig } from '@react-native-community/cli-types'; import type { RemoteBuildCache, RockCLIOptions } from '@rock-js/tools'; import { colorLink, - type FingerprintSources, + type FingerprintOptions, formatArtifactName, getBinaryPath, logger, @@ -28,7 +28,7 @@ export async function buildAndroid( args: BuildFlags, projectRoot: string, remoteCacheProvider: null | (() => RemoteBuildCache) | undefined, - fingerprintOptions: FingerprintSources, + fingerprintOptions: FingerprintOptions, ) { normalizeArgs(args); // Use assemble task by default, but bundle if the flag is set diff --git a/packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts b/packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts index f3c70e1a2..11ddd73c5 100644 --- a/packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts +++ b/packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts @@ -5,7 +5,7 @@ import type { Config, } from '@react-native-community/cli-types'; import type { StartDevServerArgs } from '@rock-js/config'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { color, formatArtifactName, @@ -50,7 +50,7 @@ export async function runAndroid( args: Flags, projectRoot: string, remoteCacheProvider: null | (() => RemoteBuildCache) | undefined, - fingerprintOptions: FingerprintSources, + fingerprintOptions: FingerprintOptions, startDevServer: (options: StartDevServerArgs) => void, reactNativeVersion: string, reactNativePath: string, diff --git a/packages/platform-android/src/lib/commands/runAndroid/tryInstallAppOnDevice.ts b/packages/platform-android/src/lib/commands/runAndroid/tryInstallAppOnDevice.ts index bc5556eef..f5e1e934c 100644 --- a/packages/platform-android/src/lib/commands/runAndroid/tryInstallAppOnDevice.ts +++ b/packages/platform-android/src/lib/commands/runAndroid/tryInstallAppOnDevice.ts @@ -20,9 +20,7 @@ export async function tryInstallAppOnDevice( ) { let deviceId: string; if (!device.deviceId) { - logger.debug( - `No "deviceId" for ${device}, skipping launching the app`, - ); + logger.debug(`No "deviceId" for ${device}, skipping launching the app`); return; } else { deviceId = device.deviceId; diff --git a/packages/platform-android/src/lib/commands/runAndroid/tryLaunchAppOnDevice.ts b/packages/platform-android/src/lib/commands/runAndroid/tryLaunchAppOnDevice.ts index 9d71b6b17..a0298247f 100644 --- a/packages/platform-android/src/lib/commands/runAndroid/tryLaunchAppOnDevice.ts +++ b/packages/platform-android/src/lib/commands/runAndroid/tryLaunchAppOnDevice.ts @@ -11,9 +11,7 @@ export async function tryLaunchAppOnDevice( ) { let deviceId; if (!device.deviceId) { - logger.debug( - `No "deviceId" for ${device}, skipping launching the app`, - ); + logger.debug(`No "deviceId" for ${device}, skipping launching the app`); return {}; } else { deviceId = device.deviceId; diff --git a/packages/platform-apple-helpers/src/lib/commands/build/createBuild.ts b/packages/platform-apple-helpers/src/lib/commands/build/createBuild.ts index 2fd703dc6..8a2490f45 100644 --- a/packages/platform-apple-helpers/src/lib/commands/build/createBuild.ts +++ b/packages/platform-apple-helpers/src/lib/commands/build/createBuild.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { colorLink, formatArtifactName, @@ -37,7 +37,7 @@ export const createBuild = async ({ args: BuildFlags; projectRoot: string; reactNativePath: string; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; remoteCacheProvider: null | (() => RemoteBuildCache) | undefined; usePrebuiltRNCore?: number; }) => { diff --git a/packages/platform-apple-helpers/src/lib/commands/run/createRun.ts b/packages/platform-apple-helpers/src/lib/commands/run/createRun.ts index 0f7bc318a..f59756cce 100644 --- a/packages/platform-apple-helpers/src/lib/commands/run/createRun.ts +++ b/packages/platform-apple-helpers/src/lib/commands/run/createRun.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import type { StartDevServerArgs } from '@rock-js/config'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { color, formatArtifactName, @@ -45,7 +45,7 @@ export const createRun = async ({ args: RunFlags; projectRoot: string; remoteCacheProvider: null | (() => RemoteBuildCache) | undefined; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; reactNativePath: string; reactNativeVersion: string; platforms: { [platform: string]: object }; diff --git a/packages/platform-apple-helpers/src/lib/commands/run/runOptions.ts b/packages/platform-apple-helpers/src/lib/commands/run/runOptions.ts index 29e3affe2..0a98a4423 100644 --- a/packages/platform-apple-helpers/src/lib/commands/run/runOptions.ts +++ b/packages/platform-apple-helpers/src/lib/commands/run/runOptions.ts @@ -38,7 +38,8 @@ export const getRunOptions = ({ platformName }: BuilderCommand) => { }, { name: '--dev-server', - description: 'Automatically start a dev server (bundler) after building the app.', + description: + 'Automatically start a dev server (bundler) after building the app.', }, ...getBuildOptions({ platformName }), ]; diff --git a/packages/platform-apple-helpers/src/lib/utils/buildApp.ts b/packages/platform-apple-helpers/src/lib/utils/buildApp.ts index dc7e5b793..8b6c25411 100644 --- a/packages/platform-apple-helpers/src/lib/utils/buildApp.ts +++ b/packages/platform-apple-helpers/src/lib/utils/buildApp.ts @@ -1,7 +1,7 @@ import path from 'node:path'; import type { IOSProjectConfig } from '@react-native-community/cli-types'; import { - type FingerprintSources, + type FingerprintOptions, formatArtifactName, getInfoPlist, RockError, @@ -52,14 +52,14 @@ export async function buildApp({ brownfield?: false; artifactName: string; deviceOrSimulator: string; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; } & SharedBuildAppOptions) | ({ brownfield: true; // artifactName, deviceOrSimulator and fingerprintOptions are not used for brownfield builds artifactName?: string; deviceOrSimulator?: string; - fingerprintOptions?: FingerprintSources; + fingerprintOptions?: FingerprintOptions; } & SharedBuildAppOptions)) { if (binaryPath) { // @todo Info.plist is hardcoded when reading from binaryPath diff --git a/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts b/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts index f0f43e904..82e66dc6a 100644 --- a/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts +++ b/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts @@ -1,7 +1,7 @@ import type { RemoteBuildCache, RockCLIOptions } from '@rock-js/tools'; import { colorLink, - type FingerprintSources, + type FingerprintOptions, formatArtifactName, getBinaryPath, logger, @@ -26,7 +26,7 @@ export async function buildHarmony( args: BuildFlags, projectRoot: string, remoteCacheProvider: null | (() => RemoteBuildCache) | undefined, - fingerprintOptions: FingerprintSources, + fingerprintOptions: FingerprintOptions, ) { const { sourceDir, bundleName } = harmonyConfig; const artifactName = await formatArtifactName({ diff --git a/packages/platform-harmony/src/lib/commands/run/runHarmony.ts b/packages/platform-harmony/src/lib/commands/run/runHarmony.ts index f49b68dfe..f0d65f565 100644 --- a/packages/platform-harmony/src/lib/commands/run/runHarmony.ts +++ b/packages/platform-harmony/src/lib/commands/run/runHarmony.ts @@ -1,6 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; -import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools'; +import type { FingerprintOptions, RemoteBuildCache } from '@rock-js/tools'; import { color, formatArtifactName, @@ -40,7 +40,7 @@ export async function runHarmony( args: Flags, projectRoot: string, remoteCacheProvider: null | (() => RemoteBuildCache) | undefined, - fingerprintOptions: FingerprintSources, + fingerprintOptions: FingerprintOptions, ) { intro('Running HarmonyOS Next app'); diff --git a/packages/tools/src/lib/build-cache/common.ts b/packages/tools/src/lib/build-cache/common.ts index 0c87cdde5..9a4b19647 100644 --- a/packages/tools/src/lib/build-cache/common.ts +++ b/packages/tools/src/lib/build-cache/common.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { color } from '../color.js'; -import type { FingerprintSources } from '../fingerprint/index.js'; +import type { FingerprintOptions } from '../fingerprint/index.js'; import { nativeFingerprint } from '../fingerprint/index.js'; import { isInteractive } from '../isInteractive.js'; import { getCacheRootPath } from '../project.js'; @@ -10,8 +10,8 @@ import { spinner } from '../prompts.js'; export const BUILD_CACHE_DIR = 'remote-build'; export const supportedRemoteCacheProviders = ['github-actions', 's3'] as const; -export type SupportedRemoteCacheProviders = typeof supportedRemoteCacheProviders[number]; - +export type SupportedRemoteCacheProviders = + (typeof supportedRemoteCacheProviders)[number]; export type RemoteArtifact = { name: string; @@ -111,7 +111,7 @@ export async function formatArtifactName({ platform?: 'ios' | 'android' | 'harmony'; traits?: string[]; root: string; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; raw?: boolean; type?: 'create' | 'update'; }): Promise { diff --git a/packages/tools/src/lib/build-cache/getBinaryPath.ts b/packages/tools/src/lib/build-cache/getBinaryPath.ts index 667ee07b7..56a4fb4c3 100644 --- a/packages/tools/src/lib/build-cache/getBinaryPath.ts +++ b/packages/tools/src/lib/build-cache/getBinaryPath.ts @@ -2,7 +2,7 @@ import path from 'node:path'; import { color, colorLink } from '../color.js'; import type { RockError } from '../error.js'; import { getAllIgnorePaths } from '../fingerprint/ignorePaths.js'; -import { type FingerprintSources } from '../fingerprint/index.js'; +import { type FingerprintOptions } from '../fingerprint/index.js'; import logger from '../logger.js'; import { getProjectRoot } from '../project.js'; import { spawn } from '../spawn.js'; @@ -23,7 +23,7 @@ export async function getBinaryPath({ binaryPathFlag?: string; localFlag?: boolean; remoteCacheProvider: null | (() => RemoteBuildCache) | undefined; - fingerprintOptions: FingerprintSources; + fingerprintOptions: FingerprintOptions; sourceDir: string; platformName: string; }) { @@ -67,7 +67,7 @@ Read more: ${colorLink( } async function warnIgnoredFiles( - fingerprintOptions: FingerprintSources, + fingerprintOptions: FingerprintOptions, platformName: string, sourceDir: string, ) { diff --git a/packages/tools/src/lib/fingerprint/index.ts b/packages/tools/src/lib/fingerprint/index.ts index e8bc36a2a..5a724791e 100644 --- a/packages/tools/src/lib/fingerprint/index.ts +++ b/packages/tools/src/lib/fingerprint/index.ts @@ -6,14 +6,7 @@ import { spawn } from '../spawn.js'; import { getAllIgnorePaths } from './ignorePaths.js'; export type { FingerprintInputHash } from 'fs-fingerprint'; -export type FingerprintSources = { - extraSources: string[]; - ignorePaths: string[]; - env: string[]; -}; - export type FingerprintOptions = { - platform: 'ios' | 'android' | 'harmony'; extraSources: string[]; ignorePaths: string[]; env: string[]; @@ -24,7 +17,9 @@ export type FingerprintOptions = { */ export async function nativeFingerprint( projectRoot: string, - options: FingerprintOptions, + options: FingerprintOptions & { + platform: 'ios' | 'android' | 'harmony'; + }, ): Promise { let autolinkingConfig; diff --git a/packages/tools/src/lib/hermes.ts b/packages/tools/src/lib/hermes.ts index 60c07a5e6..54086a70f 100644 --- a/packages/tools/src/lib/hermes.ts +++ b/packages/tools/src/lib/hermes.ts @@ -47,7 +47,9 @@ function getHermesOSBin(): string { function getHermesOSExe(): string { const hermesExecutableName = 'hermesc'; const os = getLocalOS(); - return os === 'windows' ? `${hermesExecutableName}.exe` : hermesExecutableName; + return os === 'windows' + ? `${hermesExecutableName}.exe` + : hermesExecutableName; } /** @@ -112,7 +114,7 @@ export async function runHermes({ sourcemapOutputPath?: string; }) { const hermescPath = getHermescPath(); - if(!hermescPath) { + if (!hermescPath) { throw new RockError( 'Hermesc binary not found. Please ensure React Native is installed correctly or use `--no-hermes` flag to disable Hermes.', ); @@ -209,7 +211,6 @@ function getHermescPath() { } } - // 2. Check bundled hermesc in react-native/sdks/hermesc (RN 0.69-0.82) const reactNativePath = getReactNativePackagePath(); const bundledHermesPath = path.join( diff --git a/templates/rock-template-default/tsconfig.json b/templates/rock-template-default/tsconfig.json index 8a28ab3d8..266ba9ca2 100644 --- a/templates/rock-template-default/tsconfig.json +++ b/templates/rock-template-default/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "@react-native/typescript-config", "compilerOptions": { - "types": ["jest"], + "types": ["jest"] }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["**/node_modules", "**/Pods"] diff --git a/website/src/docs/remote-cache/introduction.md b/website/src/docs/remote-cache/introduction.md index c00550596..924d70716 100644 --- a/website/src/docs/remote-cache/introduction.md +++ b/website/src/docs/remote-cache/introduction.md @@ -34,4 +34,3 @@ These actions automatically store native artifacts that can be reused across CI - Falls back to local build if no match is found ![How CLI works with remote cache](/cli-remote-cache.png) - From e12ffe4a098a743e289cbad9f7c7663119e19ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Jan 2026 10:30:33 +0100 Subject: [PATCH 11/16] fixup test types --- .../commands/runAndroid/__tests__/runAndroid.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/platform-android/src/lib/commands/runAndroid/__tests__/runAndroid.test.ts b/packages/platform-android/src/lib/commands/runAndroid/__tests__/runAndroid.test.ts index b42a62945..10e0a5fc1 100644 --- a/packages/platform-android/src/lib/commands/runAndroid/__tests__/runAndroid.test.ts +++ b/packages/platform-android/src/lib/commands/runAndroid/__tests__/runAndroid.test.ts @@ -313,7 +313,7 @@ test.each([['release'], ['debug'], ['staging']])( { ...args, variant }, '/', undefined, - { extraSources: [], ignorePaths: [], env: [] }, + { extraSources: [], ignorePaths: [], env: [], platform: 'android' }, vi.fn(), // startDevServer mock '/path/to/react-native', // reactNativePath '0.79.0', // reactNativeVersion @@ -365,7 +365,7 @@ test('runAndroid runs gradle build with custom --appId, --appIdSuffix and --main }, '/', undefined, - { extraSources: [], ignorePaths: [], env: [] }, + { extraSources: [], ignorePaths: [], env: [], platform: 'android' }, vi.fn(), // startDevServer mock '/path/to/react-native', // reactNativePath '0.79.0', // reactNativeVersion @@ -395,7 +395,7 @@ test('runAndroid fails to launch an app on not-connected device when specified w { ...args, device: 'emulator-5554' }, '/', undefined, - { extraSources: [], ignorePaths: [], env: [] }, + { extraSources: [], ignorePaths: [], env: [], platform: 'android' }, vi.fn(), // startDevServer mock '/path/to/react-native', // reactNativePath '0.79.0', // reactNativeVersion @@ -468,7 +468,7 @@ test.each([['release'], ['debug']])( { ...args, device: 'emulator-5554', variant }, '/', undefined, - { extraSources: [], ignorePaths: [], env: [] }, + { extraSources: [], ignorePaths: [], env: [], platform: 'android' }, vi.fn(), // startDevServer mock '/path/to/react-native', // reactNativePath '0.79.0', // reactNativeVersion @@ -538,6 +538,7 @@ test('runAndroid launches an app on all connected devices', async () => { extraSources: [], ignorePaths: [], env: [], + platform: 'android', }, vi.fn(), '/path/to/react-native', @@ -609,7 +610,7 @@ test('runAndroid skips building when --binary-path is passed', async () => { }, '/root', undefined, - { extraSources: [], ignorePaths: [], env: [] }, + { extraSources: [], ignorePaths: [], env: [], platform: 'android' }, vi.fn(), // startDevServer mock '/path/to/react-native', // reactNativePath '0.79.0', // reactNativeVersion From 04a7c033dfee955976320c98f64ba1b0dd1a582e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Jan 2026 10:36:56 +0100 Subject: [PATCH 12/16] cleanup; iosConfig is from autolinking; derivedDataDir can be overriden by --build-folder arg --- .../src/lib/pluginBrownfieldIos.ts | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index ad3975322..a117e343d 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -9,7 +9,6 @@ import { getBuildPaths, getValidProjectConfig, mergeFrameworks, - type ProjectConfig as AppleProjectConfig, } from '@rock-js/platform-apple-helpers'; import { colorLink, @@ -20,7 +19,6 @@ import { RockError, } from '@rock-js/tools'; import { copyHermesXcframework } from './copyHermesXcframework.js'; -import type { RequireAllOrNone } from './types.js'; const buildOptions = getBuildOptions({ platformName: 'ios' }); @@ -38,41 +36,17 @@ export const packageIosAction = async ( usePrebuiltRNCore: number | undefined; }, pluginConfig?: IOSProjectConfig, - /** - * Rock-dependent logic escape hatch. - * If this is not provided, the logic will depend on the presence of a Rock config file. - */ - { - iosConfigOverride, - derivedDataDirOverride, - }: RequireAllOrNone<{ - /** - * Override for iOS project config. - */ - iosConfigOverride: AppleProjectConfig; - - /** - * Override for derivedDataDir path. - */ - derivedDataDirOverride: string; - }> = {}, ) => { intro('Packaging iOS project'); // 1) Build the project - const iosConfig = - iosConfigOverride ?? - getValidProjectConfig('ios', projectRoot, pluginConfig); - - const { derivedDataDir } = derivedDataDirOverride - ? { derivedDataDir: derivedDataDirOverride } - : getBuildPaths('ios'); + const iosConfig = getValidProjectConfig('ios', projectRoot, pluginConfig); const destination = args.destination ?? [ genericDestinations.ios.device, genericDestinations.ios.simulator, ]; - const buildFolder = args.buildFolder ?? derivedDataDir; + const buildFolder = args.buildFolder ?? getBuildPaths('ios').derivedDataDir; const configuration = args.configuration ?? 'Debug'; let scheme; From 9b9caa616587c7392aea6ecaf16719c82be81126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 11 Jan 2026 10:41:17 +0100 Subject: [PATCH 13/16] cleanup unused type --- packages/plugin-brownfield-ios/src/lib/types.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 packages/plugin-brownfield-ios/src/lib/types.ts diff --git a/packages/plugin-brownfield-ios/src/lib/types.ts b/packages/plugin-brownfield-ios/src/lib/types.ts deleted file mode 100644 index 82c8b0e53..000000000 --- a/packages/plugin-brownfield-ios/src/lib/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type RequireAllOrNone = T | { [K in keyof T]?: never }; From f4b8611e2709bc50b53976a09b514202d6942663 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Sun, 11 Jan 2026 22:52:05 +0100 Subject: [PATCH 14/16] feat: add skipCache parameter to buildApp, packageIosAction and installPodsIfNeeded --- .../src/lib/utils/buildApp.ts | 3 +++ .../platform-apple-helpers/src/lib/utils/pods.ts | 15 ++++++++++----- .../src/lib/pluginBrownfieldIos.ts | 4 ++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/platform-apple-helpers/src/lib/utils/buildApp.ts b/packages/platform-apple-helpers/src/lib/utils/buildApp.ts index 8b6c25411..33c5188cf 100644 --- a/packages/platform-apple-helpers/src/lib/utils/buildApp.ts +++ b/packages/platform-apple-helpers/src/lib/utils/buildApp.ts @@ -30,6 +30,7 @@ type SharedBuildAppOptions = { reactNativePath: string; binaryPath?: string; usePrebuiltRNCore?: number; + skipCache?: boolean; }; export async function buildApp({ @@ -47,6 +48,7 @@ export async function buildApp({ fingerprintOptions, deviceOrSimulator, usePrebuiltRNCore, + skipCache, }: | ({ brownfield?: false; @@ -88,6 +90,7 @@ export async function buildApp({ reactNativePath, brownfield, usePrebuiltRNCore, + skipCache, ); // When the project is not a workspace, we need to get the project config again, // because running pods install might have generated .xcworkspace project. diff --git a/packages/platform-apple-helpers/src/lib/utils/pods.ts b/packages/platform-apple-helpers/src/lib/utils/pods.ts index ad01e11b9..b8e6cde9f 100644 --- a/packages/platform-apple-helpers/src/lib/utils/pods.ts +++ b/packages/platform-apple-helpers/src/lib/utils/pods.ts @@ -29,6 +29,7 @@ export async function installPodsIfNeeded( reactNativePath: string, brownfield?: boolean, usePrebuiltRNCore?: number, + skipCache?: boolean, ) { const podsPath = path.join(sourceDir, 'Pods'); const podfilePath = path.join(sourceDir, 'Podfile'); @@ -37,7 +38,9 @@ export async function installPodsIfNeeded( const nativeDependencies = await getNativeDependencies(platformName); const cacheKey = `pods-dependencies`; - const cachedDependenciesHash = cacheManager.get(cacheKey); + const cachedDependenciesHash = skipCache + ? undefined + : cacheManager.get(cacheKey); const podsDirExists = fs.existsSync(podsPath); const hashChanged = cachedDependenciesHash ? !compareMd5Hashes( @@ -56,10 +59,12 @@ export async function installPodsIfNeeded( brownfield, usePrebuiltRNCore, }); - cacheManager.set( - cacheKey, - calculateCurrentHash({ podfilePath, podsPath, nativeDependencies }), - ); + if (!skipCache) { + cacheManager.set( + cacheKey, + calculateCurrentHash({ podfilePath, podsPath, nativeDependencies }), + ); + } return true; } return false; diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index a117e343d..601b87923 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -29,11 +29,13 @@ export const packageIosAction = async ( reactNativePath, reactNativeVersion, usePrebuiltRNCore, + skipCache, }: { projectRoot: string; reactNativePath: string; reactNativeVersion: string; usePrebuiltRNCore: number | undefined; + skipCache?: boolean; }, pluginConfig?: IOSProjectConfig, ) => { @@ -59,6 +61,8 @@ export const packageIosAction = async ( reactNativePath, brownfield: true, usePrebuiltRNCore, + pluginConfig, + skipCache, }); logger.log(`Build available at: ${colorLink(relativeToCwd(appPath))}`); From 48da76a182e68aa46a32864997c8f9bfb33fd9d5 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Mon, 12 Jan 2026 11:03:51 +0100 Subject: [PATCH 15/16] feat: make args.buildFolder override getBuildPaths in iOS brownfield plugin to work with non-Rock projects --- packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index 601b87923..612494a19 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -74,7 +74,9 @@ export const packageIosAction = async ( // 2) Merge the .framework outputs of the framework target const productsPath = path.join(buildFolder, 'Build', 'Products'); - const { packageDir: frameworkTargetOutputDir } = getBuildPaths('ios'); + const frameworkTargetOutputDir = args.buildFolder + ? path.join(args.buildFolder, 'package') + : getBuildPaths('ios').packageDir; const { sourceDir } = iosConfig; await mergeFrameworks({ From 8175200fda3d8738ebd038a55654257a1ff82822 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Mon, 12 Jan 2026 12:55:31 +0100 Subject: [PATCH 16/16] feat: add packageDir to override artifact outputs path in non-Rock projects --- .../plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts index 612494a19..665a1e4cd 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -30,12 +30,14 @@ export const packageIosAction = async ( reactNativeVersion, usePrebuiltRNCore, skipCache, + packageDir, }: { projectRoot: string; reactNativePath: string; reactNativeVersion: string; usePrebuiltRNCore: number | undefined; skipCache?: boolean; + packageDir?: string; }, pluginConfig?: IOSProjectConfig, ) => { @@ -74,9 +76,8 @@ export const packageIosAction = async ( // 2) Merge the .framework outputs of the framework target const productsPath = path.join(buildFolder, 'Build', 'Products'); - const frameworkTargetOutputDir = args.buildFolder - ? path.join(args.buildFolder, 'package') - : getBuildPaths('ios').packageDir; + const frameworkTargetOutputDir = + packageDir ?? getBuildPaths('ios').packageDir; const { sourceDir } = iosConfig; await mergeFrameworks({