From b91c42b1604a31603aecc474db91dc4cc37e1847 Mon Sep 17 00:00:00 2001 From: DaveyEke Date: Wed, 11 Mar 2026 16:28:35 +0100 Subject: [PATCH 1/7] feat: enable per-platform language selection and match with new nitro version --- src/cli/create.ts | 88 +++++++------- src/file-generators/android-file-generator.ts | 6 +- src/file-generators/ios-file-generator.ts | 5 +- src/file-generators/js-file-generator.ts | 15 ++- src/generate-nitro-package.ts | 62 ++++++---- src/types.ts | 6 +- src/utils.ts | 110 +++++++----------- 7 files changed, 146 insertions(+), 146 deletions(-) diff --git a/src/cli/create.ts b/src/cli/create.ts index 07f7c06e..ffce8eed 100644 --- a/src/cli/create.ts +++ b/src/cli/create.ts @@ -10,6 +10,7 @@ import { Nitro, PackageManager, PLATFORM_LANGUAGE_MAP, + PlatformLangMap, SupportedLang, SupportedPlatform, UserAnswers, @@ -55,7 +56,7 @@ export const createModule = async ( moduleFactory = new NitroModuleFactory({ description: answers.description, - langs: answers.langs, + platformLangs: answers.platformLangs, packageName, platforms: answers.platforms, pm: answers.pm, @@ -132,57 +133,49 @@ export const createModule = async ( } } -const selectLanguages = async ( +const selectPlatformLanguages = async ( platforms: SupportedPlatform[], packageType: Nitro -) => { - const availableLanguages = Array.from( - new Set(platforms.flatMap(platform => PLATFORM_LANGUAGE_MAP[platform])) - ).filter(lang => packageType !== Nitro.View || lang !== SupportedLang.CPP) +): Promise => { + const result: PlatformLangMap = {} + + for (const platform of platforms) { + const availableLanguages = PLATFORM_LANGUAGE_MAP[platform].filter( + lang => packageType !== Nitro.View || lang !== SupportedLang.CPP + ) - const options = - platforms.includes(SupportedPlatform.IOS) && - platforms.includes(SupportedPlatform.ANDROID) - ? [ - { - label: 'Swift & Kotlin', - value: [SupportedLang.SWIFT, SupportedLang.KOTLIN], - hint: `Use Swift and Kotlin to build your Nitro ${packageType.toLowerCase()} for iOS and Android`, - }, - ...(packageType === Nitro.Module - ? [ - { - label: 'C++', - value: [SupportedLang.CPP], - hint: 'Use C++ to share code between iOS and Android', - }, - ] - : []), - ] - : availableLanguages.map(lang => ({ - label: capitalize(lang), - value: [lang], - hint: `Use ${lang === SupportedLang.CPP ? 'C++' : capitalize(lang)} to build your Nitro ${packageType.toLowerCase()} for ${platforms.join(' and ')}`, - })) + if (availableLanguages.length === 1) { + result[platform] = availableLanguages[0]! + continue + } - const selectedLangs = await p.select({ - message: kleur.cyan('Which language(s) would you like to use?'), - options, - }) + const selected = await p.select({ + message: kleur.cyan(`Choose language for ${platform}:`), + options: availableLanguages.map(lang => ({ + label: lang === SupportedLang.CPP ? 'C++' : capitalize(lang), + value: lang, + hint: `Use ${lang === SupportedLang.CPP ? 'C++' : capitalize(lang)} for ${platform}`, + })), + }) + + if (p.isCancel(selected)) return selected + result[platform] = selected + } - if (p.isCancel(selectedLangs)) return selectedLangs - return selectedLangs + return result } -const resolveViewLanguages = (platforms: SupportedPlatform[]) => { - const langs = new Set() +const resolveViewLanguages = ( + platforms: SupportedPlatform[] +): PlatformLangMap => { + const result: PlatformLangMap = {} if (platforms.includes(SupportedPlatform.IOS)) { - langs.add(SupportedLang.SWIFT) + result[SupportedPlatform.IOS] = SupportedLang.SWIFT } if (platforms.includes(SupportedPlatform.ANDROID)) { - langs.add(SupportedLang.KOTLIN) + result[SupportedPlatform.ANDROID] = SupportedLang.KOTLIN } - return Array.from(langs) + return result } const getUserAnswers = async ( @@ -198,10 +191,13 @@ const getUserAnswers = async ( description: `${kleur.yellow(`react-native-${name}`)} is a react native package built with Nitro`, platforms, packageType, - langs: + platformLangs: packageType === Nitro.View ? resolveViewLanguages(platforms) - : [SupportedLang.SWIFT, SupportedLang.KOTLIN], + : { + [SupportedPlatform.IOS]: SupportedLang.SWIFT, + [SupportedPlatform.ANDROID]: SupportedLang.KOTLIN, + }, pm: usedPm || 'pnpm', } } @@ -276,14 +272,14 @@ const getUserAnswers = async ( initialValue: Nitro.Module, }) }, - langs: async ({ results }) => { + platformLangs: async ({ results }) => { if (!results.platforms || !results.packageType) { throw new Error('Missing required selections') } if (results.packageType === Nitro.View) { return resolveViewLanguages(results.platforms) } - return await selectLanguages( + return await selectPlatformLanguages( results.platforms, results.packageType ) @@ -357,7 +353,7 @@ const getUserAnswers = async ( packageName: group.packageName, packageType: group.packageType, platforms: group.platforms, - langs: group.langs as SupportedLang[], + platformLangs: group.platformLangs as PlatformLangMap, pm: group.pm, description: group.description as string, } diff --git a/src/file-generators/android-file-generator.ts b/src/file-generators/android-file-generator.ts index 0bb9576a..5515430b 100644 --- a/src/file-generators/android-file-generator.ts +++ b/src/file-generators/android-file-generator.ts @@ -13,6 +13,7 @@ import { GenerateModuleConfig, Nitro, SupportedLang, + SupportedPlatform, } from '../types' import { createFolder, @@ -71,7 +72,10 @@ export class AndroidFileGenerator implements FileGenerator { ) // Only generate Kotlin file(s) if Kotlin is supported - if (config.langs.includes(SupportedLang.KOTLIN)) { + if ( + config.platformLangs[SupportedPlatform.ANDROID] === + SupportedLang.KOTLIN + ) { // Generate HybridObject file const isHybridView = config.packageType === Nitro.View await createModuleFile( diff --git a/src/file-generators/ios-file-generator.ts b/src/file-generators/ios-file-generator.ts index bbe2e5b9..2210a6e2 100644 --- a/src/file-generators/ios-file-generator.ts +++ b/src/file-generators/ios-file-generator.ts @@ -6,6 +6,7 @@ import { IOS_MODULE_NAME_TAG } from '../constants' import { Nitro, SupportedLang, + SupportedPlatform, type FileGenerator, type GenerateModuleConfig, } from '../types' @@ -82,7 +83,9 @@ export class IOSFileGenerator implements FileGenerator { replacements: bridgeReplacements, }) ) - if (config.langs.includes(SupportedLang.SWIFT)) { + if ( + config.platformLangs[SupportedPlatform.IOS] === SupportedLang.SWIFT + ) { const isHybridView = config.packageType === Nitro.View await createModuleFile( config.cwd, diff --git a/src/file-generators/js-file-generator.ts b/src/file-generators/js-file-generator.ts index eb2b86c1..3003050b 100644 --- a/src/file-generators/js-file-generator.ts +++ b/src/file-generators/js-file-generator.ts @@ -5,21 +5,20 @@ import { nitroViewSpecCode, } from '../code-snippets/code.js' import type { FileGenerator, GenerateModuleConfig } from '../types' -import { Nitro } from '../types' -import { createFolder, createModuleFile, mapPlatformToLanguage } from '../utils' +import { Nitro, SupportedLang } from '../types' +import { createFolder, createModuleFile } from '../utils' export class JSFileGenerator implements FileGenerator { async generate(config: GenerateModuleConfig): Promise { const nitroSpecFolder = '/src/specs' await createFolder(config.cwd, nitroSpecFolder) - const platformToLangMap = mapPlatformToLanguage( - config.platforms, - config.langs - ) - const platformLang = Object.entries(platformToLangMap) - .map(([platform, lang]) => `${platform}: '${lang.toLowerCase()}'`) + const platformLang = Object.entries(config.platformLangs) + .map( + ([platform, lang]) => + `${platform}: '${lang === SupportedLang.CPP ? 'c++' : lang}'` + ) .join(', ') switch (config.packageType) { diff --git a/src/generate-nitro-package.ts b/src/generate-nitro-package.ts index 9fa1e35b..0a895fe7 100644 --- a/src/generate-nitro-package.ts +++ b/src/generate-nitro-package.ts @@ -30,10 +30,10 @@ import { CppFileGenerator } from './file-generators/cpp-file-generator' import { IOSFileGenerator } from './file-generators/ios-file-generator' import { JSFileGenerator } from './file-generators/js-file-generator' import { - FileGenerator, GenerateModuleConfig, Nitro, SupportedLang, + SupportedPlatform, } from './types' import { copyTemplateFiles, @@ -51,21 +51,21 @@ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) export class NitroModuleFactory { - private generators: Map private nitroModulesVersion: string | null = null + private androidGenerator: AndroidFileGenerator + private iosGenerator: IOSFileGenerator + private cppGenerator: CppFileGenerator + private jsGenerator: JSFileGenerator + constructor(private config: GenerateModuleConfig) { - const androidGenerator = new AndroidFileGenerator() - const iosGenerator = new IOSFileGenerator() - this.generators = new Map([ - [SupportedLang.KOTLIN, androidGenerator], - [SupportedLang.SWIFT, iosGenerator], - [ - SupportedLang.CPP, - new CppFileGenerator([androidGenerator, iosGenerator]), - ], - [SupportedLang.JS, new JSFileGenerator()], + this.androidGenerator = new AndroidFileGenerator() + this.iosGenerator = new IOSFileGenerator() + this.cppGenerator = new CppFileGenerator([ + this.androidGenerator, + this.iosGenerator, ]) + this.jsGenerator = new JSFileGenerator() this.config.funcName = 'sum' this.config.prefix = 'react-native-' this.config.finalPackageName = `${this.config.prefix}${this.config.packageName}` @@ -77,14 +77,30 @@ export class NitroModuleFactory { async createNitroModule() { await createFolder(this.config.cwd) - const supportedLanguages = [...this.config.langs, SupportedLang.JS] - for (const lang of supportedLanguages) { - const generator = this.generators.get(lang) - if (!generator) { - throw new Error(`Unsupported language: ${lang}`) + + const langs = Object.values(this.config.platformLangs) + const allCpp = + langs.length > 0 && langs.every(l => l === SupportedLang.CPP) + + if (allCpp) { + // All platforms use C++ — use composite CppFileGenerator + await this.cppGenerator.generate(this.config) + } else { + // Mixed or native-only: generate C++ files if any platform uses C++, + // then run per-platform generators individually + if (langs.includes(SupportedLang.CPP)) { + await this.cppGenerator.generateCppCodeFiles(this.config) + } + if (this.config.platformLangs[SupportedPlatform.IOS] != null) { + await this.iosGenerator.generate(this.config) + } + if (this.config.platformLangs[SupportedPlatform.ANDROID] != null) { + await this.androidGenerator.generate(this.config) } - await generator.generate(this.config) } + + // Always generate JS/TS files + await this.jsGenerator.generate(this.config) await this.copyNitroTemplateFiles() await this.replaceNitroJsonPlaceholders() await this.updatePackageJsonConfig(this.config.skipExample) @@ -136,7 +152,7 @@ export class NitroModuleFactory { const newNitroJsonContent = JSON.parse(nitroJsonContent) newNitroJsonContent.autolinking = generateAutolinking( toPascalCase(this.config.packageName), - this.config.langs + this.config.platformLangs ) await createModuleFile( this.config.cwd, @@ -180,8 +196,10 @@ export class NitroModuleFactory { newWorkspacePackageJsonFile.scripts = { ...newWorkspacePackageJsonFile.scripts, build: `${this.config.pm} run typecheck && bob build`, - codegen: `nitrogen --logLevel="debug" && ${this.config.pm} run build${this.config.langs.includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}`, - postcodegen: !this.config.langs.includes(SupportedLang.KOTLIN) + codegen: `nitrogen --logLevel="debug" && ${this.config.pm} run build${Object.values(this.config.platformLangs).includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}`, + postcodegen: !Object.values(this.config.platformLangs).includes( + SupportedLang.KOTLIN + ) ? this.getPostCodegenScript() : undefined, } @@ -503,7 +521,7 @@ export class NitroModuleFactory { await execAsync(`${this.config.pm} install`, { cwd: this.config.cwd }) let packageManager = this.config.pm === 'npm' ? 'npx --yes' : this.config.pm - let codegenCommand = `${packageManager} nitrogen --logLevel="debug" && ${this.config.pm} run build${this.config.langs.includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}` + let codegenCommand = `${packageManager} nitrogen --logLevel="debug" && ${this.config.pm} run build${Object.values(this.config.platformLangs).includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}` await execAsync(codegenCommand, { cwd: this.config.cwd }) } diff --git a/src/types.ts b/src/types.ts index d60ef284..1921190c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,12 +1,14 @@ import * as p from '@clack/prompts' import { detectPackageManager } from './utils' +export type PlatformLangMap = Partial> + export interface UserAnswers { packageName: string description: string platforms: SupportedPlatform[] packageType: Nitro - langs: SupportedLang[] + platformLangs: PlatformLangMap pm: PackageManager } @@ -48,7 +50,7 @@ export enum Nitro { export type GenerateModuleConfig = { pm: PackageManager cwd: string - langs: SupportedLang[] + platformLangs: PlatformLangMap prefix?: string spinner: ReturnType description: string diff --git a/src/utils.ts b/src/utils.ts index d9115b8c..ca2822e7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,10 +2,26 @@ import kleur from 'kleur' import { execSync } from 'node:child_process' import { access, cp, mkdir, readFile, writeFile } from 'node:fs/promises' import path from 'node:path' -import { GenerateModuleConfig, SupportedLang } from './types' +import { + GenerateModuleConfig, + PlatformLangMap, + SupportedLang, + SupportedPlatform, +} from './types' + +type AutolinkingImplementation = { + language: string + implementationClassName: string +} + +type AutolinkingEntry = { + all?: AutolinkingImplementation + ios?: AutolinkingImplementation + android?: AutolinkingImplementation +} type AutolinkingConfig = { - [key: string]: Partial> + [key: string]: AutolinkingEntry } export const LANGS = ['c++', 'swift', 'kotlin'] as const @@ -63,84 +79,46 @@ export const toPascalCase = (str: string) => { .join('') } -export const mapPlatformToLanguage = ( - platforms: string[], - selectedLangs: string[] -): Record => { - const result: Record = {} - const [cpp, swift, kotlin] = LANGS - - platforms.forEach(platform => { - if (selectedLangs.includes(swift) || selectedLangs.includes(kotlin)) { - result[platform] = platform === 'ios' ? swift : kotlin - } else { - result[platform] = cpp - } - }) - - return result -} - export const generateAutolinking = ( moduleName: string, - langs: SupportedLang[] + platformLangs: PlatformLangMap ): AutolinkingConfig => { - if ( - !langs.some(lang => - [SupportedLang.SWIFT, SupportedLang.KOTLIN].includes(lang) - ) - ) { + const className = `Hybrid${moduleName}` + const langs = Object.values(platformLangs) + + // If all platforms use C++, use the "all" shorthand + if (langs.every(lang => lang === SupportedLang.CPP)) { return { [moduleName]: { - cpp: `Hybrid${moduleName}`, + all: { language: 'cpp', implementationClassName: className }, }, } } - const languageConfig = langs.reduce( - (config, lang) => { - if ([SupportedLang.SWIFT, SupportedLang.KOTLIN].includes(lang)) { - config[lang] = `Hybrid${moduleName}` - } - return config - }, - {} as Partial> - ) - - return Object.keys(languageConfig).length > 0 - ? { [moduleName]: languageConfig } - : {} -} - -export const validateTemplate = (answer: string[]) => { - return answer.length > 0 || 'You must choose at least one template' -} + const entry: AutolinkingEntry = {} -export const validateLang = (choices: string[]) => { - if (!choices.length) { - return 'You must choose at least one lang' + const langToNitroLang = (lang: SupportedLang): string => { + return lang === SupportedLang.CPP ? 'cpp' : lang } - if ( - choices.includes(SupportedLang.SWIFT) && - choices.includes(SupportedLang.KOTLIN) && - choices.includes(SupportedLang.CPP) - ) { - return 'You can not choose swift, kotlin and c++. use either swift with kotlin or c++' - } - if ( - choices.includes(SupportedLang.SWIFT) && - choices.includes(SupportedLang.CPP) - ) { - return 'You can not choose swift and c++. use either swift with kotlin or c++' + if (platformLangs[SupportedPlatform.IOS]) { + entry.ios = { + language: langToNitroLang(platformLangs[SupportedPlatform.IOS]), + implementationClassName: className, + } } - if ( - choices.includes(SupportedLang.KOTLIN) && - choices.includes(SupportedLang.CPP) - ) { - return 'You can not choose kotlin and c++. use either swift with kotlin or c++' + if (platformLangs[SupportedPlatform.ANDROID]) { + entry.android = { + language: langToNitroLang(platformLangs[SupportedPlatform.ANDROID]), + implementationClassName: className, + } } - return true + + return { [moduleName]: entry } +} + +export const validateTemplate = (answer: string[]) => { + return answer.length > 0 || 'You must choose at least one template' } export const dirExist = async (dir: string) => { From 0d1b3f04655c8687cce2939e059b48ce1fe0ce48 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 06:45:32 +0200 Subject: [PATCH 2/7] chore: bump up nitrogen and react-native-nitro-modules to version 0.35.2 --- assets/template/package.json | 4 ++-- src/generate-nitro-package.ts | 4 ++-- src/utils.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/template/package.json b/assets/template/package.json index 3310608d..0282ed6f 100644 --- a/assets/template/package.json +++ b/assets/template/package.json @@ -54,11 +54,11 @@ "@semantic-release/git": "^10.0.1", "@types/jest": "^29.5.12", "@types/react": "19.2.0", - "nitrogen": "^0.35.0", + "nitrogen": "^0.35.2", "react": "19.2.3", "react-native": "0.84.1", "react-native-builder-bob": "^0.40.18", - "react-native-nitro-modules": "^0.35.0", + "react-native-nitro-modules": "^0.35.2", "conventional-changelog-conventionalcommits": "^9.1.0", "semantic-release": "^25.0.3", "typescript": "^5.8.3" diff --git a/src/generate-nitro-package.ts b/src/generate-nitro-package.ts index 0a895fe7..66fe469c 100644 --- a/src/generate-nitro-package.ts +++ b/src/generate-nitro-package.ts @@ -519,9 +519,9 @@ export class NitroModuleFactory { private async installDependenciesAndRunCodegen() { await execAsync(`${this.config.pm} install`, { cwd: this.config.cwd }) - let packageManager = + const packageManager = this.config.pm === 'npm' ? 'npx --yes' : this.config.pm - let codegenCommand = `${packageManager} nitrogen --logLevel="debug" && ${this.config.pm} run build${Object.values(this.config.platformLangs).includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}` + const codegenCommand = `${packageManager} nitrogen --logLevel="debug" && ${this.config.pm} run build${Object.values(this.config.platformLangs).includes(SupportedLang.KOTLIN) ? ' && node post-script.js' : ''}` await execAsync(codegenCommand, { cwd: this.config.cwd }) } diff --git a/src/utils.ts b/src/utils.ts index ca2822e7..90b6aeb3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,8 +3,8 @@ import { execSync } from 'node:child_process' import { access, cp, mkdir, readFile, writeFile } from 'node:fs/promises' import path from 'node:path' import { - GenerateModuleConfig, - PlatformLangMap, + type GenerateModuleConfig, + type PlatformLangMap, SupportedLang, SupportedPlatform, } from './types' @@ -152,7 +152,7 @@ export const replacePlaceholder = async ({ replacements: Record data?: string }) => { - let fileContent + let fileContent: string if (data) { fileContent = data } else if (filePath) { From 93c60a37bdc15e9f80ee5b54be5b91d439f59c40 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 07:20:32 +0200 Subject: [PATCH 3/7] fix: correct C++ language reference --- src/constants.ts | 2 +- src/file-generators/android-file-generator.ts | 4 ++-- src/file-generators/cpp-file-generator.ts | 4 ++-- src/generate-nitro-package.ts | 2 +- src/utils.ts | 10 +++------- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index e9e14bed..2c456958 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -74,7 +74,7 @@ Begin development: ${kleur.cyan('Implement native code:')} ${kleur.white('ios/')} ${kleur.dim('# iOS native implementation using swift')} ${kleur.white('android/')} ${kleur.dim('# Android native implementation using kotlin')} - ${kleur.white('cpp/')} ${kleur.dim('# C++ native implementation. Shareable between iOS and Android (Will be generated if cpp was selected)')} + ${kleur.white('cpp/')} ${kleur.dim('# C++ native implementation. Shareable between iOS and Android (Will be generated if c++ was selected)')} ${ skipExample diff --git a/src/file-generators/android-file-generator.ts b/src/file-generators/android-file-generator.ts index 5515430b..4475378a 100644 --- a/src/file-generators/android-file-generator.ts +++ b/src/file-generators/android-file-generator.ts @@ -9,8 +9,8 @@ import { import { postScript } from '../code-snippets/code.js' import { ANDROID_CXX_LIB_NAME_TAG, ANDROID_NAME_SPACE_TAG } from '../constants' import { - FileGenerator, - GenerateModuleConfig, + type FileGenerator, + type GenerateModuleConfig, Nitro, SupportedLang, SupportedPlatform, diff --git a/src/file-generators/cpp-file-generator.ts b/src/file-generators/cpp-file-generator.ts index 43396f70..e7002427 100644 --- a/src/file-generators/cpp-file-generator.ts +++ b/src/file-generators/cpp-file-generator.ts @@ -2,8 +2,8 @@ import { readFile } from 'fs/promises' import path from 'path' import { cppCode, hppCode } from '../code-snippets/code.cpp' import { - FileGenerator, - GenerateModuleConfig, + type FileGenerator, + type GenerateModuleConfig, SupportedPlatform, } from '../types' import { createFolder, createModuleFile, toPascalCase } from '../utils' diff --git a/src/generate-nitro-package.ts b/src/generate-nitro-package.ts index 66fe469c..a42a183a 100644 --- a/src/generate-nitro-package.ts +++ b/src/generate-nitro-package.ts @@ -30,7 +30,7 @@ import { CppFileGenerator } from './file-generators/cpp-file-generator' import { IOSFileGenerator } from './file-generators/ios-file-generator' import { JSFileGenerator } from './file-generators/js-file-generator' import { - GenerateModuleConfig, + type GenerateModuleConfig, Nitro, SupportedLang, SupportedPlatform, diff --git a/src/utils.ts b/src/utils.ts index 90b6aeb3..741a0230 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,26 +90,22 @@ export const generateAutolinking = ( if (langs.every(lang => lang === SupportedLang.CPP)) { return { [moduleName]: { - all: { language: 'cpp', implementationClassName: className }, + all: { language: SupportedLang.CPP, implementationClassName: className }, }, } } const entry: AutolinkingEntry = {} - const langToNitroLang = (lang: SupportedLang): string => { - return lang === SupportedLang.CPP ? 'cpp' : lang - } - if (platformLangs[SupportedPlatform.IOS]) { entry.ios = { - language: langToNitroLang(platformLangs[SupportedPlatform.IOS]), + language: platformLangs[SupportedPlatform.IOS], implementationClassName: className, } } if (platformLangs[SupportedPlatform.ANDROID]) { entry.android = { - language: langToNitroLang(platformLangs[SupportedPlatform.ANDROID]), + language: platformLangs[SupportedPlatform.ANDROID], implementationClassName: className, } } From 0b6524541dd0ecc0366fd8f418fb3a07c9f6464f Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 07:21:05 +0200 Subject: [PATCH 4/7] chore: format code --- src/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 741a0230..aa3eb0b4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,7 +90,10 @@ export const generateAutolinking = ( if (langs.every(lang => lang === SupportedLang.CPP)) { return { [moduleName]: { - all: { language: SupportedLang.CPP, implementationClassName: className }, + all: { + language: SupportedLang.CPP, + implementationClassName: className, + }, }, } } From 001f36ca774a1900f98d8bc787e7e82aa670c200 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 13:07:51 +0200 Subject: [PATCH 5/7] fix: c++ with swift or kotlin modules creation --- .github/workflows/ci-packages.yml | 16 ++++++++++++++++ src/cli/create.ts | 12 +++++++++++- src/file-generators/cpp-file-generator.ts | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-packages.yml b/.github/workflows/ci-packages.yml index 14406cb5..f50cb386 100644 --- a/.github/workflows/ci-packages.yml +++ b/.github/workflows/ci-packages.yml @@ -101,6 +101,22 @@ jobs: runs_ios: true, runs_android: true, }, + { + package_type: 'module', + scenario: 'swift-cpp', + platforms: 'ios,android', + langs: 'swift,c++', + runs_ios: true, + runs_android: true, + }, + { + package_type: 'module', + scenario: 'cpp-kotlin', + platforms: 'ios,android', + langs: 'c++,kotlin', + runs_ios: true, + runs_android: true, + }, { package_type: 'module', scenario: 'swift', diff --git a/src/cli/create.ts b/src/cli/create.ts index 04c188ad..7a45302d 100644 --- a/src/cli/create.ts +++ b/src/cli/create.ts @@ -49,7 +49,13 @@ const getAllowedLanguageSelections = ( ) { return [ [SupportedLang.SWIFT, SupportedLang.KOTLIN], - ...(packageType === Nitro.Module ? [[SupportedLang.CPP]] : []), + ...(packageType === Nitro.Module + ? [ + [SupportedLang.CPP], + [SupportedLang.SWIFT, SupportedLang.CPP], + [SupportedLang.CPP, SupportedLang.KOTLIN], + ] + : []), ] } @@ -105,10 +111,14 @@ const getPlatformLangMap = ( if (langs.includes(SupportedLang.SWIFT)) { result[SupportedPlatform.IOS] = SupportedLang.SWIFT + } else if (platforms.includes(SupportedPlatform.IOS)) { + result[SupportedPlatform.IOS] = SupportedLang.CPP } if (langs.includes(SupportedLang.KOTLIN)) { result[SupportedPlatform.ANDROID] = SupportedLang.KOTLIN + } else if (platforms.includes(SupportedPlatform.ANDROID)) { + result[SupportedPlatform.ANDROID] = SupportedLang.CPP } return result diff --git a/src/file-generators/cpp-file-generator.ts b/src/file-generators/cpp-file-generator.ts index e7002427..cf3459be 100644 --- a/src/file-generators/cpp-file-generator.ts +++ b/src/file-generators/cpp-file-generator.ts @@ -74,6 +74,8 @@ export class CppFileGenerator implements FileGenerator { } async generateCppCodeFiles(config: GenerateModuleConfig) { + await createFolder(config.cwd, 'cpp') + const cppPath = path.join( 'cpp', `Hybrid${toPascalCase(config.packageName)}.cpp` From d73e2ef7bca18d1127f69d0a2c5d7e7758da81d6 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 13:36:41 +0200 Subject: [PATCH 6/7] fix: include cpp header files to `CMakeLists.txt` when building on android --- .../$$androidCxxLibName$$Package.kt | 4 +- .../$$androidCxxLibName$$Package_view.kt | 4 +- src/file-generators/cpp-file-generator.ts | 55 ++++++++++--------- src/generate-nitro-package.ts | 6 ++ 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package.kt b/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package.kt index 5fc91fb0..bd907b72 100644 --- a/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package.kt +++ b/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package.kt @@ -3,11 +3,11 @@ package com.$$androidNamespace$$; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.TurboReactPackage; +import com.facebook.react.BaseReactPackage; import com.margelo.nitro.$$androidNamespace$$.$$androidCxxLibName$$OnLoad; -public class $$androidCxxLibName$$Package : TurboReactPackage() { +public class $$androidCxxLibName$$Package : BaseReactPackage() { override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() } diff --git a/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package_view.kt b/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package_view.kt index e54d4c8c..7637cf8f 100644 --- a/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package_view.kt +++ b/assets/template/android/src/main/java/com/$$androidNamespace$$/$$androidCxxLibName$$Package_view.kt @@ -3,13 +3,13 @@ package com.$$androidNamespace$$; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.TurboReactPackage; +import com.facebook.react.BaseReactPackage; import com.facebook.react.uimanager.ViewManager; import com.margelo.nitro.$$androidNamespace$$.*; import com.margelo.nitro.$$androidNamespace$$.views.*; -public class $$androidCxxLibName$$Package : TurboReactPackage() { +public class $$androidCxxLibName$$Package : BaseReactPackage() { override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() } diff --git a/src/file-generators/cpp-file-generator.ts b/src/file-generators/cpp-file-generator.ts index cf3459be..3d7ab82e 100644 --- a/src/file-generators/cpp-file-generator.ts +++ b/src/file-generators/cpp-file-generator.ts @@ -23,31 +23,7 @@ export class CppFileGenerator implements FileGenerator { config.platforms.includes(SupportedPlatform.ANDROID) ) { await generator.generate(config) - // add generate objects to CMakeLists.txt - const cmakeListsPath = path.join( - config.cwd, - 'android', - 'CMakeLists.txt' - ) - let cmakeListsContent = await readFile(cmakeListsPath, { - encoding: 'utf-8', - }) - - cmakeListsContent = cmakeListsContent.replace( - 'src/main/cpp/cpp-adapter.cpp', - '' - ) - cmakeListsContent = cmakeListsContent.replace( - 'add_library(${PACKAGE_NAME} SHARED', - 'add_library(${PACKAGE_NAME} SHARED \n\t' + - `src/main/cpp/cpp-adapter.cpp\n\t../cpp/Hybrid${toPascalCase(config.packageName)}.cpp\n\t../cpp/Hybrid${toPascalCase(config.packageName)}.hpp` - ) - - await createModuleFile( - config.cwd, - 'android/CMakeLists.txt', - cmakeListsContent - ) + await this.updateAndroidCMakeLists(config) continue } @@ -96,4 +72,33 @@ export class CppFileGenerator implements FileGenerator { hppCode(toPascalCase(config.packageName), `${config.funcName}`) ) } + + async updateAndroidCMakeLists(config: GenerateModuleConfig) { + const cmakeListsPath = path.join(config.cwd, 'android', 'CMakeLists.txt') + let cmakeListsContent = await readFile(cmakeListsPath, { + encoding: 'utf-8', + }) + + const hybridCppPath = `../cpp/Hybrid${toPascalCase(config.packageName)}.cpp` + + if (cmakeListsContent.includes(hybridCppPath)) { + return + } + + cmakeListsContent = cmakeListsContent.replace( + 'src/main/cpp/cpp-adapter.cpp', + '' + ) + cmakeListsContent = cmakeListsContent.replace( + 'add_library(${PACKAGE_NAME} SHARED', + 'add_library(${PACKAGE_NAME} SHARED \n\t' + + `src/main/cpp/cpp-adapter.cpp\n\t${hybridCppPath}` + ) + + await createModuleFile( + config.cwd, + 'android/CMakeLists.txt', + cmakeListsContent + ) + } } diff --git a/src/generate-nitro-package.ts b/src/generate-nitro-package.ts index a42a183a..175d52b4 100644 --- a/src/generate-nitro-package.ts +++ b/src/generate-nitro-package.ts @@ -96,6 +96,12 @@ export class NitroModuleFactory { } if (this.config.platformLangs[SupportedPlatform.ANDROID] != null) { await this.androidGenerator.generate(this.config) + if ( + this.config.platformLangs[SupportedPlatform.ANDROID] === + SupportedLang.CPP + ) { + await this.cppGenerator.updateAndroidCMakeLists(this.config) + } } } From ca6ffced9c920de65b5627c98ba14301763af085 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Mon, 16 Mar 2026 13:36:53 +0200 Subject: [PATCH 7/7] fix: include cpp header files to `CMakeLists.txt` when building on android --- src/file-generators/cpp-file-generator.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/file-generators/cpp-file-generator.ts b/src/file-generators/cpp-file-generator.ts index 3d7ab82e..d026c8bd 100644 --- a/src/file-generators/cpp-file-generator.ts +++ b/src/file-generators/cpp-file-generator.ts @@ -74,7 +74,11 @@ export class CppFileGenerator implements FileGenerator { } async updateAndroidCMakeLists(config: GenerateModuleConfig) { - const cmakeListsPath = path.join(config.cwd, 'android', 'CMakeLists.txt') + const cmakeListsPath = path.join( + config.cwd, + 'android', + 'CMakeLists.txt' + ) let cmakeListsContent = await readFile(cmakeListsPath, { encoding: 'utf-8', })