Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/curvy-crabs-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rnef/plugin-brownfield-ios': patch
---

Implement [react-native-brownfield](https://github.com/callstack/react-native-brownfield) to iOS brownfield template.
5 changes: 5 additions & 0 deletions .changeset/rare-needles-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rnef/create-app': patch
---

List the `brownfield-ios` plugin on the plugins list shown while creating an app.
2 changes: 1 addition & 1 deletion packages/create-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"scripts": {
"publish:npm": "npm publish --access public",
"publish:verdaccio": "npm publish --registry http://localhost:4873 --userconfig ../../.npmrc",
"e2e": "vitest --config vite.e2e.config.js"
"e2e": "CI=true vitest --config vite.e2e.config.js"
},
"bin": {
"create-app": "./dist/src/bin.js"
Expand Down
11 changes: 10 additions & 1 deletion packages/create-app/src/lib/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ export const TEMPLATES: TemplateInfo[] = [
},
];

export const PLUGINS: TemplateInfo[] = [];
export const PLUGINS: TemplateInfo[] = [
{
type: 'npm',
name: 'brownfield-ios',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should have unified browfield (iOS + Android) template?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have both specified as optional plugins, that's it

packageName: '@rnef/plugin-brownfield-ios',
version: 'latest',
directory: 'template',
importName: 'pluginBrownfieldIos',
},
];

export const BUNDLERS: TemplateInfo[] = [
{
Expand Down
3 changes: 2 additions & 1 deletion packages/create-app/src/lib/utils/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
color,
intro,
isInteractive,
note,
outro,
promptConfirm,
Expand Down Expand Up @@ -124,7 +125,7 @@ export function promptPlatforms(
export function promptPlugins(
plugins: TemplateInfo[]
): Promise<TemplateInfo[] | null> {
if (plugins.length === 0) {
if (plugins.length === 0 || !isInteractive()) {
return Promise.resolve(null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { PathLike } from 'node:fs';
import fs from 'node:fs';
import type { AndroidProjectConfig } from '@react-native-community/cli-types';
import * as tools from '@rnef/tools';
import { color, spawn } from '@rnef/tools';
import { spawn } from '@rnef/tools';
import type { Mock } from 'vitest';
import { test, vi } from 'vitest';
import { buildAndroid, type BuildFlags } from '../buildAndroid.js';
Expand Down Expand Up @@ -80,9 +80,9 @@ test('buildAndroid runs gradle build with correct configuration for debug and ou
cwd: '/android',
});
expect(spinnerMock.stop).toBeCalledWith(
`Build available at: ${color.cyan(
'/android/app/build/outputs/bundle/debug/app-debug.aab'
)}`
expect.stringContaining(
'android/app/build/outputs/bundle/debug/app-debug.aab'
)
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path from 'node:path';
import type { AndroidProjectConfig } from '@react-native-community/cli-types';
import type { FingerprintSources } from '@rnef/tools';
import {
Expand Down Expand Up @@ -43,7 +44,11 @@ export async function buildAndroid(
if (outputFilePath) {
const loader = spinner();
loader.start('');
loader.stop(`Build available at: ${color.cyan(outputFilePath)}`);
loader.stop(
`Build available at: ${color.cyan(
path.relative(process.cwd(), outputFilePath)
)}`
);
}
outro('Success 🎉.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@ export const createBuild = async ({
projectRoot,
reactNativePath,
fingerprintOptions,
brownfield,
}: {
platformName: BuilderCommand['platformName'];
projectConfig: ProjectConfig;
args: BuildFlags;
projectRoot: string;
reactNativePath: string;
fingerprintOptions: FingerprintSources;
brownfield?: boolean;
}) => {
await validateArgs(args);

let xcodeProject: XcodeProjectInfo;
let sourceDir: string;
let scheme: string;
const deviceOrSimulator = args.destination
? // there can be multiple destinations, so we'll pick the first one
args.destination[0].match(/simulator/i)
Expand All @@ -58,16 +61,24 @@ export const createBuild = async ({
platformName,
args,
reactNativePath,
brownfield,
});
// The path may not exist when we archive
if (!args.archive) {
const loader = spinner();
loader.start('');
loader.stop(`Build available at: ${color.cyan(appPath)}`);
loader.stop(
`Build available at: ${color.cyan(
path.relative(process.cwd(), appPath)
)}`
);
saveLocalBuildCache(artifactName, appPath);
}
xcodeProject = buildAppResult.xcodeProject;
sourceDir = buildAppResult.sourceDir;
// @ts-expect-error - scheme is not set when binaryPath is provided,
// which is not supported for build command (but is used by run command)
scheme = buildAppResult.scheme;
} catch (error) {
const message = `Failed to create ${args.archive ? 'archive' : 'build'}`;
throw new RnefError(message, { cause: error });
Expand All @@ -92,6 +103,8 @@ export const createBuild = async ({
// Save the IPA to the local build cache so it's available for remote-cache command
saveLocalBuildCache(artifactName, ipaPath);
}

return { scheme };
};

async function validateArgs(args: BuildFlags) {
Expand Down
5 changes: 4 additions & 1 deletion packages/platform-apple-helpers/src/lib/utils/buildApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function buildApp({
deviceName,
reactNativePath,
binaryPath,
brownfield,
}: {
args: RunFlags | BuildFlags;
projectConfig: ProjectConfig;
Expand All @@ -33,6 +34,7 @@ export async function buildApp({
projectRoot: string;
reactNativePath: string;
binaryPath?: string;
brownfield?: boolean;
}) {
if (binaryPath) {
return {
Expand All @@ -53,7 +55,8 @@ export async function buildApp({
platformName,
sourceDir,
args.newArch,
reactNativePath
reactNativePath,
brownfield
);
// When the project is not a workspace, we need to get the project config again,
// because running pods install might have generated .xcworkspace project.
Expand Down
16 changes: 14 additions & 2 deletions packages/platform-apple-helpers/src/lib/utils/pods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export async function installPodsIfNeeded(
platformName: ApplePlatform,
sourceDir: string,
newArch: boolean,
reactNativePath: string
reactNativePath: string,
brownfield?: boolean
) {
const podsPath = path.join(sourceDir, 'Pods');
const podfilePath = path.join(sourceDir, 'Podfile');
Expand All @@ -43,7 +44,13 @@ export async function installPodsIfNeeded(

if (!podsDirExists || hashChanged) {
await runCodegen({ projectRoot, platformName, reactNativePath, sourceDir });
await installPods({ projectRoot, sourceDir, podfilePath, newArch });
await installPods({
projectRoot,
sourceDir,
podfilePath,
newArch,
brownfield,
});
cacheManager.set(
cacheKey,
calculateCurrentHash({ podfilePath, podsPath, nativeDependencies })
Expand Down Expand Up @@ -92,6 +99,7 @@ async function runPodInstall(options: {
sourceDir: string;
newArch: boolean;
useBundler: boolean;
brownfield?: boolean;
}) {
if (!options.useBundler) {
await validatePodCommand(options.sourceDir);
Expand All @@ -113,6 +121,7 @@ async function runPodInstall(options: {
env: {
RCT_NEW_ARCH_ENABLED: options.newArch ? '1' : '0',
RCT_IGNORE_PODS_DEPRECATION: '1',
...(options.brownfield && { USE_FRAMEWORKS: 'static' }),
...(process.env['USE_THIRD_PARTY_JSC'] && {
USE_THIRD_PARTY_JSC: process.env['USE_THIRD_PARTY_JSC'],
}),
Expand All @@ -137,6 +146,7 @@ async function runPodInstall(options: {
sourceDir: options.sourceDir,
newArch: options.newArch,
useBundler: options.useBundler,
brownfield: options.brownfield,
});
} else {
throw new RnefError(
Expand Down Expand Up @@ -177,6 +187,7 @@ async function installPods(options: {
projectRoot: string;
podfilePath: string;
newArch: boolean;
brownfield?: boolean;
}) {
if (!fs.existsSync(options.podfilePath)) {
logger.debug(
Expand All @@ -195,6 +206,7 @@ async function installPods(options: {
sourceDir: options.sourceDir,
newArch: options.newArch,
useBundler,
brownfield: options.brownfield,
});
}

Expand Down
32 changes: 32 additions & 0 deletions packages/plugin-brownfield-ios/src/lib/copyHermesXcframework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fs, { existsSync } from 'node:fs';
import path from 'node:path';
import { color, logger, spinner } from '@rnef/tools';

export function copyHermesXcframework({
sourceDir,
destinationDir,
}: {
sourceDir: string;
destinationDir: string;
}) {
const loader = spinner();

loader.start(`Copying ${color.bold('hermes.xcframework')}`);
const hermesDestination = path.join(destinationDir, 'hermes.xcframework');

if (existsSync(hermesDestination)) {
logger.debug(`Removing old hermes copy`);
fs.rmSync(hermesDestination, { recursive: true, force: true });
}

fs.cpSync(
path.join(
sourceDir,
'Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework'
),
hermesDestination,
{ recursive: true, force: true }
);

loader.stop(`Copied ${color.bold('hermes.xcframework')}`);
}
63 changes: 15 additions & 48 deletions packages/plugin-brownfield-ios/src/lib/mergeFrameworks.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,44 @@
import { existsSync, rmSync } from 'node:fs';
import fs, { existsSync } from 'node:fs';
import path from 'node:path';
import { getBuildPaths } from '@rnef/platform-apple-helpers';
import type { SubprocessError } from '@rnef/tools';
import { spawn, spinner } from '@rnef/tools';
import { color, logger, spawn, spinner } from '@rnef/tools';

/**
* Xcode emits different `.framework` file based on the destination (simulator arm64/x86_64, iphone arm64 etc.)
* This takes those `.frameworks` files and merges them to a single `.xcframework` file for easier distribution.
*/
export async function mergeFrameworks({
frameworkPaths,
outputPath,
sourceDir,
scheme,
configuration,
platformName,
buildFolder,
}: {
frameworkPaths: string[];
outputPath: string;
sourceDir: string;
scheme: string;
configuration: string;
platformName: string;
buildFolder: string;
}) {
const loader = spinner();
const xcframeworkName = path.basename(outputPath);

const { packageDir } = getBuildPaths(platformName);
const productsPath = path.join(buildFolder, 'Build', 'Products');

const iosPath = path.join(
productsPath,
`${configuration}-iphoneos`,
`${scheme}.framework`
);
const simulatorPath = path.join(
productsPath,
`${configuration}-iphonesimulator`,
`${scheme}.framework`
);

const xcframeworkPath = path.join(packageDir, `${scheme}.xcframework`);

if (existsSync(xcframeworkPath)) {
loader.start('Removing old framework output');
rmSync(xcframeworkPath, { recursive: true, force: true });

loader.stop('Removed old framework output');
if (existsSync(outputPath)) {
logger.debug(`Removing `);
fs.rmSync(outputPath, { recursive: true, force: true });
}

loader.start('Merging the frameworks...');
loader.start(`Creating ${color.bold(xcframeworkName)}`);

const xcodebuildArgs = [
'-create-xcframework',
'-framework',
iosPath,
'-framework',
simulatorPath,
...frameworkPaths.flatMap((frameworkPath) => ['-framework', frameworkPath]),
'-output',
xcframeworkPath,
outputPath,
];

try {
await spawn('xcodebuild', xcodebuildArgs, { cwd: sourceDir });

loader.stop(
`Exported the xcframework for ${scheme} scheme in ${configuration} configuration to ${path.join(
packageDir,
xcframeworkPath
)}`
);
loader.stop(`Created ${color.bold(xcframeworkName)}`);
} catch (error) {
loader.stop(
'Running xcodebuild failed. Check the error message above for details.',
1
);
loader.stop(`Couldn't create ${color.bold(xcframeworkName)}.`, 1);
throw new Error('Running xcodebuild failed', {
cause: (error as SubprocessError).stderr,
});
Expand Down
Loading