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/.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 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/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/packageAar.ts b/packages/platform-android/src/lib/commands/aar/packageAar.ts index 9ae9f7dbb..6b7b68ab4 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'; @@ -12,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'; } } @@ -54,4 +44,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..6de2ebd75 100644 --- a/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts +++ b/packages/platform-android/src/lib/commands/aar/publishLocalAar.ts @@ -1,7 +1,12 @@ +import type { RockCLIOptions } from '@rock-js/tools'; 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']; @@ -17,4 +22,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..703bcd0c8 100644 --- a/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts +++ b/packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts @@ -1,8 +1,8 @@ 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, + 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 @@ -104,4 +104,4 @@ export const options = [ name: '--local', description: 'Force local build with Gradle wrapper.', }, -]; +] satisfies RockCLIOptions; 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 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/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'; diff --git a/packages/platform-apple-helpers/src/lib/utils/buildApp.ts b/packages/platform-apple-helpers/src/lib/utils/buildApp.ts index dc7e5b793..33c5188cf 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, @@ -30,6 +30,7 @@ type SharedBuildAppOptions = { reactNativePath: string; binaryPath?: string; usePrebuiltRNCore?: number; + skipCache?: boolean; }; export async function buildApp({ @@ -47,19 +48,20 @@ export async function buildApp({ fingerprintOptions, deviceOrSimulator, usePrebuiltRNCore, + skipCache, }: | ({ 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 @@ -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/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 }); } 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/platform-harmony/src/lib/commands/build/buildHarmony.ts b/packages/platform-harmony/src/lib/commands/build/buildHarmony.ts index ec0df0b33..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 } from '@rock-js/tools'; +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({ @@ -96,4 +96,4 @@ export const options = [ description: 'OpenHarmony product defined in build-profile.json5.', default: 'default', }, -]; +] satisfies RockCLIOptions; 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/platform-ios/src/lib/commands/signIos.ts b/packages/platform-ios/src/lib/commands/signIos.ts index 3d21e88ae..efd5719e2 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 '@rock-js/tools'; 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({ diff --git a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts index 991b4fe5c..17194f3df 100644 --- a/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts +++ b/packages/plugin-brownfield-android/src/lib/pluginBrownfieldAndroid.ts @@ -6,40 +6,80 @@ import { type PackageAarFlags, packageAarOptions, publishLocalAar, + type PublishLocalAarFlags, publishLocalAarOptions, } from '@rock-js/platform-android'; 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 packageAarAction = async ({ + variant, + moduleName, + projectRoot, + pluginConfig, +}: { + variant: string; + moduleName: string | undefined; + projectRoot: string; + pluginConfig?: AndroidProjectConfig; +}) => { + intro('Creating an AAR file'); + + const androidConfig = projectConfig(projectRoot, pluginConfig); + + if (androidConfig) { + const config = getAarConfig(moduleName, androidConfig); + await packageAar(config, variant); + } else { + throw new RockError('Android project not found.'); + } +}; + +export const publishLocalAarAction = async ({ + moduleName, + projectRoot, + pluginConfig, +}: { + moduleName: string | undefined; + projectRoot: string; + pluginConfig?: AndroidProjectConfig; +}) => { + intro('Publishing AAR'); + + const androidConfig = projectConfig(projectRoot, pluginConfig); + + if (androidConfig) { + const config = getAarConfig(moduleName, 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) => { + return packageAarAction({ + variant: args.variant, + moduleName: args.moduleName, + projectRoot: api.getProjectRoot(), + pluginConfig, + }); }, options: packageAarOptions, }); @@ -47,17 +87,12 @@ export const pluginBrownfieldAndroid = 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: PublishLocalAarFlags) => { + 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 6e9906d96..665a1e4cd 100644 --- a/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts +++ b/packages/plugin-brownfield-ios/src/lib/pluginBrownfieldIos.ts @@ -22,117 +22,138 @@ import { copyHermesXcframework } from './copyHermesXcframework.js'; const buildOptions = getBuildOptions({ platformName: 'ios' }); -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'); +export const packageIosAction = async ( + args: BuildFlags, + { + projectRoot, + reactNativePath, + reactNativeVersion, + usePrebuiltRNCore, + skipCache, + packageDir, + }: { + projectRoot: string; + reactNativePath: string; + reactNativeVersion: string; + usePrebuiltRNCore: number | undefined; + skipCache?: boolean; + packageDir?: string; + }, + pluginConfig?: IOSProjectConfig, +) => { + intro('Packaging iOS project'); - // 1) Build the project - const projectRoot = api.getProjectRoot(); - const iosConfig = getValidProjectConfig( - 'ios', - projectRoot, - pluginConfig, - ); - const { derivedDataDir } = getBuildPaths('ios'); + // 1) Build the project + const iosConfig = getValidProjectConfig('ios', projectRoot, pluginConfig); + const destination = args.destination ?? [ + genericDestinations.ios.device, + genericDestinations.ios.simulator, + ]; - const destination = args.destination ?? [ - genericDestinations.ios.device, - genericDestinations.ios.simulator, - ]; + const buildFolder = args.buildFolder ?? getBuildPaths('ios').derivedDataDir; + const configuration = args.configuration ?? 'Debug'; + let scheme; - 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, + brownfield: true, + usePrebuiltRNCore, + pluginConfig, + skipCache, + }); + logger.log(`Build available at: ${colorLink(relativeToCwd(appPath))}`); - 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 }); + } - 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 frameworkTargetOutputDir = + packageDir ?? getBuildPaths('ios').packageDir; + const { sourceDir } = iosConfig; - // 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`), + }); - 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', + ), + }); - // 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, + }); - // 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), + )}`, + ); - // 5) Inform the user - logger.log( - `XCFrameworks are available at: ${colorLink( - relativeToCwd(frameworkTargetOutputDir), - )}`, - ); + outro('Success 🎉.'); +}; - 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) => + packageIosAction( + args, + { + projectRoot: api.getProjectRoot(), + reactNativePath: api.getReactNativePath(), + reactNativeVersion: api.getReactNativeVersion(), + usePrebuiltRNCore: api.getUsePrebuiltRNCore(), + }, + pluginConfig, + ), options: buildOptions, }); 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/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/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[]; 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) -